{"openapi":"3.1.0","info":{"title":"OpCreative API","version":"v1","description":"The seller API for OpCreative — place orders, read their progress, and hear about changes over webhooks.\n\nAuthentication is an `x-api-key` header. Money is always a string with two decimals (JSON numbers are float64, and cents do not survive that). Timestamps are ISO-8601 UTC.\n\n- The version is in the path. `/api/v1` keeps its promises for as long as it exists; a change that would break them ships as `/api/v2` instead.\n- Additive changes are NOT breaking and can arrive any day: a new endpoint, a new optional parameter, a new field in a response, a new webhook event. Parse defensively — ignore fields you do not recognise, and do not validate that a response has exactly the keys you expect.\n- Breaking means: removing or renaming a response field, removing an endpoint, making an optional parameter required, narrowing an accepted value, or changing what an existing field means.\n- Error codes are append-only. A new `code` may appear in the envelope; an existing one is never reworded or reused for a different condition. Branch on `error.code`, never on `error.message`.\n- Enum-like values (`status`, tracking states) may gain members. Treat an unknown one as unknown rather than as an error.\n- If v1 is ever retired, you get at least 180 days' notice by email to the account that owns the key, and the endpoints keep working throughout that window."},"servers":[{"url":"https://mcp.opcreative.us/api/v1"}],"security":[{"ApiKeyAuth":[]}],"paths":{"/me":{"get":{"operationId":"get-me","summary":"Who this key belongs to","description":"Returns the account behind the key and its current balance. Small on purpose: it exists so an integration can prove its key works and show a balance without scraping the dashboard.\n\n`tier` is the pricing tier the catalogue quotes against, so a seller can see why they were charged what they were charged.","tags":["me"],"responses":{"200":{"description":"The key's owner.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"example":{"data":{"id":"clx8fh2k90000abcd1234efgh","name":"Bright Prints","email":"orders@brightprints.example","roles":["SELLER"],"tier":2,"balance":"1240.55","debt":"0.00","created_at":"2026-02-03T11:20:00.000Z"}}}}},"401":{"description":"No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"429":{"description":"Over the per-key limit. `Retry-After` says how many seconds to wait.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"500":{"description":"Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}}}},"/catalog":{"get":{"operationId":"get-catalog","summary":"What you can order, and what it costs you","description":"Replaces two legacy endpoints at once: `GET /api/customer/metadata` (what can I order?) and `GET /api/v3/customer/orders/pricing` (what does it cost me?). They were separate, so every integration joined them client-side and each did it slightly differently.\n\nPrices are THIS KEY'S prices — computed against the owner's tier with the same function that charges them at assignment. A price list that differs from the invoice is worse than no price list.\n\n`skus[].id` is what `POST /orders` wants as `product_variant_id`.","tags":["catalog"],"parameters":[{"name":"cursor","in":"query","required":false,"description":"Continue from a previous page — pass the `next_cursor` you were given.","schema":{"type":"integer"},"example":"412"},{"name":"limit","in":"query","required":false,"description":"Products per page, 1–200.","schema":{"type":"integer"},"example":"50"}],"responses":{"200":{"description":"A page of products, each with its orderable SKUs.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"example":{"data":[{"id":12,"name":"Leather Keychain","key":"leather-keychain","thumbnail":"https://cdn.example.com/products/12.png","skus":[{"id":881,"code":"HW-TRK-GK-LG__P-C","status":"ACTIVE","variant":{"id":4,"name":"Black","key":"black"},"price":"12.30"}]}],"next_cursor":412}}}},"401":{"description":"No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"429":{"description":"Over the per-key limit. `Retry-After` says how many seconds to wait.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"500":{"description":"Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}}}},"/orders":{"get":{"operationId":"list-orders","summary":"List your orders","description":"Cursor paginated and filterable. The filters are the same ones the dashboard's own table uses — one shared query builder — so a filtered list here and a filtered table there can never disagree, and no filter can widen what the key is allowed to see.\n\nThe list is deliberately lean. For every parcel, the artwork and the proof photo, read one order with `GET /orders/{id}`.","tags":["orders"],"parameters":[{"name":"cursor","in":"query","required":false,"description":"Continue from a previous page — pass the `next_cursor` you were given.","schema":{"type":"integer"},"example":"48100"},{"name":"limit","in":"query","required":false,"description":"Orders per page, 1–100. Defaults to 50.","schema":{"type":"integer"},"example":"50"},{"name":"status","in":"query","required":false,"description":"Comma-separated fulfilment statuses. Unknown values are ignored rather than erroring.","schema":{"type":"string"},"example":"IN_PRODUCTION,FULFILLED"},{"name":"marketplace","in":"query","required":false,"description":"Comma-separated shop labels, matched verbatim as they were imported.","schema":{"type":"string"},"example":"Etsy UK,Tiktok US"},{"name":"external_id","in":"query","required":false,"description":"Your own order id, matched as a PREFIX. `1047-ETSY-9F2` finds that row; it also finds every `-1`, `-2` row a batch split it into.","schema":{"type":"string"},"example":"1047-ETSY-9F2"},{"name":"paid","in":"query","required":false,"description":"`true` or `false` only. Any other value is treated as no filter at all.","schema":{"type":"boolean"},"example":"false"},{"name":"warehouse","in":"query","required":false,"description":"Comma-separated warehouse ids.","schema":{"type":"string"},"example":"2,3"},{"name":"placed_after","in":"query","required":false,"description":"ISO-8601. Orders placed at or after this moment.","schema":{"type":"string"},"example":"2026-08-01T00:00:00Z"},{"name":"placed_before","in":"query","required":false,"description":"ISO-8601. Orders placed at or before this moment.","schema":{"type":"string"},"example":"2026-08-15T00:00:00Z"}],"responses":{"200":{"description":"A page of orders. `next_cursor` is null on the last page.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"example":{"data":[{"id":48213,"external_id":"1047-ETSY-9F2","marketplace":"Etsy UK","status":"IN_PRODUCTION","quantity":2,"filled":0,"base_cost":"24.60","paid":true,"placed_at":"2026-08-11T09:14:22.000Z","assigned_at":"2026-08-11T10:02:00.000Z","fulfilled_at":null,"deadline":"2026-08-18T00:00:00.000Z","product":{"id":12,"name":"Leather Keychain","key":"leather-keychain"},"variant":{"id":4,"name":"Black","key":"black"},"sku":{"id":881,"code":"HW-TRK-GK-LG__P-C"},"warehouse":{"id":2,"code":"US-E","name":"US East"},"tracking":{"number":"9400111899561234567890","status":"In transit","carrier":"usps"},"note":"Gift wrap"}],"next_cursor":48100}}}},"401":{"description":"No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"429":{"description":"Over the per-key limit. `Retry-After` says how many seconds to wait.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"500":{"description":"Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}}},"post":{"operationId":"create-order","summary":"Create one order","description":"Name the SKU and where it goes. The SKU decides the product, the variant and the price — sending a product id alongside would let a client name its own price, so the server reads all three from the SKU and ignores anything else you send about them.\n\nTwo ways to name what you want: `product_variant_id` from the catalogue, or `product_id` plus an `options` map of catalog-v2 axis codes (the server resolves those to a physical SKU, creating it on first use).\n\nAn `Idempotency-Key` header is REQUIRED. Without one, a dropped response leaves you unable to retry safely — which is exactly how duplicate orders get made. Replaying a key returns the original order with status 200 instead of 201.","tags":["orders"],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Any unique string of your choosing. Scoped to your account, so it cannot collide with another seller's.","schema":{"type":"string"},"example":"order-1047-attempt-1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["external_id","quantity","shipping"],"properties":{"external_id":{"type":"string","description":"Your own order id. Not unique here — one marketplace order can split into several rows — but it is how support finds anything."},"product_variant_id":{"type":"integer","description":"The SKU, from `GET /catalog`. Send this OR `product_id` + `options`."},"product_id":{"type":"integer","description":"Catalog v2: the product, paired with `options`."},"options":{"type":"object","description":"Catalog v2: one active value code per option axis, e.g. `{ \"method\": \"P\", \"color\": \"KL\" }`. Keys are axis codes and are NOT converted — send them exactly as the catalogue lists them."},"quantity":{"type":"integer","description":"1–10000."},"shipping":{"type":"object","description":"`name`, `company`, `email`, `phone`, `line1`, `line2`, `city`, `state`, `zip`, `country`. Only `zip` is required — marketplaces like Amazon withhold buyer names, and rejecting those rows loses real orders."},"marketplace":{"type":"string","description":"Which shop it came from, e.g. `Etsy UK`."},"placed_at":{"type":"string","description":"When the marketplace took the order. Defaults to now."},"deadline":{"type":"string","description":"When it must ship by."},"image_url":{"type":"string","description":"The design to print."},"mockup_url":{"type":"string","description":"The mockup picture — what the listing shows. Stored once per picture per account, so re-sending the same link reuses the same mockup rather than making another."},"note":{"type":"string","description":"Anything the floor should read."}}},"example":{"external_id":"1047-ETSY-9F2","marketplace":"Etsy UK","product_variant_id":881,"quantity":2,"image_url":"https://cdn.example.com/designs/1047.png","mockup_url":"https://cdn.example.com/mockups/1047.png","note":"Gift wrap","shipping":{"name":"A. Recipient","line1":"18 Example Street","city":"Washington","state":"DC","zip":"20002","country":"US"}}}}},"responses":{"201":{"description":"Created. Status 200 instead when the Idempotency-Key was a replay.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"example":{"data":{"id":48213}}}}},"401":{"description":"No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"403":{"description":"The key is valid but its owner may not do this.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"422":{"description":"The body or query is malformed. Field-level detail arrives in `error.fields`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"429":{"description":"Over the per-key limit. `Retry-After` says how many seconds to wait.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"500":{"description":"Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}}}},"/orders/match":{"post":{"operationId":"match-orders","summary":"Match rows to catalog SKUs","description":"Turn the data you already have — a listing title, a variant string, a SKU — into the `product_variant_id` or `product_id` + `options` that `POST /orders/batch` expects. Nothing is created: this returns suggestions with a confidence, and you decide what to send.\n\nTwo deterministic passes run on every request and cost nothing: an exact SKU reference, then an exact product-plus-option-label match. Well-formed rows resolve here.\n\nThe AI pass is BRING YOUR OWN KEY. Send your own provider credentials in the `X-AI-*` headers and the leftover rows go to your model, on your bill — we never charge our own AI to your requests, and your key is used for that one call and never stored. Send no `X-AI-*` headers and you get the free passes only, with the rest reported as `unmatched`.\n\n`claude-sonnet-5` is the recommended model and what this endpoint is tested against. Matching quality is the model's, so a cheaper one matches worse.\n\nThis endpoint carries its own rate limit of 200 requests per minute, tighter than the API's usual 600. One match call builds the whole catalog digest and runs up to 200 rows, so it costs far more than a plain read. It counts in a separate bucket, so matching never eats the budget for reading your orders.","tags":["orders"],"parameters":[{"name":"X-AI-Provider","in":"header","required":false,"description":"`anthropic` (default) or `openai-compatible`. Omit every `X-AI-*` header to skip the AI pass.","schema":{"type":"string"},"example":"anthropic"},{"name":"X-AI-Key","in":"header","required":false,"description":"Your own provider key. Used for this request only — never stored, never logged, never returned.","schema":{"type":"string"},"example":"sk-ant-…"},{"name":"X-AI-Model","in":"header","required":false,"description":"The model id, e.g. `claude-sonnet-5`. Required whenever a key is sent.","schema":{"type":"string"},"example":"claude-sonnet-5"},{"name":"X-AI-Base-Url","in":"header","required":false,"description":"Required for `openai-compatible` — the endpoint your provider serves, e.g. `https://api.openai.com/v1`.","schema":{"type":"string"},"example":"https://api.openai.com/v1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","description":"1–200 objects. Loose key/value pairs, exactly as your export has them — `product`, `variant`, `sku`, `quantity`, or your own column names. Unknown keys are ignored rather than rejected."}}},"example":{"rows":[{"product":"Personalized Leather Keychain","variant":"Tan / Center","quantity":2},{"sku":"HW-TRK-GK-LG__P-C","quantity":1}]}}}},"responses":{"200":{"description":"Per-row results. `status` is `matched` (deterministic, confidence 1), `suggested` (your AI proposed it) or `unmatched`. Every suggestion is re-verified against the catalog before it is returned.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"example":{"data":{"matched":1,"suggested":1,"unmatched":0,"ai_used":true,"results":[{"row":0,"status":"suggested","via":"ai","confidence":0.92,"product_variant_id":null,"product_id":41,"sku":null,"product":"Leather Keychain","options":{"color":"TN","print_area":"C"},"option_labels":[{"axis":"Colour","label":"Tan"},{"axis":"Print area","label":"Center"}]},{"row":1,"status":"matched","via":"sku","confidence":1,"product_variant_id":881,"product_id":null,"sku":"HW-TRK-GK-LG__P-C","product":"Trucker Hat","options":{},"option_labels":[]}]}}}}},"401":{"description":"No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"403":{"description":"The key is valid but its owner may not do this.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"422":{"description":"The body or query is malformed. Field-level detail arrives in `error.fields`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"429":{"description":"Over the per-key limit. `Retry-After` says how many seconds to wait.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"500":{"description":"Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}}}},"/orders/batch":{"post":{"operationId":"create-orders-batch","summary":"Create one multi-item order","description":"One shipping block, many items — the replacement for legacy's `POST /api/v2/customer/orders/multi`. Each item becomes its own order row, suffixed `-1`, `-2`, … off the shared `external_id`, which is the convention legacy sellers already handle. A single-item batch is NOT suffixed: the same order should not arrive under two different ids depending on how it was sent.\n\nOmit `external_id` and one is generated for you (legacy did this too) — but an id you chose is the one support can search for.\n\nIt reports PER ROW. A batch whose third item names a dead SKU creates the other items and tells you about the third; it is never all-or-nothing and never silently partial. Read `results[]`, not just the status code.\n\nThe `Idempotency-Key` is derived per row, so retrying a batch that half-succeeded creates exactly the missing half.\n\nAt most 500 items per request.","tags":["orders"],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Any unique string. Each row derives its own key from it.","schema":{"type":"string"},"example":"batch-1047-attempt-1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["shipping","items"],"properties":{"external_id":{"type":"string","description":"Your marketplace order id. Generated if omitted."},"shipping":{"type":"object","description":"Shared by every item — same fields as `POST /orders`."},"items":{"type":"array","description":"1–500 entries. Each takes the same per-item fields as `POST /orders` (`product_variant_id` or `product_id`+`options`, `quantity`, `image_url`, `mockup_url`, `note`) and inherits anything set at the top level. Artwork is per item: one order can hold several products, each printed from its own design and mockup."},"marketplace":{"type":"string","description":"Applied to every row."},"placed_at":{"type":"string","description":"Applied to every row."},"mockup_url":{"type":"string","description":"The mockup picture — what the listing shows. Stored once per picture per account, so re-sending the same link reuses the same mockup rather than making another."}}},"example":{"external_id":"1047-ETSY-9F2","marketplace":"Etsy UK","shipping":{"name":"A. Recipient","line1":"18 Example Street","city":"Washington","state":"DC","zip":"20002","country":"US"},"items":[{"product_variant_id":881,"quantity":2,"image_url":"https://cdn.example.com/designs/1047-a.png","mockup_url":"https://cdn.example.com/mockups/1047-a.png"},{"product_variant_id":903,"quantity":1,"image_url":"https://cdn.example.com/designs/1047-b.png","note":"Second initial"}]}}}},"responses":{"201":{"description":"201 when at least one row was created, 200 when every row was a replay, 422 when nothing landed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"example":{"data":{"external_id":"1047-ETSY-9F2","created":1,"deduped":0,"failed":1,"results":[{"index":0,"external_id":"1047-ETSY-9F2-1","id":48213,"deduped":false},{"index":1,"external_id":"1047-ETSY-9F2-2","error":"That SKU is not currently active and cannot be ordered."}]}}}}},"401":{"description":"No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"403":{"description":"The key is valid but its owner may not do this.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"413":{"description":"The request body is over 2 MB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"422":{"description":"The body or query is malformed. Field-level detail arrives in `error.fields`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"429":{"description":"Over the per-key limit. `Retry-After` says how many seconds to wait.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"500":{"description":"Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}}}},"/orders/{id}":{"get":{"operationId":"get-order","summary":"Read one order in full","description":"Everything the list gives, plus every shipment, the design, the proof photo, the shipping address and the catalog-v2 option codes.\n\n`shipments[]` is why this endpoint is worth a second call: a split or re-shipped order has several parcels and the list's `tracking` field shows only the newest.\n\nAn id belonging to another seller resolves to a 404, not a 403 — a 403 would confirm the id exists.","tags":["orders"],"parameters":[{"name":"id","in":"path","required":true,"description":"The order id from a list response.","schema":{"type":"integer"}}],"responses":{"200":{"description":"One order.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"example":{"data":{"id":48213,"external_id":"1047-ETSY-9F2","marketplace":"Etsy UK","status":"IN_PRODUCTION","quantity":2,"filled":0,"base_cost":"24.60","paid":true,"placed_at":"2026-08-11T09:14:22.000Z","assigned_at":"2026-08-11T10:02:00.000Z","fulfilled_at":null,"deadline":"2026-08-18T00:00:00.000Z","product":{"id":12,"name":"Leather Keychain","key":"leather-keychain"},"variant":{"id":4,"name":"Black","key":"black"},"sku":{"id":881,"code":"HW-TRK-GK-LG__P-C"},"warehouse":{"id":2,"code":"US-E","name":"US East"},"tracking":{"number":"9400111899561234567890","status":"In transit","carrier":"usps"},"note":"Gift wrap","shipments":[{"tracking_number":"9400111899561234567890","tracking_status":"In transit","carrier":"usps","method":"Standard","cost":"4.35","label_url":"https://files.opcreative.us/labels/9400111899561234567890.pdf","created_at":"2026-08-12T16:40:11.000Z"}],"image_url":"https://cdn.example.com/designs/1047.png","proof_image_url":"https://files.opcreative.us/proofs/48213.jpg","mockup":{"id":91022,"name":"Keychain — initials","url":"https://cdn.example.com/mockups/91022.png"},"shipping_address":{"name":"A. Recipient","company":null,"line1":"18 Example Street","line2":null,"city":"Washington","state":"DC","zip":"20002","country":"US"},"options":{"method":"P","color":"KL","patch_color":"LG","print_area":"C"}}}}}},"401":{"description":"No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"404":{"description":"No such record — including records that exist but belong to someone else. The API never confirms another seller's ids.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"429":{"description":"Over the per-key limit. `Retry-After` says how many seconds to wait.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"500":{"description":"Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}}},"patch":{"operationId":"patch-order","summary":"Update one order","description":"Replaces THREE legacy routes — `patch`, `resolve-design` and `resolve-label` — because from a seller's point of view they were one act: here is the missing piece, put my order back in the queue. What you send decides which.\n\nSending `image_url` to an order that is ON_HOLD releases it back to where it was held from. Sending `label_url` and `tracking_number` together attaches a shipment through the same code path the scan stations use, so the seller notification and the audit trail are identical.\n\nAbsent means leave it alone. Unknown field names are rejected rather than ignored — a typo you can see beats a silent no-op.\n\n`quantity` is the one field locked after assignment: it changes what somebody has to make.\n\nNo `Idempotency-Key` is needed. A PATCH sets fields to the values you send, so replaying one lands on the same state.","tags":["orders"],"parameters":[{"name":"id","in":"path","required":true,"description":"The order id.","schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"note":{"type":"string","description":"Free text for the floor."},"quantity":{"type":"integer","description":"Only while the order is still PENDING."},"image_url":{"type":"string","description":"The design. Releases an ON_HOLD order."},"external_id":{"type":"string","description":"Your own order id."},"deadline":{"type":"string","description":"When it must ship by."},"shipping":{"type":"object","description":"Any of `name`, `company`, `email`, `phone`, `line1`, `line2`, `city`, `state`, `zip`, `country`."},"label_url":{"type":"string","description":"A label you bought yourself. Must be sent with `tracking_number`."},"tracking_number":{"type":"string","description":"Must be sent with `label_url`."},"carrier":{"type":"string","description":"Optional carrier name for the shipment."}}},"example":{"image_url":"https://cdn.example.com/designs/1047.png","note":"Design attached"}}}},"responses":{"200":{"description":"The updated order in full, so you never need a follow-up GET. `meta.released` says whether a hold was lifted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"example":{"data":{"id":48213,"external_id":"1047-ETSY-9F2","marketplace":"Etsy UK","status":"IN_PRODUCTION","quantity":2,"filled":0,"base_cost":"24.60","paid":true,"placed_at":"2026-08-11T09:14:22.000Z","assigned_at":"2026-08-11T10:02:00.000Z","fulfilled_at":null,"deadline":"2026-08-18T00:00:00.000Z","product":{"id":12,"name":"Leather Keychain","key":"leather-keychain"},"variant":{"id":4,"name":"Black","key":"black"},"sku":{"id":881,"code":"HW-TRK-GK-LG__P-C"},"warehouse":{"id":2,"code":"US-E","name":"US East"},"tracking":{"number":"9400111899561234567890","status":"In transit","carrier":"usps"},"note":"Gift wrap","shipments":[{"tracking_number":"9400111899561234567890","tracking_status":"In transit","carrier":"usps","method":"Standard","cost":"4.35","label_url":"https://files.opcreative.us/labels/9400111899561234567890.pdf","created_at":"2026-08-12T16:40:11.000Z"}],"image_url":"https://cdn.example.com/designs/1047.png","proof_image_url":"https://files.opcreative.us/proofs/48213.jpg","mockup":{"id":91022,"name":"Keychain — initials","url":"https://cdn.example.com/mockups/91022.png"},"shipping_address":{"name":"A. Recipient","company":null,"line1":"18 Example Street","line2":null,"city":"Washington","state":"DC","zip":"20002","country":"US"},"options":{"method":"P","color":"KL","patch_color":"LG","print_area":"C"}},"meta":{"released":true,"label_linked":false}}}}},"401":{"description":"No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"404":{"description":"No such record — including records that exist but belong to someone else. The API never confirms another seller's ids.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"422":{"description":"The body or query is malformed. Field-level detail arrives in `error.fields`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"429":{"description":"Over the per-key limit. `Retry-After` says how many seconds to wait.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}},"500":{"description":"Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}}}}},"webhooks":{"order_status":{"post":{"operationId":"webhook-order_status","summary":"An order reaches IN_PRODUCTION, FULFILLED or ON_HOLD. The old system documented this event but never actually sent it — if your receiver only ever saw shipping_added, this is new traffic.","description":"Signed with HMAC-SHA256 over the exact request bytes, in the X-Signature header. Respond 2xx; anything else is retried with backoff.","parameters":[{"name":"X-Signature","in":"header","required":true,"description":"HMAC-SHA256 of the raw body, keyed with your webhook secret, hex encoded.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"type":"order_status","data":{"id":48213,"order_id":"1047-ETSY-9F2","status":"IN_PRODUCTION","note":null,"updated_at":"2026-08-12T16:40:11.000Z"}}}}},"responses":{"200":{"description":"Acknowledged."}}}},"shipping_added":{"post":{"operationId":"webhook-shipping_added","summary":"A label is attached to your parcel, whether we bought it or the floor scanned one in. One event per parcel per seller — three of your items in one box is one event, not three.","description":"Signed with HMAC-SHA256 over the exact request bytes, in the X-Signature header. Respond 2xx; anything else is retried with backoff.","parameters":[{"name":"X-Signature","in":"header","required":true,"description":"HMAC-SHA256 of the raw body, keyed with your webhook secret, hex encoded.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"type":"shipping_added","data":{"tracking_number":"9400111899561234567890","label_url":"https://files.opcreative.us/labels/9400111899561234567890.pdf","provider":"usps","orders":[{"id":48213,"order_id":"1047-ETSY-9F2"}],"updated_at":"2026-08-12T16:41:02.000Z"}}}}},"responses":{"200":{"description":"Acknowledged."}}}},"tracking_status":{"post":{"operationId":"webhook-tracking_status","summary":"The carrier reports movement. Every change, not just the notable ones — a machine reading a feed wants the whole route.","description":"Signed with HMAC-SHA256 over the exact request bytes, in the X-Signature header. Respond 2xx; anything else is retried with backoff.","parameters":[{"name":"X-Signature","in":"header","required":true,"description":"HMAC-SHA256 of the raw body, keyed with your webhook secret, hex encoded.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"type":"tracking_status","data":{"tracking_number":"9400111899561234567890","status":"Delivered","detail":"Left with individual","updated_at":"2026-08-14T18:03:44.000Z"}}}}},"responses":{"200":{"description":"Acknowledged."}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"Create one in your dashboard under Profile → API."}},"headers":{"XRequestId":{"description":"This request's id. Echoed from your own X-Request-Id when you send one; quote it to support.","schema":{"type":"string"}},"XRateLimitLimit":{"description":"Requests allowed per minute (600).","schema":{"type":"integer"}},"XRateLimitRemaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"XRateLimitReset":{"description":"Seconds until the window resets. A duration, not a timestamp.","schema":{"type":"integer"}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","conflict","payload_too_large","invalid_request","rate_limited","internal"]},"message":{"type":"string"},"fields":{"type":"array","description":"Present on invalid_request: which field, and why.","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}}},"request_id":{"type":"string","description":"Present on internal; matches the X-Request-Id header."}}}}}}}}