# Management Store Etsy Public API — Full Reference

REST API for Etsy shop and order data, including manual fulfillment updates. Authenticate with Bearer API keys (mse_ prefix).

## Overview

- v1 exposes GET endpoints (list/get orders, list shops) and write endpoints for manual fulfillment (PATCH/DELETE). Keys are created in the web app under Settings → API access.
- Default key scopes are orders:read and shops:read. Opt in to orders:write when creating a key to update or cancel manual fulfillment.
- Each API key is tenant-scoped: it can only access shops linked to the license keys owned by the key creator.
- ROOT/ADMIN users do not get system-wide access via API keys — only license-owned shops apply.
- List endpoints accept multiple query parameters at once (filters combine with AND).
- Orders: shopEtsyId accepts multiple shops: repeat ?shopEtsyId=a&shopEtsyId=b or comma-separated ?shopEtsyId=a,b. Alias query key: shop.
- Never log or commit raw API keys. Use environment variables or secret managers in production integrations.

## Authentication

- Header: `Authorization: Bearer mse_<secret>`
- Key prefix: `mse_`

## Response formats

- List responses use { data: T[], pagination: { page, limit, total, totalPages } }.
- Single-resource responses use { data: T } (no pagination).
- Errors return { error: string, code?: string } with appropriate HTTP status.

Rate limit: 60 requests per 60 seconds per API key.

## Links

- OpenAPI: https://etsy.lntech-ecommerce.com/openapi.json
- Index: https://etsy.lntech-ecommerce.com/llms.txt
- Explorer: https://etsy.lntech-ecommerce.com/developers

## Endpoints

## GET /api/v1/orders

**Operation ID:** `listOrders`  
**Scope:** `orders:read`  
**Group:** Orders

Returns a paginated list of orders for shops accessible to your API key. Combine filters (shops, dates, search, fulfillment, tracking, sort). Requires the orders:read scope.

### Path parameters

_None._

### Query parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| page | number | no | 1 | Page number (1-based). Default: 1. |
| limit | number | no | 20 | Items per page (max 100). Default: 20. |
| shopEtsyId | string[] | no |  | Filter one or more shops by Etsy shop ID (shopEtsyId from the response). Add multiple IDs below or use comma-separated values. Omit for all accessible shops. |
| search | string | no |  | Search order ID, recipient name, or address fields. |
| startDate | date | no |  | Inclusive start date (YYYY-MM-DD) for orderDate filter. |
| endDate | date | no |  | Inclusive end date (YYYY-MM-DD) for orderDate filter. |
| timeZone | string | no | Asia/Bangkok | IANA time zone for date filters. Default: Asia/Bangkok. |
| fulfillmentStatus | string | no |  | Filter by fulfillment: fulfilled, synced_to_provider, manual, pending, or failed. |
| tracking | string | no |  | Filter by tracking: has (with tracking number) or none. |
| sortBy | string | no | orderDate | Sort field: orderDate, orderId, recipientName, totalCost, or shop. Default: orderDate. |
| sortOrder | string | no | desc | asc or desc. Default: desc. |

### Example request

```bash
curl -sS -X GET \
  -H 'Authorization: Bearer mse_YOUR_API_KEY' \
  -H 'Accept: application/json' \
  'https://etsy.lntech-ecommerce.com/api/v1/orders?page=1&limit=20'
```

### Example response

```json
{
  "data": [
    {
      "id": "clx000000000000000000000001",
      "shopId": "clx000000000000000000000002",
      "orderId": "1234567890",
      "orderDate": "2026-07-31T10:00:00.000Z",
      "totalCostFormatted": "$24.99",
      "currencyCode": "USD",
      "shop": {
        "id": "clx000000000000000000000002",
        "shopName": "My Etsy Shop",
        "shopEtsyId": "12345"
      },
      "items": []
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "totalPages": 1
  }
}
```

### Errors

- **401** (`MISSING_API_KEY`): Missing or invalid API key.
- **401** (`INVALID_API_KEY`): Missing or invalid API key.
- **400** (`INVALID_QUERY_PARAM`): One or more query parameters are invalid.
- **400** (`INVALID_DATE_FORMAT`): Date query parameters must use YYYY-MM-DD.
- **403** (`FORBIDDEN_SHOP`): One or more Etsy shop IDs are not accessible for this key.
- **429** (`RATE_LIMITED`): Too many requests. Retry after the Retry-After header value.

## GET /api/v1/orders/{id}

**Operation ID:** `getOrder`  
**Scope:** `orders:read`  
**Group:** Orders

Returns a single order by its internal UUID (`id` from the list response), including line items and shop summary. The order must belong to a shop accessible to your API key. Requires the orders:read scope.

### Path parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| id | string | yes |  | Internal order UUID from the list response `id` field (not the Etsy receipt / orderId). |

### Query parameters

_None._

### Example request

```bash
curl -sS -X GET \
  -H 'Authorization: Bearer mse_YOUR_API_KEY' \
  -H 'Accept: application/json' \
  'https://etsy.lntech-ecommerce.com/api/v1/orders/clx000000000000000000000001'
```

