{"openapi":"3.0.0","info":{"title":"Printags - API Documentation, version 2","description":"This is the official API documentation for Printags: PVC, NFC and wood cards, printed and encoded. If you have any questions, please contact us at contact@printags.com. Authentication: send your API key as a Bearer token, `Authorization: Bearer <key>`. Keys are created in the dashboard and act on the account that holds them. Live keys start with `sk_live_`: their orders are real, debited and manufactured. Test keys start with `sk_test_`: their orders are test orders, never debited and never manufactured, and a test key only sees test orders (a live key only sees real ones); use `simulateOrderStatus` to move a test order through its life cycle and receive the same webhooks as in production. Four routes need no key: `listOrderStatuses`, `listShippingCarriers`, `calculateShippingRates` and `getFile`. Errors: a failed call answers with a non-2xx status and the JSON body described by the `Error` schema (`success: false`, a machine-readable `code`, a `message`). The operations of this document do not paginate. Rate limit: 1200 calls per minute per API key, and 6000 per minute per IP address for the calls without a key; every response of a limited operation carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (the `X-RateLimit-*` forms too), and a 429 `RATE_LIMITED` carries `Retry-After`; the file download (`getFile`) is not limited. Version 2 serves the routes of version 1 under /v2, with the names of the platform: an order's items (version 1 product_groups) and units (version 1 products, the cards), the catalogue's products (version 1 models, productId for modelId) and shipments (version 1 shippings). Version 1 is deprecated since 25 October 2026 and will be removed on 4 January 2027.","version":"2.0.0"},"servers":[{"url":"https://api.printags.com","description":"Main (production) server"},{"url":"https://sandbox-api.printags.com","description":"Sandbox: accepts only test API keys (`sk_test_...`); any other credential (a live key, a dashboard session, an OAuth token) is refused with a 403 `SANDBOX_REQUIRES_TEST_KEY`. The orders you create are test orders, neither debited nor manufactured. A test key also works on the production server, where it creates test orders as well."}],"tags":[{"name":"Accounts","description":"Endpoints related to account management"},{"name":"Orders","description":"Endpoints related to order management"},{"name":"Files","description":"Endpoints related to file management"},{"name":"Statuses","description":"Endpoints related to status management"},{"name":"Shipping","description":"Endpoints related to shipping rates and carrier availability"},{"name":"Products","description":"Endpoints related to the catalogue of products"}],"security":[{"ApiKeyAuth":[]}],"components":{"headers":{"RateLimit-Limit":{"description":"Calls allowed in the 60-second window: 1200 per API key (per Bearer token), or 6000 per IP address for a call without a key (`listOrderStatuses`, `listShippingCarriers`, `calculateShippingRates`).","schema":{"type":"integer","example":1200}},"RateLimit-Remaining":{"description":"Calls left in the current window","schema":{"type":"integer","example":1199}},"RateLimit-Reset":{"description":"Seconds left before the window resets","schema":{"type":"integer","example":42}},"X-RateLimit-Limit":{"description":"Same value as `RateLimit-Limit`","schema":{"type":"integer","example":1200}},"X-RateLimit-Remaining":{"description":"Same value as `RateLimit-Remaining`","schema":{"type":"integer","example":1199}},"X-RateLimit-Reset":{"description":"Unix time, in seconds, at which the window resets (not a number of seconds left: use `RateLimit-Reset` for that)","schema":{"type":"integer","example":1793700000}},"Retry-After":{"description":"Seconds to wait before calling again","schema":{"type":"integer","example":12}}},"responses":{"TooManyRequests":{"description":"Rate limit exceeded (`code` `RATE_LIMITED`): wait for `Retry-After` seconds. Calls are limited to 1200 per minute per API key, and 6000 per minute per IP address for the calls without a key.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"success":false,"status":429,"code":"RATE_LIMITED","message":"Too many requests. Wait for the number of seconds in the Retry-After header before calling again."}}}}},"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"An API key (`sk_live_...` for real orders, `sk_test_...` for test orders), or the JWT of a dashboard session, in the Authorization header: `Authorization: Bearer <key>`."}},"schemas":{"Error":{"type":"object","description":"Body of a failed call. Most errors are flat (`success`, `status`, `code`, `message`); the ones raised by the request validator (an invalid body or parameter) and the unexpected ones nest `code` and `message` in `error`, and the validator lists its findings in `errors`. Read `code` rather than `message`: messages may be French or English and can change.","properties":{"success":{"type":"boolean","example":false},"status":{"type":"integer","description":"HTTP status, repeated in the body (flat errors only)","example":404},"code":{"type":"string","description":"Machine-readable code, e.g. `INVALID_API_KEY`, `INACTIVE_API_KEY`, `ACCOUNT_ACCESS_DENIED`, `SUBSCRIPTION_REQUIRED`, `ORDER_NOT_FOUND`, `FILE_NOT_FOUND`, `MISSING_TO_COUNTRY`, `TEST_ORDER_NOT_READY`, `RATE_LIMITED`, `SANDBOX_REQUIRES_TEST_KEY` (flat errors only)","example":"ORDER_NOT_FOUND"},"message":{"type":"string","example":"Order 987e6543-e21a-11d4-a789-0242ac120002 not found"},"inputs":{"type":"object","description":"The values the error is about, when the endpoint gives them","additionalProperties":true},"error":{"type":"object","description":"Nested form, used by validation errors and unexpected errors","properties":{"code":{"type":"string","example":"INTERNAL_ERROR"},"message":{"type":"string"}}},"errors":{"type":"array","description":"Validation findings (request validator): one entry per invalid field","items":{"type":"object","properties":{"path":{"type":"string","example":".body.shipTo"},"message":{"type":"string"},"errorCode":{"type":"string"}}}}},"required":["success"]},"Account":{"type":"object","properties":{"id":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"displayname":{"type":"string","example":"John Doe"},"email":{"type":"string","format":"email","example":"johndoe@example.com"},"phone":{"type":"string","example":"+1234567890"},"balance":{"type":"number","format":"float","example":100.5},"minimal_balance":{"type":"number","example":0},"minimal_credit":{"type":"number","example":0},"fiscal_number":{"type":"string","example":"FR12345678901"},"subscription_plan":{"$ref":"#/components/schemas/SubscriptionPlan"},"options":{"type":"object","properties":{"lang":{"type":"string","example":"EN"},"payments":{"type":"object","properties":{"groupWithSubscription":{"type":"boolean","example":false}}}}},"createdAt":{"type":"string","format":"date-time","example":"2024-01-01T10:00:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2024-11-01T12:00:00.000Z"}}},"Address":{"type":"object","description":"Address can be either a standard address or a service point (relay point). Use 'address' for home delivery or 'servicePoint' for relay point delivery.","properties":{"id":{"type":"string","nullable":true,"example":"123e4567-e89b-12d3-a456-426614174000"},"name":{"type":"string","example":"Jean Dupont","maxLength":38,"pattern":"^[a-zA-Z0-9à-öø-ÿÀ-ÖØ-ß-._ ']+$"},"phone":{"type":"string","example":"+33612345678"},"email":{"type":"string","format":"email","example":"jean.dupont@example.fr"},"address":{"type":"object","description":"Standard address for home delivery. Required if servicePoint is not provided.","properties":{"addressLine1":{"type":"string","example":"10 rue de la République","maxLength":38,"pattern":"^[a-zA-Z0-9à-öø-ÿÀ-ÖØ-ß-._ ',]+$"},"addressLine2":{"type":"string","nullable":true,"example":"Appartement 123","maxLength":38,"pattern":"^[a-zA-Z0-9à-öø-ÿÀ-ÖØ-ß-._ ',]+$"},"city":{"type":"string","example":"Paris","maxLength":35,"pattern":"^[a-zA-Z0-9à-öø-ÿÀ-ÖØ-ß-._ ']+$"},"postcode":{"type":"string","example":"75001"},"countryCode":{"type":"string","example":"FR","minLength":2,"maxLength":2}},"required":["addressLine1","city","postcode","countryCode"]},"servicePoint":{"type":"object","description":"Service point (relay point) for pickup delivery. Required if address is not provided. Use with a carrierServiceId that supports service points.","properties":{"id":{"type":"string","description":"The service point ID from the carrier (e.g., Sendcloud service point ID)","example":"12624766"},"country":{"type":"string","description":"ISO 2-letter country code of the service point","example":"LU","minLength":2,"maxLength":2}},"required":["id","country"]}},"required":["name","phone","email"]},"Customer":{"type":"object","properties":{"id":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"email":{"type":"string","format":"email","example":"jean.dupont@example.fr"},"firstname":{"type":"string","example":"Jean"},"lastname":{"type":"string","example":"Dupont"},"company":{"type":"string","example":"Dupont Entreprises"},"accountId":{"type":"string","example":"c10e4fdf-a77d-457c-8fd6-2c0b29214800","description":"The accountId of the owner of this customer"},"createdAt":{"type":"string","format":"date-time","example":"2024-07-01T09:26:55.737Z"}}},"Product":{"type":"object","properties":{"id":{"type":"string","description":"Product identifier, used as productId when creating an order. The authoritative list is returned by GET /v2/products; the values below are common examples and may evolve.","example":"ntag424_pvccard_white"},"size":{"type":"object","properties":{"width":{"type":"number","example":0},"height":{"type":"number","example":0},"length":{"type":"number","example":0},"weight":{"type":"number","example":6}}},"prices":{"type":"array","description":"Pricing entries associated with the model.","items":{"type":"object"},"example":[]},"canBeEncoded":{"type":"boolean","example":true}}},"Unit":{"type":"object","properties":{"id":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"product":{"$ref":"#/components/schemas/Product"},"metadata":{"type":"array","description":"Always empty, kept for compatibility (the card metadata stopped in June 2024)","items":{"type":"object"},"example":[]},"files":{"type":"array","description":"The files of the card's order item, its front and back visuals","items":{"type":"object"},"example":[]},"status":{"type":"string","description":"Current status of the product","example":"created"},"shipments":{"type":"array","description":"Shipping information associated with the product","items":{"type":"object"},"example":[]},"createdAt":{"type":"string","format":"date-time","example":"2024-04-08T13:22:37.000Z"}}},"Item":{"type":"object","properties":{"id":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"productId":{"type":"string","example":"ntag424_pvccard_white"},"name":{"type":"string","example":"Jean Dupont"},"quantity":{"type":"integer","example":10},"nfc":{"$ref":"#/components/schemas/NFC"},"product":{"$ref":"#/components/schemas/Product"}}},"SubscriptionPlan":{"type":"object","properties":{"id":{"type":"string","example":"987e6543-e21a-11d4-a789-0242ac120002"},"displayname":{"type":"string","example":"Basic Plan"},"price":{"type":"number","example":49.99},"shippingLetter":{"type":"boolean","example":true},"whiteLabel":{"type":"boolean","example":false}}},"Shipment":{"type":"object","properties":{"id":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"trackerId":{"type":"string","example":"1234567890"},"trackingNumber":{"type":"string","example":"1Z999AA10123456784"},"unitsByProduct":{"type":"object","description":"The shipment's units counted per catalogue product, keyed by the product's id","additionalProperties":{"type":"integer"},"example":{"ntag424_pvccard_white":4}}}},"ShippingCarrier":{"type":"object","description":"A carrier service available for a destination, with its computed price.","properties":{"id":{"type":"string","description":"Carrier service identifier to pass as carrierServiceId when creating an order.","example":"lpf_standard"},"name":{"type":"string","description":"Human-readable carrier service name.","example":"La Poste - Lettre Suivie"},"code":{"type":"string","description":"Internal carrier code.","example":"laposte"},"hasServicePoints":{"type":"boolean","description":"Whether this service delivers to service points (relay points / lockers).","example":false},"apiProvider":{"type":"string","description":"Backend provider handling this carrier.","example":"laposte"},"countries":{"type":"array","items":{"type":"string"},"example":["FR","BE"]},"enabledCountries":{"type":"array","description":"Destination countries this service is enabled for ('*' means all).","items":{"type":"string"},"example":["FR","BE","LU"]},"price":{"type":"number","format":"float","description":"Price for the requested destination and weight, in EUR. Only services with a price greater than 0 are returned.","example":2.3},"weightTier":{"type":"integer","nullable":true,"description":"Weight threshold (in grams) of the matched price tier.","example":0},"priceTiers":{"type":"object","description":"Raw price tiers for this service. For weight-based providers the keys are weight thresholds in grams; for Sendcloud-based providers the keys are country codes.","example":{"0":2.3,"250":3}},"minWeight":{"type":"integer","description":"Minimum supported weight in grams.","example":0},"maxWeight":{"type":"integer","description":"Maximum supported weight in grams.","example":3000}}},"ShippingRate":{"type":"object","description":"A carrier service available for a set of items, with its computed price.","properties":{"id":{"type":"string","description":"Carrier service identifier to pass as carrierServiceId when creating an order.","example":"lpf_standard"},"name":{"type":"string","example":"La Poste - Lettre Suivie"},"code":{"type":"string","example":"laposte"},"serviceCode":{"type":"string","nullable":true,"description":"Stable code of the service within its carrier (for example chrono_13, chrono_18). Use it to pick a service without parsing its name, which is generated from the carrier catalogue.","example":"chrono_18"},"hasServicePoints":{"type":"boolean","example":false},"price":{"type":"number","format":"float","description":"Price for the computed weight and destination, in EUR.","example":3},"weightTier":{"type":"integer","nullable":true,"description":"Weight threshold (in grams) of the matched price tier.","example":250}}},"ShippingEnvelope":{"type":"object","description":"The packaging envelope selected for the items, based on total volume.","properties":{"id":{"type":"string","description":"Envelope identifier, or 'custom' when no standard envelope matches the volume.","example":"custom"},"name":{"type":"string","example":"Emballage spécial"},"weight":{"type":"number","description":"Envelope weight in grams, included in the total shipping weight.","example":50}}},"Order":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"987e6543-e21a-11d4-a789-0242ac120002"},"reference":{"type":"string","example":"Order-JeanDupont-2024"},"accountId":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"status":{"type":"string","example":"created"},"customer":{"$ref":"#/components/schemas/Customer"},"shipTo":{"$ref":"#/components/schemas/Address"},"statusHistory":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"987e6543-e21a-11d4-a789-0242ac120002"},"status":{"type":"string","example":"created"},"createdAt":{"type":"string","format":"date-time","example":"2024-07-01T09:26:55.737Z"}}}},"units":{"type":"array","items":{"$ref":"#/components/schemas/Unit"}},"items":{"type":"array","items":{"$ref":"#/components/schemas/Item"}},"metadata":{"type":"object","nullable":true,"description":"Additional metadata for the order","example":{}},"shipments":{"description":"Shipping information associated with the order","type":"array","items":{"$ref":"#/components/schemas/Shipment"}},"total":{"type":"number","example":50.99}}},"NFC":{"type":"object","properties":{"type":{"type":"string","enum":["empty","uri","text","dynamic"],"example":"empty"},"content":{"type":"string","example":"https://example.com"},"dynamic":{"type":"object","required":["baseUrl","records"],"properties":{"baseUrl":{"type":"string","format":"uri","example":"https://example.com"},"records":{"type":"object","required":["publicKey","privateKey"],"properties":{"recordId":{"type":"integer","example":1},"publicKey":{"type":"string","example":"B5ADF942C642D16B2803CA672F47D11D","minLength":32,"maxLength":32},"privateKey":{"type":"string","example":"82F87EB3F693B84BCC154A0373363A62","minLength":32,"maxLength":32}}}}},"options":{"type":"object","description":"Encoding options applied during NFC encoding","properties":{"lock":{"type":"boolean","description":"If true, the NFC chip is permanently locked (read-only) after encoding. This is irreversible.","default":false,"example":true},"uid":{"type":"boolean","description":"If true, the encoder reads and stores the chip UID without writing any payload.","example":false}}}}}}},"paths":{"/v2/accounts/@me":{"get":{"operationId":"getCurrentAccount","summary":"Get the authenticated account","description":"Returns the account the API key belongs to, with its balance and subscription plan. Call it first: `account.id` is the `accountId` path parameter of every `/v2/accounts/{accountId}/...` endpoint (the alias `@me` also works there).","tags":["Accounts"],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Successfully retrieved account details","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"account":{"$ref":"#/components/schemas/Account"}}}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/accounts/{accountId}":{"get":{"operationId":"getAccount","summary":"Get an account by ID","description":"Returns an account's details. An API key only reads the account that holds it (use `@me` or its own id).","tags":["Accounts"],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"description":"Unique identifier of the account"}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Successfully retrieved account details","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"account":{"$ref":"#/components/schemas/Account"}}}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/accounts/{accountId}/orders/find":{"get":{"operationId":"findOrders","summary":"Find orders by reference or metadata","description":"Searches the account's orders by `reference` or `metadata`, and can limit the fields returned. The response is not paginated. A test key (`sk_test_`) only finds test orders, a live key only real orders.","tags":["Orders"],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"description":"Unique identifier of the account"},{"in":"query","name":"metadata","schema":{"type":"string","example":"internalOrderId:987e6543-e21a-11d4-a789-0242ac120002"},"description":"Filter orders based on metadata"},{"in":"query","name":"reference","schema":{"type":"string","example":"reference:987e6543-e21a-11d4-a789-0242ac120002"},"description":"Filter orders based on reference"},{"in":"query","name":"fields","schema":{"type":"string","example":"fields:id,reference"},"description":"Get only wanted fields"}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Successfully retrieved orders","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"orders":{"type":"array","items":{"$ref":"#/components/schemas/Order"}}}}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/accounts/{accountId}/orders/{orderId}":{"get":{"operationId":"getOrder","summary":"Get an order by ID","description":"Returns an order with its items, units, shipments, status history and prices. A test key only sees test orders, a live key only real orders.","tags":["Orders"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"description":"Unique identifier of the account"},{"in":"path","name":"orderId","required":true,"schema":{"type":"string","example":"987e6543-e21a-11d4-a789-0242ac120002"},"description":"Unique identifier of the order"}],"responses":{"200":{"description":"Successfully retrieved order details","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"order":{"$ref":"#/components/schemas/Order"}}}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/accounts/{accountId}/orders":{"get":{"operationId":"listOrders","x-eov-allow-unknown-query-parameters":true,"summary":"List the orders of an account","description":"Returns a page of the account's orders, the most recent first (sorted by creation date, not configurable). `page` and `per_page` paginate (defaults 1 and 20); `status` and `search` filter. Each order is a summary: its items, units, customer and recipient, without the prices and shipments of `getOrder`. The `recipient` (name and email of the delivery address) replaces `shipTo`. A test key (`sk_test_`) only lists test orders, a live key only real orders. For the full detail of one order, call `getOrder`.","tags":["Orders"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"@me"},"description":"Unique identifier of the account, or the alias `@me` for the account authenticated by the token."},{"in":"query","name":"page","required":false,"schema":{"type":"string","example":"1"},"description":"Page number, starting at 1 (default 1). A value that is not a positive whole number is replaced by 1. Declared as a string so that a malformed value is never refused: the route has always accepted it."},{"in":"query","name":"per_page","required":false,"schema":{"type":"string","example":"20"},"description":"Orders per page (default 20). A value that is not a positive whole number is replaced by 20. The API sets no maximum: ask for pages of 50 at most. Declared as a string so that a malformed value is never refused: the route has always accepted it."},{"in":"query","name":"status","required":false,"allowReserved":true,"schema":{"type":"string","example":"draft,in_queue"},"description":"Keeps the orders in one of these statuses: one status, or several separated by commas (`draft,in_queue`). Statuses are listed by `GET /orders/statuses`. Do not repeat the parameter."},{"in":"query","name":"search","required":false,"allowReserved":true,"schema":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"example":"Dupont"},"description":"Free text, 100 characters kept. Send it once: a repeated parameter is accepted but ignored. Matches, without regard to case, a part of the order's reference, of its customer's name, company or email, of its recipient's name or email, or the exact order id."}],"responses":{"200":{"description":"A page of orders","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"orders":{"type":"array","description":"The orders of the page, most recent first. Empty when the page is past the last one.","items":{"allOf":[{"$ref":"#/components/schemas/Order"},{"type":"object","properties":{"recipient":{"type":"object","description":"The delivery address's name and email, present when the order has a delivery address. It replaces `shipTo` in this list.","properties":{"name":{"type":"string","example":"Jean Dupont"},"email":{"type":"string","example":"jean@example.com"}}}}}]}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"Number of orders matching the filters, all pages together","example":134},"page":{"type":"integer","description":"The page returned","example":1},"per_page":{"type":"integer","description":"The page size used","example":20}}}}}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createOrder","summary":"Create an order","description":"Creates an order for the account. With `isDraft: true` nothing is produced or debited and the order can be validated later; with a live key a firm order debits the account balance and goes to production. With a test key (`sk_test_`) the order is a test order: nothing is debited and nothing is manufactured. Needs an account with a subscription plan (402 otherwise). To retry safely after a timeout, send an `Idempotency-Key` header: a second call with the same key on the same account returns the order already created, without creating or debiting a second one. Without the header, check with `findOrders` before retrying.","tags":["Orders"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"@me"},"description":"Unique identifier of the account. You can also use the alias `@me` to target the account authenticated by the token, without having to fetch its id first."},{"in":"header","name":"Idempotency-Key","required":false,"schema":{"type":"string","minLength":8,"maxLength":100,"example":"5f0c3b1e-8f4a-4b0e-9a57-2d1c6b7e9a10"},"description":"Optional. A unique key of your choice (8 to 100 characters, a UUID for example) that makes the creation safe to retry. The key is scoped to the account and to the key's mode (test or live): a second call with the same key returns HTTP 200 with the same body (`success`, `orderId`) of the order already created, plus an `Idempotent-Replayed: true` header, and creates and debits nothing. The request is not compared with the first one: the same key with a different body still returns the first order. The key is stored in the order's `metadata` (`mcp_idempotency_key`), shared with the MCP `idempotencyKey`. Without the header, nothing changes. A key shorter than 8 or longer than 100 characters is refused with 400."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"isDraft":{"type":"boolean","description":"Indicates if the order is a draft","example":false},"reference":{"type":"string","description":"Reference for the order","example":"Order-JeanDupont-2024"},"carrierServiceId":{"type":"string","description":"ID of the carrier service to use for shipping. Defaults to 'lpf_standard' if not provided. Use a service that supports service points when shipTo contains a servicePoint.","example":"lpf_standard"},"shipTo":{"$ref":"#/components/schemas/Address"},"items":{"type":"array","items":{"type":"object","properties":{"quantity":{"type":"integer","description":"Quantity of the product","example":10,"default":1},"productId":{"type":"string","enum":["pvccard_white","mifare1k_pvccard_white","ntag213_pvccard_white","ntag216_pvccard_white","ntag424_pvccard_white","ntag424_woodcard"],"description":"Product ID of the product","example":"ntag424_pvccard_white"},"metadata":{"type":"object","description":"Additional metadata for the product","example":{"internalId":"a3d3842e-89c0-41d2-9384-2e89c021d2bc"}},"packagingId":{"type":"string","description":"ID of the packaging","example":"aluminum_protection"},"printingVisuals":{"type":"array","description":"List of printing visuals for the product, obtained from the files API","items":{"type":"string","format":"uuid","nullable":true},"example":["0806a77d-8007-4cfc-86a7-7d80079cfc8f","a77d8007-007c-fc86-7a7d-80079cfc8f0"]},"nfc":{"oneOf":[{"$ref":"#/components/schemas/NFC"},{"type":"array","items":{"$ref":"#/components/schemas/NFC"}}]}},"required":["quantity","productId"]}}},"required":["shipTo"]}}}},"responses":{"200":{"description":"Order created successfully, or, for a replayed `Idempotency-Key`, the order already created (header `Idempotent-Replayed true`)","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Idempotent-Replayed":{"description":"`true` when the response replays the order a previous call created with the same `Idempotency-Key`; absent otherwise","schema":{"type":"string","example":"true"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"orderId":{"type":"string","example":"987e6543-e21a-11d4-a789-0242ac120002"}}}}}},"400":{"description":"Bad request, invalid input. `INVALID_QUANTITY` for a quantity that is not a whole number of at least 1 (a missing quantity counts as 1). An `Idempotency-Key` header of fewer than 8 or more than 100 characters is refused (`INVALID_IDEMPOTENCY_KEY`, or a validation error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account has no plan (`SUBSCRIPTION_REQUIRED`). Orders, test orders included, need an active plan or its free trial.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Account not found, a printing visual that is not a file of the account (`FILE_NOT_FOUND`), or an `orderId` that is not an order of the account (`ORDER_NOT_FOUND`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/accounts/{accountId}/files":{"post":{"operationId":"uploadFile","summary":"Upload a visual file","description":"Uploads a file (image or PDF) to the account's storage as `multipart/form-data`. The id of the stored file goes in `printingVisuals` when creating an order.","tags":["Files"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"description":"Unique identifier of the account"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The file to upload"}}}}}},"responses":{"201":{"description":"File uploaded successfully","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"image/*":{"schema":{"type":"string","format":"binary"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Bad request, invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/accounts/{accountId}/files/{fileId}":{"get":{"operationId":"getFile","summary":"Get a file by ID","description":"Returns a stored file. This route does not check the API key.","tags":["Files"],"security":[],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"description":"Unique identifier of the account"},{"in":"path","name":"fileId","required":true,"schema":{"type":"string","example":"987e6543-e21a-11d4-a789-0242ac120002"},"description":"Unique identifier of the file"}],"responses":{"200":{"description":"Successfully retrieved file details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"file":{"type":"object","properties":{"id":{"type":"string","example":"987e6543-e21a-11d4-a789-0242ac120002"},"name":{"type":"string","example":"example.pdf"},"size":{"type":"number","example":1024},"createdAt":{"type":"string","format":"date-time","example":"2024-07-01T09:26:55.737Z"}}}}}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/accounts/{accountId}/orders/{orderId}/validate":{"post":{"operationId":"validateOrder","summary":"Validate a draft order","description":"Validates a draft order: computes its prices and, with a live key, debits the account and sends it to production. The order must be in `draft` status. Test orders debit nothing.","tags":["Orders"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"123e4567-e89b-12d3-a456-426614174000"},"description":"Unique identifier of the account"},{"in":"path","name":"orderId","required":true,"schema":{"type":"string","example":"987e6543-e21a-11d4-a789-0242ac120002"},"description":"Unique identifier of the order"}],"responses":{"200":{"description":"Order validated successfully","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"prices":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","example":0},"quantity":{"type":"integer","example":10},"productId":{"type":"string","example":"ntag424_pvccard_white"},"unit_charges":{"type":"object","properties":{"encoding":{"type":"number","example":0.5,"nullable":true},"product":{"type":"number","example":0.5,"nullable":true},"printing":{"type":"number","example":0.1,"nullable":true},"packaging":{"type":"number","example":0.2,"nullable":true},"recordOptions":{"type":"number","example":0.3,"nullable":true}}},"charges":{"type":"object","properties":{"encoding":{"type":"number","example":5,"nullable":true},"product":{"type":"number","example":5,"nullable":true},"printing":{"type":"number","example":1,"nullable":true},"packaging":{"type":"number","example":2,"nullable":true},"recordOptions":{"type":"number","example":3,"nullable":true}}},"price":{"type":"number","example":4.49},"unit_price":{"type":"number","example":4.49}}}},"shipping":{"type":"object","properties":{"transporter":{"type":"string","example":"default"},"price":{"type":"number","example":5.99}}},"total":{"type":"number","example":50.99}}}}}}}},"400":{"description":"Bad request, invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/accounts/{accountId}/orders/{orderId}/simulate":{"post":{"operationId":"simulateOrderStatus","summary":"Simulate a status change on a test order","description":"Test environment only. Moves a test order, created with a `sk_test_` key, to the requested status and sends the same webhooks as in production (`order.updated`, plus `order.shipped` or `order.delivered`). Without a printer, this is how you see an order go through its life cycle. Real orders are refused with a 404. A test order is processed within a few seconds of its creation: until then (statuses `creating`, `draft`, `in_queue`) this endpoint answers 409 `TEST_ORDER_NOT_READY`, retry shortly.","tags":["Orders"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","example":"@me"},"description":"Unique identifier of the account. You can also use the alias `@me`."},{"in":"path","name":"orderId","required":true,"schema":{"type":"string","example":"987e6543-e21a-11d4-a789-0242ac120002"},"description":"Unique identifier of the test order"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Any status of the order life cycle, see `GET /orders/statuses`.","example":"shipped"}},"required":["status"]}}}},"responses":{"200":{"description":"Status changed, webhooks sent","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"order":{"type":"object","description":"The order in its new status"}}}}}},"400":{"description":"Missing or unknown status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Test order not found. Only orders created with a `sk_test_` key can be simulated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The test order is still being processed (`TEST_ORDER_NOT_READY`), retry in a few seconds","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/statuses":{"get":{"operationId":"listOrderStatuses","summary":"List order statuses","description":"Returns every status of the order life cycle (`id` and `name`). This route does not need authentication.","tags":["Statuses"],"security":[],"responses":{"200":{"description":"Successfully retrieved all statuses","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":0},"name":{"type":"string","example":"created"}}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/shipping/carriers":{"get":{"operationId":"listShippingCarriers","summary":"List carriers for a destination","description":"Returns the carrier services available for a destination, each with its computed price. Useful to let a customer pick a carrierServiceId before creating an order. Only services whose price is greater than 0 for the requested destination are returned.","tags":["Shipping"],"security":[],"parameters":[{"in":"query","name":"to_country","required":true,"schema":{"type":"string","minLength":2,"maxLength":2,"example":"FR"},"description":"ISO 2-letter destination country code."},{"in":"query","name":"to_postal_code","required":false,"schema":{"type":"string","example":"75001"},"description":"Destination postal code (reserved for postal-code-aware pricing)."},{"in":"query","name":"weight","required":false,"schema":{"type":"integer","example":30},"description":"Total weight in grams. When provided, only services whose weight range covers it are returned."}],"responses":{"200":{"description":"Successfully retrieved available carriers","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"count":{"type":"integer","example":2},"carriers":{"type":"array","items":{"$ref":"#/components/schemas/ShippingCarrier"}}}}}}},"400":{"description":"Bad request, missing required parameter (e.g. to_country)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/shipping/rates":{"post":{"operationId":"calculateShippingRates","summary":"Calculate shipping rates","description":"Calculates shipping rates for a list of items. The endpoint computes the total weight and volume of the items (model and optional packaging), selects the appropriate envelope, and returns the carrier services available for the destination with their price. Only services whose price is greater than 0 are returned.","tags":["Shipping"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to_country":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 2-letter destination country code.","example":"FR"},"to_postal_code":{"type":"string","description":"Destination postal code (reserved for postal-code-aware pricing).","example":"75001"},"items":{"type":"array","minItems":1,"description":"Items to ship. Weight and volume are derived from each model (and optional packaging).","items":{"type":"object","properties":{"productId":{"type":"string","enum":["pvccard_white","mifare1k_pvccard_white","ntag213_pvccard_white","ntag216_pvccard_white","ntag424_pvccard_white","ntag424_woodcard"],"description":"Product ID of the item.","example":"ntag424_pvccard_white"},"packagingId":{"type":"string","nullable":true,"description":"Optional packaging ID, added to the item weight and volume.","example":"aluminum_protection"},"quantity":{"type":"integer","default":1,"description":"Number of units for this item.","example":10}},"required":["productId"]}}},"required":["to_country","items"]}}}},"responses":{"200":{"description":"Successfully calculated shipping rates","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"weight":{"type":"integer","description":"Total shipping weight in grams (items + envelope).","example":110},"volume":{"type":"number","description":"Total items volume in cm³.","example":42.5},"envelope":{"$ref":"#/components/schemas/ShippingEnvelope"},"count":{"type":"integer","example":2},"carriers":{"type":"array","items":{"$ref":"#/components/schemas/ShippingRate"}}}}}}},"400":{"description":"Bad request, missing to_country or items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No matching models found for the provided items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/products":{"get":{"operationId":"listProducts","summary":"List available products","description":"Returns the list of product models available, read from the database. This is the authoritative source for the `productId` values used when creating an order or calculating shipping rates.","tags":["Products"],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Successfully retrieved available models","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"models":{"type":"array","items":{"$ref":"#/components/schemas/Product"}}}}}}},"401":{"description":"Unauthorized, authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API key is inactive or revoked (`INACTIVE_API_KEY`), or targets another account (`ACCOUNT_ACCESS_DENIED`, refused once the account guard is enforced)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}