{"openapi":"3.1.0","info":{"title":"TCGPlaytest Partner API","version":"1.2.0","description":"Public /v1 artwork API: optional direct upload → create → poll → redirect. Upload to TCGPlaytest-owned storage or supply existing HTTPS image URLs. Human setup at https://partner.tcgplaytest.com/dashboard is required: sign in, complete the partner application, accept current partner terms and create a key. Keys stay on your server. Do not send certification, affiliate_code or pricing fields. There is no finalize endpoint. Customers certify image rights at hosted checkout. This document revision does not change the /v1 path version."},"servers":[{"url":"https://partner.tcgplaytest.com","description":"API base for this partner platform deployment; use keys issued by this environment."}],"externalDocs":{"url":"https://partner.tcgplaytest.com/docs","description":"Setup, constraints, responsibilities and integration guide"},"security":[{"PartnerApiKey":[]}],"tags":[{"name":"Uploads"},{"name":"Carts"},{"name":"Orders"},{"name":"Widget"},{"name":"Sandbox"},{"name":"Prepaid"}],"paths":{"/v1/prepaid/balance":{"get":{"operationId":"getPrepaidBalance","tags":["Prepaid"],"summary":"Read your active-mode USD balance and 50 ledger entries","parameters":[{"name":"before","in":"query","schema":{"type":"string","pattern":"^[0-9]{1,18}$"},"description":"Use next_before to fetch older entries."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidBalance"}}}},"400":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Insufficient available credit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Expired/unavailable reservation, hold or idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"description":"Counts toward the shared 60 requests per API key per minute, including quote polling, balance reads, reserve/release and retries. This is not a separate prepaid allowance. Poll quotes every five seconds or slower and budget across all concurrent orders. On 429, honor Retry-After; retry mutations with the same body and idempotency key where required. Only authenticated cart-status GETs are exempt."}},"/v1/prepaid/quotes":{"post":{"operationId":"createPrepaidQuote","tags":["Prepaid"],"summary":"Quote printing, shipping and tax for a validated prepaid cart","description":"Requires prepaid terms and settlement=prepaid. An existing live partner API key works with either Stripe environment; Stripe credentials select the payment environment. Test credentials use isolated balances and simulated orders. Immutable address and price. Expires in at most 30 minutes, or sooner with the cart. No balance is reserved yet. Identical retries return the saved quote. Counts toward the shared 60 requests per API key per minute, including quote polling, balance reads, reserve/release and retries. This is not a separate prepaid allowance. Poll quotes every five seconds or slower and budget across all concurrent orders. On 429, honor Retry-After; retry mutations with the same body and idempotency key where required. Only authenticated cart-status GETs are exempt.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[!-~]{1,200}$"},"description":"Save one stable value for these exact instructions. Retry ambiguous failures with the same body and key."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidQuoteRequest"}}}},"responses":{"200":{"description":"Idempotent replay","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidQuote"}}}},"201":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidQuote"}}}},"400":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Insufficient available credit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Expired/unavailable reservation, hold or idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/prepaid/quotes/{id}":{"get":{"operationId":"getPrepaidQuote","tags":["Prepaid"],"summary":"Read an owned quote and its reservation/funding state","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidQuote"}}}},"400":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Insufficient available credit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Expired/unavailable reservation, hold or idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"description":"Counts toward the shared 60 requests per API key per minute, including quote polling, balance reads, reserve/release and retries. This is not a separate prepaid allowance. Poll quotes every five seconds or slower and budget across all concurrent orders. On 429, honor Retry-After; retry mutations with the same body and idempotency key where required. Only authenticated cart-status GETs are exempt."}},"/v1/prepaid/quotes/{id}/reserve":{"post":{"operationId":"reservePrepaidCredit","tags":["Prepaid"],"summary":"Reserve printing credit before charging your customer for cards","description":"Reserves printing_cents only. No body. Idempotent by quote ID. One active reservation per cart. Does not extend expires_at. Returns 402 for insufficient funds; no printing occurs. Counts toward the shared 60 requests per API key per minute, including quote polling, balance reads, reserve/release and retries. This is not a separate prepaid allowance. Poll quotes every five seconds or slower and budget across all concurrent orders. On 429, honor Retry-After; retry mutations with the same body and idempotency key where required. Only authenticated cart-status GETs are exempt.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidQuote"}}}},"400":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Insufficient available credit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Expired/unavailable reservation, hold or idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/prepaid/quotes/{id}/release":{"post":{"operationId":"releasePrepaidCredit","tags":["Prepaid"],"summary":"Release an unused reservation","description":"No body. Idempotent by quote ID. Released or expired quotes cannot be reserved again. Cannot release funded orders; contact support about cancellation. Counts toward the shared 60 requests per API key per minute, including quote polling, balance reads, reserve/release and retries. This is not a separate prepaid allowance. Poll quotes every five seconds or slower and budget across all concurrent orders. On 429, honor Retry-After; retry mutations with the same body and idempotency key where required. Only authenticated cart-status GETs are exempt.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidQuote"}}}},"400":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Insufficient available credit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Expired/unavailable reservation, hold or idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/prepaid/orders":{"post":{"operationId":"submitPrepaidOrder","tags":["Prepaid"],"summary":"Spend printing credit and open customer shipping/tax checkout","description":"Confirm the card-sale payment in your own store first. Atomically spends printing_cents once and returns 202 with checkout_url, order_id:null and a quote status_url. Redirect the owning customer to Stripe to pay shipping_cents + tax_cents. Both payments must be verified before manufacturing and order.paid. A retry after funding returns 200 with the manufacturing ID. Persist acceptance/body/key. Cart must have more than 65 minutes remaining; customer checkout expires one hour after submission. Verified unpaid expiry automatically returns printing credit. Never infer payment from the browser return. Counts toward the shared 60 requests per API key per minute, including quote polling, balance reads, reserve/release and retries. This is not a separate prepaid allowance. Poll quotes every five seconds or slower and budget across all concurrent orders. On 429, honor Retry-After; retry mutations with the same body and idempotency key where required. Only authenticated cart-status GETs are exempt.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[!-~]{1,200}$"},"description":"Save one stable value for these exact instructions. Retry ambiguous failures with the same body and key."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidOrderRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidFundedOrder"}}}},"202":{"description":"Printing credit spent; customer shipping/tax payment required. Follow checkout_url and poll the quote through your server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidFundedOrder"}}}},"400":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Insufficient available credit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Expired/unavailable reservation, hold or idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Request failed; see error.code. Retry safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/estimate":{"get":{"operationId":"estimatePrinting","security":[],"summary":"Estimate standard-card printing before uploading","description":"Amounts are USD cents. Shipping, tax and discounts are excluded. Checkout recalculates the final price. A temporary estimate failure never blocks cart creation.","parameters":[{"name":"quantity","in":"query","required":true,"schema":{"type":"integer","minimum":1,"maximum":1000}}],"responses":{"200":{"description":"Printing estimate","content":{"application/json":{"schema":{"type":"object","properties":{"currency":{"type":"string","const":"USD"},"quantity":{"type":"integer"},"unit_amount":{"type":"integer"},"subtotal_amount":{"type":"integer"},"excludes":{"type":"array","items":{"type":"string"}}},"required":["currency","quantity","unit_amount","subtotal_amount","excludes"],"additionalProperties":false}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Live checkout or deployment configuration unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/widget/sessions":{"post":{"operationId":"startWidgetSession","tags":["Widget"],"security":[],"summary":"Start a standalone widget session using a public partner code","description":"The browser Origin must match the registered website or an additional widget origin. Requires active partnership and current terms. No API key. Configured quota: 60 new sessions per partner/hour (default 60). Token expires in one hour and authorizes only this session's uploads and one cart. Widget quotas are separate from the API-key rate limit.","parameters":[{"name":"Origin","in":"header","required":true,"schema":{"type":"string","format":"uri"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"partner_key":{"type":"string","pattern":"^partner_[a-f0-9]{24,32}$"},"mode":{"type":"string","enum":["test","live"]}},"required":["partner_key","mode"],"additionalProperties":false}}}},"responses":{"201":{"description":"Customer-scoped token; never log or share it.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"expires_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."},"mode":{"type":"string","enum":["test","live"]},"max_cards":{"type":"integer"},"max_image_bytes":{"type":"integer","const":20971520}},"required":["token","expires_at","mode","max_cards","max_image_bytes"],"additionalProperties":false}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"JSON body exceeds 512,000 bytes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer","const":60},"description":"Wait 60 seconds before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Live checkout or deployment configuration unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/widget/uploads":{"post":{"operationId":"createWidgetUpload","tags":["Widget"],"security":[{"WidgetSession":[]}],"summary":"Reserve a session-owned direct-storage upload","description":"Send Origin and the session bearer token. Reuse request_id for an identical retry. Returns a two-hour non-overwriting storage grant. PUT raw bytes with returned headers, without the widget token or cookies. Configured quotas: 2000 grants/session and 10000 grants/partner/rolling 24 hours (defaults 2,000 and 10,000). Each grant reserves a full 20 MiB. Front and back images each use a grant; reusing an uploaded image for copies does not create extra uploads. Operator environment overrides apply.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"},"content_type":{"type":"string","enum":["image/png","image/jpeg"]},"size":{"type":"integer","minimum":1,"maximum":20971520}},"required":["request_id","content_type","size"],"additionalProperties":false}}}},"responses":{"201":{"description":"Upload the bytes before creating the cart.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"upload_url":{"type":"string","format":"uri"},"method":{"type":"string","const":"PUT"},"headers":{"type":"object","properties":{"Content-Type":{"type":"string","enum":["image/png","image/jpeg"]}},"required":["Content-Type"],"additionalProperties":false},"max_bytes":{"type":"integer","const":20971520},"upload_expires_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."}},"required":["id","upload_url","method","headers","max_bytes","upload_expires_at"],"additionalProperties":false}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicting instructions or invalid state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"JSON body exceeds 512,000 bytes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer","const":60},"description":"Wait 60 seconds before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Live checkout or deployment configuration unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/widget/cart":{"post":{"operationId":"createWidgetCart","tags":["Widget"],"security":[{"WidgetSession":[]}],"summary":"Create this session's single cart","description":"Send Origin and session bearer token. Upload IDs must belong to this session. Identical retries reuse the cart; changed inputs need a new session. Optional return_url must use the registered website origin; omit for the registered website URL. No Idempotency-Key header, API key, certification or affiliate_code field. Attribution and terms are set by the backend.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","minItems":1,"maxItems":1000,"items":{"type":"object","properties":{"front_upload_id":{"type":"string","format":"uuid"},"back_upload_id":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1,"maximum":1000,"description":"Number of copies. The sum of all item quantities must be at most 1000."}},"required":["front_upload_id","quantity"],"additionalProperties":false}},"external_ref":{"type":"string","maxLength":200,"description":"Optional partner reference, at most 200 UTF-16 code units. No format restriction; not a uniqueness or idempotency key. Omitted/empty input is returned as null."},"return_url":{"type":"string","format":"uri","description":"HTTPS URL on the exact registered website origin."}},"required":["items"],"additionalProperties":false}}}},"responses":{"200":{"description":"Existing cart","headers":{"Retry-After":{"schema":{"type":"integer","const":3},"description":"Poll after at least 3 seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"},"examples":{"open":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"open","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":2,"total":2},"errors":[],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z","checkout_url":"https://partner.tcgplaytest.com/partner-checkout/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}},"failed":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"failed","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":2,"total":2},"errors":[{"item_index":0,"side":"front","code":"image_invalid","message":"Image is not a readable PNG or JPEG."},{"item_index":0,"side":"back","code":"image_unreachable","message":"Image could not be downloaded."}],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z"}}}}}},"202":{"description":"Queued for validation","headers":{"Retry-After":{"schema":{"type":"integer","const":3},"description":"Poll after at least 3 seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"},"examples":{"validating":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"validating","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":0,"total":2},"errors":[],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicting instructions or invalid state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"JSON body exceeds 512,000 bytes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer","const":60},"description":"Wait 60 seconds before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Live checkout or deployment configuration unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"operationId":"getWidgetCart","tags":["Widget"],"security":[{"WidgetSession":[]}],"summary":"Poll only the session's cart","description":"Send Origin and session bearer token. No arbitrary cart ID parameter. Poll every 3–5 seconds; reads are exempt from upload/session quotas. Follow checkout_url only when open.","responses":{"200":{"description":"Current cart","headers":{"Retry-After":{"schema":{"type":"integer","const":3},"description":"Poll after at least 3 seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"},"examples":{"validating":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"validating","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":0,"total":2},"errors":[],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z"}},"open":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"open","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":2,"total":2},"errors":[],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z","checkout_url":"https://partner.tcgplaytest.com/partner-checkout/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}},"failed":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"failed","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":2,"total":2},"errors":[{"item_index":0,"side":"front","code":"image_invalid","message":"Image is not a readable PNG or JPEG."},{"item_index":0,"side":"back","code":"image_unreachable","message":"Image could not be downloaded."}],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z"}}}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found for this partner and key mode","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/uploads":{"post":{"operationId":"createUpload","tags":["Uploads"],"summary":"Get a temporary direct-to-TCGPlaytest-storage upload link","description":"Send metadata only with your server-side API key. No partner storage account is needed. Counts toward the shared 60/minute/key limit. PUT raw file bytes to upload_url with the returned headers, without the API key or cookies. Wait for a successful 2xx PUT, then use image_url unchanged in a cart front/back field. No finalize call. Links cannot overwrite existing files. Test and live keys are supported; hosted/Connect test checkout supports simulated completion and order events. Prepaid sandbox supports Stripe test payments only. Upload success is not artwork validation: the cart worker performs all checks. On an uncertain PUT outcome, request a new upload link and retry the file.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUpload"},"example":{"content_type":"image/png","size":123456}}}},"responses":{"201":{"description":"Scoped upload grant; do not log or share the signed URLs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadGrant"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"JSON body exceeds 512,000 bytes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer","const":60},"description":"Wait 60 seconds before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Live checkout or deployment configuration unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/uploads/source":{"get":{"operationId":"readUploadedImage","tags":["Uploads"],"summary":"Resolve an API-issued image reference","security":[],"description":"Use the returned image_url verbatim in a cart. This capability URL redirects to a short-lived private-storage read link. No bearer API key is sent. Access expires after 8 days and requires the issuing key and partner to remain active. It is not a checkout URL. Partners normally do not need to call this endpoint themselves.","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","maxLength":1800},"description":"Opaque signed capability already included in image_url."}],"responses":{"307":{"description":"Follow the HTTPS redirect to read the image.","headers":{"Location":{"schema":{"type":"string","format":"uri"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found for this partner and key mode","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"410":{"description":"upload_expired: request a fresh upload grant and upload again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/carts":{"post":{"operationId":"createCart","tags":["Carts"],"summary":"Queue URL validation and create a cart","description":"Returns 202 immediately while a separate worker checks artwork and prepares default 2 mm bleed on every front/back; recognized existing 2 mm bleed is preserved and 3 mm is cropped to 2 mm. Source hashes always refer to submitted bytes, and production uses the prepared image. The source and prepared file must each meet the size/pixel limits; no checkout_url until open. Request JSON is limited to 512,000 bytes. Maximum 1000 items AND 1000 total copies. Downloads allow 20 MiB/file, 40 MP, recommended 744×1040; smaller readable images remain orderable with image_low_resolution warnings, 3 redirects and a 15-second total deadline per URL attempt. Identical retries with the same key return the original cart (202 validating; 200 otherwise). A failed cart requires corrected inputs and a new Idempotency-Key, not a new API key. Default deployment cart lifetime: 168 hours from creation.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"1–200 visible ASCII characters (0x21–0x7E); no spaces. Use a unique value per order revision. Scoped to partner + test/live mode; reuse only for identical retries.","schema":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[!-~]{1,200}$"},"example":"store-order-1042-rev1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCart"},"example":{"external_ref":"store-order-1042","return_url":"https://shop.example/order-complete","items":[{"image_url":"https://images.example/front.png","back_image_url":"https://images.example/back.jpg","quantity":3}]}}}},"responses":{"200":{"description":"An idempotent replay of an existing cart which is no longer validating","headers":{"Retry-After":{"schema":{"type":"integer","const":3},"description":"Poll after at least 3 seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"},"examples":{"open":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"open","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":2,"total":2},"errors":[],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z","checkout_url":"https://partner.tcgplaytest.com/partner-checkout/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}},"failed":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"failed","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":2,"total":2},"errors":[{"item_index":0,"side":"front","code":"image_invalid","message":"Image is not a readable PNG or JPEG."},{"item_index":0,"side":"back","code":"image_unreachable","message":"Image could not be downloaded."}],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z"}}}}}},"202":{"description":"Artwork queued or still validating","headers":{"Retry-After":{"schema":{"type":"integer","const":3},"description":"Poll after at least 3 seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"},"examples":{"validating":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"validating","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":0,"total":2},"errors":[],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicting instructions or invalid state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"JSON body exceeds 512,000 bytes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer","const":60},"description":"Wait 60 seconds before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Live checkout or deployment configuration unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/carts/{id}":{"get":{"operationId":"getCart","tags":["Carts"],"summary":"Poll cart validation and checkout availability","description":"Poll every 3–5 seconds; authenticated cart GETs are exempt from the 60 requests/key/minute limit. A failed cart still returns HTTP 200 with status failed and item errors. Checkout URLs are present only for open carts. Keep polling validating carts even if some errors are already visible; the complete list is available when failed.","parameters":[{"name":"id","in":"path","required":true,"description":"The exact cart.id returned by POST /v1/carts. Treat it as opaque.","schema":{"type":"string"}}],"responses":{"200":{"description":"Current cart, including all validation progress/errors","headers":{"Retry-After":{"schema":{"type":"integer","const":3},"description":"Poll after at least 3 seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"},"examples":{"validating":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"validating","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":0,"total":2},"errors":[],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z"}},"open":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"open","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":2,"total":2},"errors":[],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z","checkout_url":"https://partner.tcgplaytest.com/partner-checkout/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}},"failed":{"value":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"failed","external_ref":"store-order-1042","card_count":3,"card_stock":"standard","affiliate_code":null,"progress":{"done":2,"total":2},"errors":[{"item_index":0,"side":"front","code":"image_invalid","message":"Image is not a readable PNG or JPEG."},{"item_index":0,"side":"back","code":"image_unreachable","message":"Image could not be downloaded."}],"warnings":[],"partner_terms_version":"2026-10-04-partner-v2","mode":"test","order_id":null,"expires_at":"2026-10-13T12:00:00.000Z","created_at":"2026-10-06T12:00:00.000Z"}}}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found for this partner and key mode","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/orders/{id}":{"get":{"operationId":"getOrder","tags":["Orders"],"summary":"Read manufacturing and shipment status","description":"Use cart.order_id or an order webhook's data.id, never the cart ID or Stripe session ID. Manufacturing orders exist only after live payment has been recorded. Hosted/Connect simulator orders (test_order_ IDs) and paid prepaid sandbox orders are simulated, returned with mode:test and simulated:true, and never print or ship. Scoped to partner + key mode; test keys cannot retrieve live orders. Counts toward 60 requests/key/minute. Browser return alone does not confirm payment.","parameters":[{"name":"id","in":"path","required":true,"description":"The manufacturing order ID from cart.order_id or an order webhook data.id. Treat it as opaque.","schema":{"type":"string"}}],"responses":{"200":{"description":"Order status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"},"example":{"id":"00000000-0000-4000-a000-000000000042","cart_id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","status":"shipped","shipment":{"carrier":"Example carrier","tracking_number":"TRACK-1042","tracking_url":"https://shipping.example/track/TRACK-1042"},"created_at":"2026-10-06T12:10:00.000Z","updated_at":"2026-10-08T15:00:00.000Z","external_ref":"store-order-1042","card_count":3}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found for this partner and key mode","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer","const":60},"description":"Wait 60 seconds before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/test/carts/{id}/complete":{"post":{"operationId":"completeSimulatedCheckout","tags":["Sandbox"],"summary":"Complete a test hosted or Connect cart without payment","description":"Requires a test API key, ownership, an open unexpired cart and stored, unblocked artwork. Never invokes Stripe, credits or manufacturing. Atomically converts the cart and queues order.paid to the Test webhook. Idempotent by cart; no Idempotency-Key required. Repeats return current order state. Counts toward 60 requests/key/minute.","parameters":[{"name":"id","in":"path","required":true,"description":"An open test cart ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"confirm_simulation":{"const":true}},"required":["confirm_simulation"],"additionalProperties":false}}}},"responses":{"200":{"description":"Simulated order; mode:test and simulated:true","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found for this partner and key mode","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicting instructions or invalid state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"410":{"description":"Test cart expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer","const":60},"description":"Wait 60 seconds before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Live checkout or deployment configuration unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/test/orders/{id}/events":{"post":{"operationId":"advanceSimulatedOrder","tags":["Sandbox"],"summary":"Simulate production, shipment or cancellation","description":"Requires the owning partner's test key and a test_order_ ID. paid → in_production → shipped; cancellation allowed from paid/in_production. Repeating an emitted status returns current state without another event. Invalid transitions return 409. Each transition queues a signed Test webhook with data.mode:test and data.simulated:true. Shipment contains dummy tracking, never real fulfillment. No Idempotency-Key required. Counts toward 60 requests/key/minute.","parameters":[{"name":"id","in":"path","required":true,"description":"Simulated order ID returned by completion, cart.order_id or the Test webhook.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"enum":["in_production","shipped","cancelled"]}},"required":["status"],"additionalProperties":false}}}},"responses":{"200":{"description":"Current simulated order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Invalid, revoked or unavailable API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Terms acceptance required or artwork blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found for this partner and key mode","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicting instructions or invalid state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer","const":60},"description":"Wait 60 seconds before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Live checkout or deployment configuration unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"webhooks":{"partnerEvent":{"post":{"operationId":"receivePartnerEvent","summary":"Receive a signed order or cart-expiry event","security":[],"description":"Configure separately for test/live in Dashboard → 2. Order webhooks. Use an HTTPS URL on the exact registered website origin. Save webhook displays a whsec_ secret once and rotates any previous secret for that mode. Verify HMAC-SHA256(timestamp + '.' + exact raw body), use constant-time comparison and reject timestamps outside ±300 seconds. Deduplicate by top-level id. Return 2xx after durable receipt; failures retry up to 12 attempts. Delivery is asynchronous and ordering is not guaranteed. Test hosted/Connect carts emit simulated order.paid after completion; /v1/test/orders/{id}/events advances production, shipment or cancellation. These events go only to the Test webhook with data.mode:test and data.simulated:true. Prepaid sandbox retains its separate paid/cancelled flow.","parameters":[{"name":"TCGP-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"t=<Unix seconds>,v1=<64 lowercase hexadecimal HMAC-SHA256 characters>. This signature authenticates the webhook; a partner API bearer key is not sent."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OrderEvent"},{"$ref":"#/components/schemas/CartExpiredEvent"}]},"examples":{"orderShipped":{"value":{"id":"evt_cccccccccccccccccccccccccccccccc","type":"order.shipped","created_at":"2026-10-08T15:00:00.000Z","data":{"id":"00000000-0000-4000-a000-000000000042","cart_id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","external_ref":"store-order-1042","status":"shipped","shipment":{"carrier":"Example carrier","tracking_number":"TRACK-1042","tracking_url":"https://shipping.example/track/TRACK-1042"}}}},"cartExpired":{"value":{"id":"evt_dddddddddddddddddddddddddddddddd","type":"cart.expired","created_at":"2026-10-13T12:00:01.000Z","data":{"id":"cart_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","external_ref":"store-order-1042","status":"expired"}}}}}}},"responses":{"200":{"description":"Any 2xx acknowledges receipt. Respond within 15 seconds; redirects are not followed."}}}}},"components":{"securitySchemes":{"WidgetSession":{"type":"http","scheme":"bearer","bearerFormat":"wses_<64 lowercase hex>","description":"Opaque one-hour customer session from POST /api/widget/sessions, not a partner API key. Origin must match the issuing website."},"PartnerApiKey":{"type":"http","scheme":"bearer","bearerFormat":"tcgp_test_<64 lowercase hex> or tcgp_live_<64 lowercase hex>","description":"Server-side key created in the dashboard. Test/live data are isolated. Rotating revokes the previous key for that mode."}},"schemas":{"PrepaidShipping":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","maxLength":200},"email":{"type":"string","format":"email","maxLength":254},"line1":{"type":"string","maxLength":200},"line2":{"type":"string","maxLength":200},"city":{"type":"string","maxLength":200},"state":{"type":"string","maxLength":200,"description":"Required for US, CA and AU."},"postal_code":{"type":"string","maxLength":200},"country":{"type":"string","pattern":"^[A-Z]{2}$"},"phone":{"type":"string","maxLength":200}},"required":["name","email","line1","city","postal_code","country"]},"PrepaidQuoteRequest":{"type":"object","additionalProperties":false,"properties":{"cart_id":{"type":"string"},"shipping":{"$ref":"#/components/schemas/PrepaidShipping"}},"required":["cart_id","shipping"]},"PrepaidQuote":{"type":"object","additionalProperties":false,"properties":{"mode":{"const":"test","description":"Present when Stripe test credentials were used."},"simulated":{"const":true},"id":{"type":"string","format":"uuid"},"cart_id":{"type":"string"},"currency":{"const":"USD"},"printing_cents":{"type":"integer","minimum":0},"shipping_cents":{"type":"integer","minimum":0},"tax_cents":{"type":"integer","minimum":0},"total_cents":{"type":"integer","minimum":0},"funding_model":{"enum":["cards_credit","credit_all"],"description":"New quotes use cards_credit. Historical funded orders retain credit_all."},"credit_cents":{"type":"integer","minimum":0,"description":"Printing cost only for new quotes."},"customer_payment_cents":{"type":"integer","minimum":0,"description":"Shipping plus fulfillment tax, paid separately by the customer."},"customer_payment_status":{"enum":["not_required","pending","open","paid","expired","review","refund_pending","refunded"]},"checkout_expires_at":{"type":["string","null"],"format":"date-time"},"checkout_url":{"type":"string","format":"uri","description":"Only while a submitted customer checkout is open. Redirect the owning customer to this URL unchanged."},"status":{"enum":["quoted","reserved","released","consumed","expired"],"description":"Consumed means printing credit was spent; check customer_payment_status and order_id before claiming a manufacturing order."},"expires_at":{"type":"string","format":"date-time"},"order_id":{"type":["string","null"],"description":"Null until both payments have funded manufacturing."},"refunded_at":{"type":["string","null"],"format":"date-time"}},"required":["id","cart_id","currency","printing_cents","shipping_cents","tax_cents","total_cents","funding_model","credit_cents","customer_payment_cents","customer_payment_status","checkout_expires_at","status","expires_at","order_id","refunded_at"]},"PrepaidAcceptance":{"type":"object","additionalProperties":false,"properties":{"accepted":{"const":true},"terms_version":{"const":"2026-10-04-v2"},"email":{"type":"string","format":"email"},"accepted_at":{"type":"string","format":"date-time","description":"Actual customer acceptance time, on/after cart creation. Persist this value; never regenerate it for retries."},"image_quality_accepted":{"type":"boolean","description":"Must be true when the cart has artwork warnings; show them to the customer first."}},"required":["accepted","terms_version","email","accepted_at"]},"PrepaidOrderRequest":{"type":"object","additionalProperties":false,"properties":{"quote_id":{"type":"string","format":"uuid"},"acceptance":{"$ref":"#/components/schemas/PrepaidAcceptance"}},"required":["quote_id","acceptance"]},"PrepaidFundedOrder":{"type":"object","additionalProperties":false,"properties":{"order_id":{"type":["string","null"]},"cart_id":{"type":"string"},"funding":{"enum":["printing_credit_customer_shipping_tax","prepaid_credit"]},"quote":{"$ref":"#/components/schemas/PrepaidQuote"},"status_url":{"type":"string"},"checkout_url":{"type":"string","format":"uri"}},"required":["order_id","cart_id","funding","quote","status_url"]},"PrepaidBalance":{"type":"object","additionalProperties":false,"properties":{"mode":{"const":"test","description":"Present for the isolated test wallet."},"simulated":{"const":true},"currency":{"const":"USD"},"balance_cents":{"type":"integer"},"reserved_cents":{"type":"integer","minimum":0},"available_cents":{"type":"integer"},"spending_blocked":{"type":"boolean"},"ledger":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"integer"},"amount_cents":{"type":"integer"},"balance_after_cents":{"type":"integer"},"kind":{"enum":["topup","topup_adjustment","order","refund"]},"reference":{"type":"string"},"description":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"required":["id","amount_cents","balance_after_cents","kind","reference","description","created_at"]}},"next_before":{"type":["string","null"]}},"required":["currency","balance_cents","reserved_cents","available_cents","spending_blocked","ledger","next_before"]},"CreateUpload":{"type":"object","properties":{"content_type":{"type":"string","enum":["image/png","image/jpeg"]},"size":{"type":"integer","minimum":1,"maximum":20971520,"description":"Exact file byte count; storage and the validation worker enforce the 20 MiB maximum independently."}},"required":["content_type","size"],"additionalProperties":false},"UploadGrant":{"type":"object","properties":{"upload_url":{"type":"string","format":"uri","description":"Temporary storage URL. PUT the raw file here, never multipart or JSON. Valid for 2 hours; upload once, no overwrite."},"method":{"type":"string","const":"PUT"},"headers":{"type":"object","properties":{"Content-Type":{"type":"string","enum":["image/png","image/jpeg"]}},"required":["Content-Type"],"additionalProperties":false},"image_url":{"type":"string","format":"uri","description":"Opaque image reference to use unchanged in image_url/back_image_url after PUT succeeds. Bound to partner and key mode."},"upload_expires_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."},"expires_at":{"type":"string","format":"date-time","description":"Source-reference expiry, 8 days after grant creation. Create the cart promptly. Staging objects are removed by maintenance after expiry (normally within the following day); validated cart files follow normal cart/order retention."},"max_bytes":{"type":"integer","const":20971520}},"required":["upload_url","method","headers","image_url","upload_expires_at","expires_at","max_bytes"],"additionalProperties":false},"CreateCart":{"type":"object","properties":{"retail_amount_cents":{"type":"integer","minimum":50,"maximum":5000000,"description":"Required only for Connect: whole-cart card-sale subtotal in USD cents, excluding shipping/tax. Must cover printing cost. Immutable; server-authoritative partner price."},"settlement":{"type":"string","enum":["hosted","prepaid","connect"],"default":"hosted","description":"Omit for hosted checkout: server-API carts automatically spend eligible printing credit when sufficient, otherwise charge the customer normally. No extra partner integration is needed. Choose connect with retail_amount_cents for onboarded partners receiving earnings after printing and Stripe fees, with a 5% reserve for 15 days and no printing-credit debit. Choose prepaid only for the advanced quote/reserve/submit integration. Existing live partner keys work with either Stripe environment. With a test API key, hosted/Connect use a payment-free order simulator with no onboarding or credit spending. Live Connect still requires onboarding. Immutable per cart."},"items":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/CartItem"}},"back_image_url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^[Hh][Tt][Tt][Pp][Ss]://","description":"Use the image_url returned by POST /v1/uploads after its PUT succeeds, or your existing public IPv4 HTTPS image URL. At most 2048 UTF-16 code units; no credentials, fragment or port other than 443. Each redirect is rechecked. Must serve PNG/JPEG bytes without an Authorization header. Shared back used when an item does not supply its own back_image_url. If neither is supplied, TCGPlaytest provides its default card back."},"back_sha256":{"type":["string","null"],"pattern":"^[a-fA-F0-9]{64}$","description":"Optional SHA-256 of the exact downloaded bytes, not a perceptual hash. A mismatch fails this item/side. Omit or use null to let TCGPlaytest compute it."},"card_stock":{"type":"string","enum":["standard"],"default":"standard"},"external_ref":{"type":"string","maxLength":200,"description":"Optional partner reference, at most 200 UTF-16 code units. No format restriction; not a uniqueness or idempotency key. Omitted/empty input is returned as null."},"return_url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^[Hh][Tt][Tt][Pp][Ss]://","description":"HTTPS return URL on the exact registered website_url origin (scheme/host/port). Omit to use the registered website URL. widget_allowed_origins does not authorize return domains. STORE_ORIGIN is only an example application environment variable; database changes do not update it. Return navigation is not payment confirmation; verify the order API or signed webhook."}},"required":["items"],"additionalProperties":false,"allOf":[{"if":{"properties":{"settlement":{"const":"connect"}},"required":["settlement"]},"then":{"required":["retail_amount_cents"]},"else":{"not":{"required":["retail_amount_cents"]}}}],"description":"Only these fields are accepted. Do not send certification, affiliate_code, partner-terms fields or library IDs. retail_amount_cents is allowed only for Connect. Each item needs a back, supplied per item or shared at top level. Partner terms/affiliate settings are copied server-side."},"CartItem":{"type":"object","properties":{"image_url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^[Hh][Tt][Tt][Pp][Ss]://","description":"Use the image_url returned by POST /v1/uploads after its PUT succeeds, or your existing public IPv4 HTTPS image URL. At most 2048 UTF-16 code units; no credentials, fragment or port other than 443. Each redirect is rechecked. Must serve PNG/JPEG bytes without an Authorization header."},"sha256":{"type":["string","null"],"pattern":"^[a-fA-F0-9]{64}$","description":"Optional SHA-256 of the exact downloaded bytes, not a perceptual hash. A mismatch fails this item/side. Omit or use null to let TCGPlaytest compute it."},"quantity":{"type":"integer","minimum":1,"maximum":1000,"description":"Number of copies. The sum of all item quantities must be at most 1000."},"back_image_url":{"type":"string","format":"uri","maxLength":2048,"pattern":"^[Hh][Tt][Tt][Pp][Ss]://","description":"Item-specific back; overrides the shared back. If supplied, back_sha256 also comes from this item, never the shared hash."},"back_sha256":{"type":["string","null"],"pattern":"^[a-fA-F0-9]{64}$","description":"Optional SHA-256 of the exact downloaded bytes, not a perceptual hash. A mismatch fails this item/side. Omit or use null to let TCGPlaytest compute it."}},"required":["image_url","quantity"],"additionalProperties":false},"QualityWarning":{"type":"object","properties":{"item_index":{"type":"integer","minimum":0},"side":{"type":"string","enum":["front","back"]},"code":{"type":"string","const":"image_low_resolution"},"message":{"type":"string"},"width":{"type":"integer"},"height":{"type":"integer"},"recommended_width":{"type":"integer"},"recommended_height":{"type":"integer"}},"required":["item_index","side","code","message","width","height","recommended_width","recommended_height"],"additionalProperties":false},"ValidationError":{"type":"object","properties":{"item_index":{"type":"integer","minimum":0,"description":"Zero-based index in the submitted items array."},"side":{"type":"string","enum":["front","back"]},"code":{"type":"string","enum":["image_unreachable","image_url_unsafe","image_redirect_limit","image_timeout","image_too_large","image_invalid","image_pixels_exceeded","image_hash_mismatch","content_blocked","validation_unavailable"]},"message":{"type":"string"}},"required":["item_index","side","code","message"],"additionalProperties":false},"Cart":{"type":"object","properties":{"retail_amount_cents":{"type":"integer","minimum":50,"maximum":5000000,"description":"Present only for Connect carts."},"settlement":{"type":"string","enum":["prepaid","connect"],"description":"Connect never spends credit and exposes checkout_url when open. Prepaid never exposes checkout_url. Omitted means hosted checkout."},"id":{"type":"string","description":"Opaque cart identifier."},"status":{"type":"string","enum":["validating","open","failed","converted","expired","blocked","cancelled"]},"external_ref":{"type":["string","null"]},"card_count":{"type":"integer","minimum":1,"description":"Sum of item quantities, not the number of URLs."},"card_stock":{"type":"string","enum":["standard"]},"affiliate_code":{"type":["string","null"],"description":"Saved account preference copied at cart creation; null when none. Read-only."},"progress":{"type":"object","properties":{"done":{"type":"integer","minimum":0,"description":"Finished unique-URL jobs, including failures."},"total":{"type":"integer","minimum":0,"description":"Distinct front/back URLs in this cart, not card copies."}},"required":["done","total"],"additionalProperties":false},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ValidationError"}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/QualityWarning"},"description":"Advisory only. Low-resolution artwork remains orderable after a customer warning."},"partner_terms_version":{"type":["string","null"],"description":"Accepted partner version recorded server-side. Can be null on older carts."},"mode":{"type":"string","enum":["test","live"]},"checkout_url":{"type":"string","format":"uri","description":"Present only for open hosted-checkout carts; always omitted for prepaid carts. Pass the open cart to TCGPlaytest.checkout(cart) for embedded checkout, or navigate to this URL verbatim for a full-page handoff. Do not substitute the API origin. Test mode opens the full-page checkout simulator."},"order_id":{"type":["string","null"],"description":"Manufacturing order identifier after live payment, or test_order_ identifier after simulated checkout; otherwise null."},"expires_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."},"created_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."}},"required":["id","status","external_ref","card_count","card_stock","affiliate_code","progress","errors","warnings","partner_terms_version","mode","order_id","expires_at","created_at"],"additionalProperties":false,"allOf":[{"if":{"properties":{"status":{"const":"open"}},"required":["status"],"not":{"properties":{"settlement":{"const":"prepaid"}},"required":["settlement"]}},"then":{"required":["checkout_url"]},"else":{"not":{"required":["checkout_url"]}}}]},"Shipment":{"type":"object","properties":{"carrier":{"type":"string","maxLength":80},"tracking_number":{"type":"string","maxLength":150},"tracking_url":{"type":"string","format":"uri","description":"HTTPS tracking link."}},"required":["carrier","tracking_number","tracking_url"],"additionalProperties":false},"Order":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SandboxItem"},"description":"Present for hosted/Connect simulator orders; frozen checkout quantities."},"settlement":{"enum":["hosted","connect"]},"retail_amount_cents":{"type":"integer","description":"Supplied Connect card subtotal only; not a charged or validated total."},"mode":{"const":"test"},"simulated":{"const":true},"id":{"type":"string"},"cart_id":{"type":"string"},"status":{"type":"string","enum":["paid","in_production","shipped","cancelled","blocked"]},"shipment":{"anyOf":[{"$ref":"#/components/schemas/Shipment"},{"type":"null"}],"description":"Null when no shipment is attached."},"created_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."},"updated_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."},"external_ref":{"type":["string","null"]},"card_count":{"type":"integer","minimum":1}},"required":["id","cart_id","status","shipment","created_at","updated_at","external_ref","card_count"],"additionalProperties":false},"SandboxItem":{"type":"object","properties":{"item_index":{"type":"integer","minimum":0},"quantity":{"type":"integer","minimum":1}},"required":["item_index","quantity"],"additionalProperties":false},"OrderEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique event ID used for deduplication. Re-delivery keeps this ID."},"type":{"type":"string","enum":["order.paid","order.in_production","order.shipped","order.cancelled"]},"created_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."},"data":{"type":"object","properties":{"mode":{"const":"test"},"items":{"type":"array","items":{"$ref":"#/components/schemas/SandboxItem"}},"card_count":{"type":"integer","minimum":1},"simulated":{"const":true,"description":"Present on sandbox events, sent only to the Test webhook."},"id":{"type":"string","description":"Manufacturing order ID, or simulated order ID for sandbox events."},"cart_id":{"type":"string"},"external_ref":{"type":["string","null"]},"status":{"type":"string","enum":["paid","in_production","shipped","cancelled"]},"shipment":{"anyOf":[{"$ref":"#/components/schemas/Shipment"},{"type":"null"}]}},"required":["id","cart_id","external_ref","status","shipment"],"additionalProperties":false}},"required":["id","type","created_at","data"],"additionalProperties":false},"CartExpiredEvent":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","const":"cart.expired"},"created_at":{"type":"string","format":"date-time","description":"RFC 3339 timestamp with timezone; UTC uses Z or +00:00. Fractional seconds may be present."},"data":{"type":"object","properties":{"id":{"type":"string","description":"Cart ID, not an order ID."},"external_ref":{"type":["string","null"]},"status":{"type":"string","const":"expired"}},"required":["id","external_ref","status"],"additionalProperties":false}},"required":["id","type","created_at","data"],"additionalProperties":false},"ApiError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"item_index":{"type":"integer","minimum":0,"description":"Present when a request error refers to a particular item."}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}}