### Example response

```json
{
  "data": {
    "id": "clx000000000000000000000001",
    "shopId": "clx000000000000000000000002",
    "orderId": "1234567890",
    "orderDate": "2026-07-31T10:00:00.000Z",
    "orderUrl": null,
    "totalCost": 24.99,
    "totalCostFormatted": "$24.99",
    "totalBaseCost": null,
    "baseCostUpdatedAt": null,
    "currencyCode": "USD",
    "itemsCost": 24.99,
    "shippingCost": 0,
    "taxCost": 0,
    "discount": 0,
    "recipientName": "Jane Doe",
    "recipientEmail": null,
    "recipientPhone": null,
    "addressLine1": "123 Main St",
    "addressLine2": null,
    "city": "Austin",
    "state": "TX",
    "zip": "78701",
    "country": "US",
    "noteFromBuyer": null,
    "expectedShipDate": null,
    "paymentDate": "2026-07-31T10:00:00.000Z",
    "shippingMethod": null,
    "hasTracking": false,
    "fulfillmentProviderType": null,
    "providerStatus": null,
    "lenfulSyncStatus": "PENDING",
    "merchizeSyncStatus": "PENDING",
    "mangoteeSyncStatus": "PENDING",
    "burgerPrintsSyncStatus": "PENDING",
    "syncedAt": "2026-07-31T10:05:00.000Z",
    "createdAt": "2026-07-31T10:05:00.000Z",
    "updatedAt": "2026-07-31T10:05:00.000Z",
    "shop": {
      "id": "clx000000000000000000000002",
      "shopName": "My Etsy Shop",
      "shopEtsyId": "12345"
    },
    "items": []
  }
}
```

### Errors

- **401** (`MISSING_API_KEY`): Missing or invalid API key.
- **401** (`INVALID_API_KEY`): Missing or invalid API key.
- **404** (`ORDER_NOT_FOUND`): Order not found, or not accessible with this API key.
- **429** (`RATE_LIMITED`): Too many requests. Retry after the Retry-After header value.

## POST /api/v1/orders/{id}/manual-fulfillment

**Operation ID:** `markManualFulfillment`  
**Scope:** `orders:write`  
**Group:** Orders

Marks an order as manually fulfilled (`fulfillmentProviderType` = manual). Idempotent if already manual. Fails with 409 if the order is already synced to a real fulfillment provider. Optional tracking (`trackingNumber`, `trackingCompany`, `trackingUrl`) may be set only in this request — there is no separate public API to update tracking later. Requires the orders:write scope.

### Path parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| id | string | yes |  | Internal order UUID from the list response `id` field (not the Etsy receipt / orderId). |

### Query parameters

_None._

### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| trackingNumber | string | no | Carrier tracking number (optional). Omit or send an empty string for no tracking number. |
| trackingCompany | string | no | Carrier / company name (optional). |
| trackingUrl | string | no | Public tracking URL (optional). Must be a valid URL when provided. |

### Example request

```bash
curl -sS -X POST \
  -H 'Authorization: Bearer mse_YOUR_API_KEY' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data-raw '{"trackingNumber":"1Z999AA10123456784","trackingCompany":"UPS","trackingUrl":"https://www.ups.com/track?tracknum=1Z999AA10123456784"}' \
  'https://etsy.lntech-ecommerce.com/api/v1/orders/clx000000000000000000000001/manual-fulfillment'
```

### Example response

```json
{
  "data": {
    "id": "clx000000000000000000000001",
    "fulfillmentProviderType": "manual",
    "hasTracking": false,
    "tracking": null,
    "updatedAt": "2026-08-04T10:00:00.000Z"
  }
}
```

### Errors

- **401** (`MISSING_API_KEY`): Missing or invalid API key.
- **401** (`INVALID_API_KEY`): Missing or invalid API key.
- **400** (`VALIDATION_ERROR`): Request body failed validation.
- **403** (`INSUFFICIENT_SCOPE`): API key is missing a required scope for this operation.
- **404** (`ORDER_NOT_FOUND`): Order not found, or not accessible with this API key.
- **409** (`ALREADY_SYNCED_TO_PROVIDER`): Order is already synced to a real fulfillment provider and cannot be marked as manual.
- **429** (`RATE_LIMITED`): Too many requests. Retry after the Retry-After header value.

## DELETE /api/v1/orders/{id}/manual-fulfillment

**Operation ID:** `cancelManualFulfillment`  
**Scope:** `orders:write`  
**Group:** Orders

Removes manual fulfillment from an order (inverse of mark): clears `fulfillmentProviderType`, deletes stored manual tracking, and recomputes `hasTracking`. Requires the orders:write scope. The order must already be manually fulfilled.

### Path parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| id | string | yes |  | Internal order UUID from the list response `id` field (not the Etsy receipt / orderId). |

### Query parameters

_None._

### Example request

```bash
curl -sS -X DELETE \
  -H 'Authorization: Bearer mse_YOUR_API_KEY' \
  -H 'Accept: application/json' \
  'https://etsy.lntech-ecommerce.com/api/v1/orders/clx000000000000000000000001/manual-fulfillment'
```

### Example response

```json
{
  "data": {
    "id": "clx000000000000000000000001",
    "fulfillmentProviderType": null,
    "hasTracking": false,
    "updatedAt": "2026-08-03T10:00:00.000Z"
  }
}
```

### Errors

- **401** (`MISSING_API_KEY`): Missing or invalid API key.
- **401** (`INVALID_API_KEY`): Missing or invalid API key.
- **403** (`INSUFFICIENT_SCOPE`): API key is missing a required scope for this operation.
- **404** (`ORDER_NOT_FOUND`): Order not found, or not accessible with this API key.
- **409** (`NOT_MANUAL_FULFILLMENT`): Order is not currently manually fulfilled.
- **429** (`RATE_LIMITED`): Too many requests. Retry after the Retry-After header value.

## PATCH /api/v1/orders/{id}/base-cost

**Operation ID:** `updateOrderBaseCost`  
**Scope:** `orders:write`  
**Group:** Orders

Sets or clears the internal cost of goods (`totalBaseCost`) for an order. Pass a non-negative number in dollars, or `null` to clear. Also updates `baseCostUpdatedAt`. Requires the orders:write scope. The order must belong to a shop accessible to your API key.

### Path parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| id | string | yes |  | Internal order UUID from the list response `id` field (not the Etsy receipt / orderId). |

### Query parameters

_None._

### Request body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| totalBaseCost | number or null | yes | Internal cost of goods in dollars (non-negative number), or null to clear. |

### Example request

```bash
curl -sS -X PATCH \
  -H 'Authorization: Bearer mse_YOUR_API_KEY' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data-raw '{"totalBaseCost":22}' \
  'https://etsy.lntech-ecommerce.com/api/v1/orders/clx000000000000000000000001/base-cost'
```

### Example response

```json
{
  "data": {
    "id": "clx000000000000000000000001",
    "totalBaseCost": 22,
    "baseCostUpdatedAt": "2026-08-04T08:00:00.000Z",
    "updatedAt": "2026-08-04T08:00:01.000Z"
  }
}
```

### Errors

- **401** (`MISSING_API_KEY`): Missing or invalid API key.
- **401** (`INVALID_API_KEY`): Missing or invalid API key.
- **400** (`VALIDATION_ERROR`): Request body failed validation.
- **403** (`INSUFFICIENT_SCOPE`): API key is missing a required scope for this operation.
- **404** (`ORDER_NOT_FOUND`): Order not found, or not accessible with this API key.
- **429** (`RATE_LIMITED`): Too many requests. Retry after the Retry-After header value.

## GET /api/v1/shops

**Operation ID:** `listShops`  
**Scope:** `shops:read`  
**Group:** Shops

Returns a paginated list of shops accessible to your API key. Filter by status or search by name / Etsy shop ID. Requires the shops:read scope.

### Path parameters

_None._

### Query parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| page | number | no | 1 | Page number (1-based). Default: 1. |
| limit | number | no | 20 | Items per page (max 100). Default: 20. |
| search | string | no |  | Search by shop name or Etsy shop ID. |
| status | string | no |  | Filter by shop status: ACTIVE only (API keys only access active shops). |
| sortBy | string | no | shopName | Sort field: shopName, shopEtsyId, lastSyncAt, or createdAt. Default: shopName. |
| sortOrder | string | no | asc | asc or desc. Default: asc. |

### Example request

```bash
curl -sS -X GET \
  -H 'Authorization: Bearer mse_YOUR_API_KEY' \
  -H 'Accept: application/json' \
  'https://etsy.lntech-ecommerce.com/api/v1/shops?page=1&limit=20'
```

### Example response

```json
{
  "data": [
    {
      "id": "clx000000000000000000000002",
      "shopName": "My Etsy Shop",
      "shopEtsyId": "12345",
      "avatar": null,
      "status": "ACTIVE",
      "syncStatus": "success",
      "lastSyncAt": "2026-07-31T10:00:00.000Z",
      "createdAt": "2026-01-15T08:00:00.000Z",
      "updatedAt": "2026-07-31T10:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "totalPages": 1
  }
}
```

### Errors

- **401** (`MISSING_API_KEY`): Missing or invalid API key.
- **401** (`INVALID_API_KEY`): Missing or invalid API key.
- **400** (`INVALID_QUERY_PARAM`): One or more query parameters are invalid.
- **429** (`RATE_LIMITED`): Too many requests. Retry after the Retry-After header value.

## Agent instructions

- Prefer machine-readable docs: /llms.txt (index), /llms-full.txt (full reference), /openapi.json (schema).
- Always send Authorization: Bearer <api_key> and Accept: application/json.
- Respect 429 responses and Retry-After header.
- Do not scrape the React /developers UI when structured docs are available.