Get one product, with every variant
const url = 'https://api.partners.collaterate.com/v1/products/example';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://api.partners.collaterate.com/v1/products/example \ --header 'Authorization: Bearer <token>'GET /v1/products (above) is variant-grained: a product with several variants is that
many rows to reassemble yourself. This endpoint does the grouping for you, for the one
productId you already have – from a paginated catalog sync, or from an order line
you are looking up.
Fields shared by every variant of this product (name, description, serviceType,
shippable, and the product-level catalog fields – visibility, orderable,
productStatus, the content/SEO fields, requiresApproval, supplierReference,
fulfillmentCenterName, stockUnitOfMeasure) are returned once, at the top level;
variants carries what differs per variant, including each variant’s attributes
and inventory breakdown. updatedOn is the MAX across the product’s variants, so a
change to any one of them moves it, not just a change to the first.
The product’s segment assignments are embedded as segments – the segments a
visibility: VISIBLE_TO_SOME product is shown to, by the same segmentId and name
that GET /v1/segments lists. [] means no assignments, which only matters when
visibility consults them. This detail endpoint is the only place assignments are
published; the variant-grained list carries visibility but no segment arrays.
Returns the identical 404 product_not_found whether the productId does not exist or
exists outside your granted sites – these two cases are indistinguishable on purpose,
the same rule GET /v1/orders/{orderNumber} applies (see “Scoping and the 404-vs-403
rule” above).
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The SLO_/SLS_ prefixed identifier, from GET /v1/products or from an order
line’s productId.
Responses
Section titled “Responses”The product, grouped into its variants.
GET /v1/products/{productId} – one product, every variant grouped under it.
productId/siteId/name/description/serviceType/shippable are shared by every
variant and returned once, here; variants carries what differs per variant. See
GET /v1/products (the Product schema) for the variant-grained equivalent of this
same underlying catalog entry.
object
The identifier passed in the path. See Product.productId.
The granted site this product belongs to.
For an SLS_ product this is the value the storefront shows: the SHARE’s own copy
when it overrides the master system offering’s display fields, otherwise the
MASTER’s. Resolved by this API rather than read straight from the catalog view,
which applies that rule to a warehoused share and not to a print-on-demand one.
STOCK, POD, or DIGITAL. Only the first two are orderable through this API;
a DIGITAL product is listed (it is catalog) but never orderable.
The MAX updatedOn across this product’s variants, so a change to any one of them
moves it, not just a change to the first.
Product-level, like serviceType/shippable above – not per-variant. See
Product.artworkSource.
Always ACTIVE today. See Product.productStatus.
Product-level, shared by every variant. See Product.visibility.
Product-level – its inputs (fulfillment center, service type, visibility) are all
offering-level, so every variant shares it. See Product.orderable.
The plural display name, when the site has set one.
Short storefront blurb, when set.
SEO meta description, when set. For an SLS_ product this is the master system
offering’s unless the share overrides the master’s display fields – the same
inheritance description documents, and the same values the storefront renders.
SEO meta keywords, when set. Inherited from the master system offering on the same
rule as metaDescription.
The storefront URL slug, when set.
Whether the site excludes this product from storefront search.
Fully-qualified URL of the product’s PRIMARY image. See Product.imageUrl.
Fully-qualified URL of the primary image’s thumbnail, or null.
Whether a share-backed (SLS_) product overrides the master product’s display fields,
or null on a site-owned (SLO_) product, which has no master and no override
mechanism at all.
One flag covers several fields. false means description, metaDescription,
metaKeywords and the thumbnail fields are all INHERITED from the master, and the
values published here are the inherited ones – the same values the storefront shows.
true means this share’s own values stand. There is no per-field inheritance, and
writing one of those fields does not change the flag.
Whether the site requires an approval step before this product’s orders proceed.
The site’s own supplier reference for this product, when set.
The fulfillment center this product ships from, by name, or null when none is
assigned – in which case the product is never orderable.
The unit the inventory fields count in (e.g. EA), when set.
The segments this product is assigned to – what visibility: VISIBLE_TO_SOME
gates on – ordered by name. Sourced from the segment assignments themselves,
never from the catalog view’s denormalised (and unreliable) segment code column.
[] means no assignments, which only matters when visibility consults them.
One segment assignment within ProductDetail.segments: the id and name only. The
segmentId is the same id GET /v1/segments lists, and that endpoint carries each
segment’s description – the vocabulary belongs there, not repeated on every product
assigned to it.
object
One variant within ProductDetail.variants – everything about a variant of
GET /v1/products/{productId}’s product that is NOT shared with its other variants.
weight is the same decimal-string field Product.weight publishes, unchanged.
object
The specific variant, or null for a POD product, which has no variant until the
job is configured. Send it back exactly as received, null included, on an order
line – see Product.variantId.
null for a POD product, which has no SKU.
Stock on hand, or null when untracked. See Product.availableInventory.
Whether availableInventory means anything. See Product.inventoryTracked.
Minimum orderable quantity, or null for no minimum.
Maximum orderable quantity, or null for no maximum.
Per-unit weight as a decimal string, or null for a POD product, which has
none until the job is configured. See Product.weight.
This variant’s attribute pairs. See Product.attributes.
One attribute pair of a variant. Both sides are always present – incomplete pairs are not published.
object
The attribute’s name, e.g. Color.
This variant’s choice for it, e.g. White.
On-hand stock before reservations and backorders, or null when untracked –
all four inventory fields are null together. See Product.actualInventory.
Stock reserved by open orders, or null when untracked.
Quantity on backorder, or null when untracked.
The quantity the storefront pre-fills, when one is set.
Null for every STOCK product, always – there is no such setting on a stock
product upstream, so this is not a gap in your data. On POD products it is derived
from the pricing configuration, which this API does not expose. It is read-only and
cannot be written through PATCH /v1/products/{productId}.
Examples
{ "productId": "SLO_918273", "siteId": 42, "name": "Business Card", "namePlural": "Business Cards", "description": "16pt uncoated, full color both sides", "excerpt": null, "metaDescription": null, "metaKeywords": null, "urlPattern": "business-cards", "excludeFromSearch": false, "imageUrl": "https://acme.collaterate.com/resources/offering_images/business-cards.png", "thumbnailUrl": "https://acme.collaterate.com/resources/offering_images/business-cards-thumb.png", "serviceType": "STOCK", "productStatus": "ACTIVE", "visibility": "VISIBLE_TO_ALL", "orderable": true, "requiresApproval": false, "supplierReference": null, "fulfillmentCenterName": "Cypress", "stockUnitOfMeasure": "EA", "shippable": true, "updatedOn": "2026-07-14T09:31:00.000Z", "segments": [ { "segmentId": 3, "name": "Corporate" } ], "variants": [ { "variantId": 55012, "sku": "BC-100-WHT", "attributes": [ { "name": "Color", "choice": "White" } ], "availableInventory": 1450, "actualInventory": 1500, "reservedInventory": 50, "backorderInventory": 0, "inventoryTracked": true, "minQuantity": null, "maxQuantity": null, "defaultQuantity": 100, "weight": "0.0120000000" }, { "variantId": 55013, "sku": "BC-100-CRM", "attributes": [ { "name": "Color", "choice": "Cream" } ], "availableInventory": null, "actualInventory": null, "reservedInventory": null, "backorderInventory": null, "inventoryTracked": false, "minQuantity": null, "maxQuantity": null, "defaultQuantity": 100, "weight": "0.0120000000" } ]}The productId path segment does not match the SLO_/SLS_-prefixed shape. This route accepts no query parameters at all – sending one is unknown_query_parameter, not silently ignored.
RFC 9457 application/problem+json body. code is the published, stable,
machine-readable field to branch your integration logic on – detail is a
human-readable string that may be reworded over time and must not be parsed.
This table is the whole published set: every code this API can return appears below,
and nothing below is unreachable. test/docs/openapi-matches-reality.test.ts compares
the enum to the codes the handlers actually construct, so a code added to one side and
not the other fails the build rather than shipping.
Published code values:
code |
HTTP status | Meaning | Retry? |
|---|---|---|---|
invalid_cursor |
400 | The cursor query parameter could not be decoded. |
No – restart pagination with no cursor. Do not resend the same value. |
invalid_site_id |
400 | The siteId filter is not a positive integer, or it names a site outside your grant. detail says which. |
No – fix the parameter against GET /v1/me’s grantedSiteIds. |
invalid_order_number |
400 | The order number in the path is not a positive integer. | No – fix the path. |
invalid_product_id |
400 | The productId path segment on GET /v1/products/{productId} does not match the SLO_/SLS_-prefixed shape. |
No – fix the path. |
invalid_service_type |
400 | GET /v1/products’s serviceType filter is not one of the values this API can order (STOCK or POD). |
No – fix the parameter. |
updated_since_not_supported |
400 | An updatedSince parameter was sent. Rejected rather than ignored, on purpose – see “No updatedSince / no change polling” above. |
No – remove the parameter; there is no change-polling mechanism to switch to. |
invalid_request |
400 | POST /v1/orders’s request body failed validation – malformed JSON, a missing or malformed field, an unknown key, or a body-level rule such as partnerLineId uniqueness or a misplaced shipTo – or GET /v1/orders/submissions’s status/limit filter is malformed. detail names the exact violation. |
No – fix the request. |
invalid_submission_id |
400 | The submissionId path segment on GET /v1/orders/submissions/{submissionId} is not a well-formed UUID. |
No – fix the path. |
invalid_quantity |
400 | The quantity on POST /v1/products/{productId}/quote is missing or not a positive integer. |
No – fix the request body. |
invalid_shipping_quote_request |
400 | POST /v1/shipping/quote’s body failed validation – malformed JSON, a missing or mistyped field, an unknown key at the top level or inside shipTo, or a weightOunces outside 0 < w <= 5000000. detail names the field. |
No – fix the request body. |
invalid_item_number |
400 | The itemNumber path segment on a proof route is not a positive integer. It is the line’s itemNumber from GET /v1/orders/{orderNumber}/items, never a Collaterate internal id. |
No – fix the path. |
invalid_token |
401 | The credential is missing, expired, or invalid. | No – mint a new token, retry once, then investigate the credential. |
insufficient_scope |
403 | The token lacks the scope required for this operation. | No – mint a token with the required scope. |
order_not_found |
404 | No such order, or one that exists outside your granted sites (see above). | No. |
order_item_not_found |
404 | No line matching (orderNumber, itemNumber) resolves for you – absent, outside your granted sites, or hidden from the customer, identically. |
No. |
product_not_found |
404 | No such product, or one that exists but belongs to a site outside your granted sites (same rule as order_not_found). |
No. |
quote_product_not_found |
404 | The product resolved in our own catalog but Collaterate could not price it. Rare. | No. |
template_not_found |
404 | GET /v1/products/{productId}/template found no Chili template for this product – not TEMPLATE-classified, TEMPLATE-classified with a legacy EDOC/CANVA vendor, or Chili-templated with no document configured. All three identically. |
No. |
submission_not_found |
404 | No submission exists with this id for your credential – whether it never existed or belongs to another partner, identically. | No. |
ordering_not_provisioned |
409 | POST /v1/orders’s siteId has no ordering user configured. This is set up per (partner, site) by staff at onboarding; a partner cannot provision it themselves. |
No – ask Partner Integrations to provision an ordering user for the site, then retry. |
partner_order_id_reused |
409 | POST /v1/orders’s partnerOrderId was already used, with a request body that does not match this one. Resending the identical body under the same partnerOrderId is safe and returns the original submission (see the operation description); reusing it for a materially different order is rejected instead of silently creating a second order. |
No – use a new partnerOrderId, or resend the exact original body to poll the existing submission. |
proof_not_pending_review |
409 | A proof approve or decline was sent for a line whose proofStatus is not PENDING_REVIEW – no proof yet, already decided, or a state this API does not recognise. detail says which. On a RETRY of a decision that already succeeded, this is the expected answer and means success. |
No – read the line’s current proofStatus from GET /v1/orders/{orderNumber}/items. |
order_item_cancelled |
409 | A proof approve or decline was sent for a cancelled line. It will never be produced, so its proof cannot be decided. | No. |
quote_invalid |
422 | Collaterate rejected the quote on business-validation grounds (e.g. quantity over the product’s configured maximum). detail carries Collaterate’s own validation message. |
No – fix the request body (e.g. reduce quantity). |
unknown_country_code |
422 | POST /v1/shipping/quote’s shipTo.country is not a country code Collaterate recognises. |
No – send a code Collaterate carries (US, CA, MX, …). |
unknown_state_code |
422 | POST /v1/shipping/quote’s shipTo.state is not a state or province of the country you sent. State codes are resolved WITHIN the country, since they are not unique across countries. |
No – fix the state, or the country it belongs to. |
shipping_quote_unavailable |
422 | POST /v1/shipping/quote produced no priced service at all for that destination and weight. detail carries Collaterate’s own reasons when it gave any – an invalid postal code for the country is the common one – and says so plainly when it did not. |
No – fix the destination. An empty result is never returned as a 200. |
denial_reason_not_available |
422 | POST .../proof/decline’s reason is well-formed but is not a denial reason this line’s proof offers – including a code that is valid on a different line, and UNKNOWN, which never is. detail lists what is valid here. |
No – pick a code from GET .../proof/denial-reasons for this line. |
rate_limit_exceeded |
429 | Rate limit or daily quota exceeded. | Yes – honor Retry-After. |
internal_error |
500 | Unexpected server-side failure. Detail is always the fixed string below, never the underlying error. | No, or with caution – if persistent, contact Partner Integrations. |
service_unavailable |
503 | Transient failure – typically the database path, but also a Lambda that crashed, timed out, or hit its own concurrency limit before any handler ran. | Yes, with backoff. |
Three statuses are answered by the API gateway before your request reaches application
code: 401 (authorizer denial), 429 (usage-plan throttle), and 503 (the Lambda
integration itself failed). All three are configured to return this same document with
the codes above, so one parser and one branch on code covers every error this API
produces. The 403 on GET /v1/ping is the sole exception anywhere in the API – it
comes from the edge firewall, which is not ours to shape, and carries no problem
document.
object
Always the literal string about:blank today; reserved by RFC 9457 for future use.
Short, human-readable summary of the HTTP status (e.g. “Not Found”).
The HTTP status code, repeated in the body for convenience.
Stable, machine-readable error identifier. Safe to branch on. See the table above.
Human-readable explanation. Do not parse this – it may be reworded without notice.
Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.
Example
{ "type": "about:blank", "title": "Bad Request", "status": 400, "code": "invalid_product_id", "detail": "\"productId\" must match ^(SLO|SLS)_\\d{1,18}$, got: not-a-product-id", "requestId": "8f3c1e2a-..."}Missing, expired, or otherwise invalid credential. Not retryable as-is.
Produced by the API gateway’s authorizer, before the request reaches any application
code, so every cause – no Authorization header, a malformed one, a token that is
expired, wrongly signed, from the wrong pool, or belongs to a credential that has been
disabled – yields this identical response. The gateway is configured to answer in the
same application/problem+json shape as everything else, so you do not need a second
parser for this status; what it cannot do is tell you which of those causes applied.
RFC 9457 application/problem+json body. code is the published, stable,
machine-readable field to branch your integration logic on – detail is a
human-readable string that may be reworded over time and must not be parsed.
This table is the whole published set: every code this API can return appears below,
and nothing below is unreachable. test/docs/openapi-matches-reality.test.ts compares
the enum to the codes the handlers actually construct, so a code added to one side and
not the other fails the build rather than shipping.
Published code values:
code |
HTTP status | Meaning | Retry? |
|---|---|---|---|
invalid_cursor |
400 | The cursor query parameter could not be decoded. |
No – restart pagination with no cursor. Do not resend the same value. |
invalid_site_id |
400 | The siteId filter is not a positive integer, or it names a site outside your grant. detail says which. |
No – fix the parameter against GET /v1/me’s grantedSiteIds. |
invalid_order_number |
400 | The order number in the path is not a positive integer. | No – fix the path. |
invalid_product_id |
400 | The productId path segment on GET /v1/products/{productId} does not match the SLO_/SLS_-prefixed shape. |
No – fix the path. |
invalid_service_type |
400 | GET /v1/products’s serviceType filter is not one of the values this API can order (STOCK or POD). |
No – fix the parameter. |
updated_since_not_supported |
400 | An updatedSince parameter was sent. Rejected rather than ignored, on purpose – see “No updatedSince / no change polling” above. |
No – remove the parameter; there is no change-polling mechanism to switch to. |
invalid_request |
400 | POST /v1/orders’s request body failed validation – malformed JSON, a missing or malformed field, an unknown key, or a body-level rule such as partnerLineId uniqueness or a misplaced shipTo – or GET /v1/orders/submissions’s status/limit filter is malformed. detail names the exact violation. |
No – fix the request. |
invalid_submission_id |
400 | The submissionId path segment on GET /v1/orders/submissions/{submissionId} is not a well-formed UUID. |
No – fix the path. |
invalid_quantity |
400 | The quantity on POST /v1/products/{productId}/quote is missing or not a positive integer. |
No – fix the request body. |
invalid_shipping_quote_request |
400 | POST /v1/shipping/quote’s body failed validation – malformed JSON, a missing or mistyped field, an unknown key at the top level or inside shipTo, or a weightOunces outside 0 < w <= 5000000. detail names the field. |
No – fix the request body. |
invalid_item_number |
400 | The itemNumber path segment on a proof route is not a positive integer. It is the line’s itemNumber from GET /v1/orders/{orderNumber}/items, never a Collaterate internal id. |
No – fix the path. |
invalid_token |
401 | The credential is missing, expired, or invalid. | No – mint a new token, retry once, then investigate the credential. |
insufficient_scope |
403 | The token lacks the scope required for this operation. | No – mint a token with the required scope. |
order_not_found |
404 | No such order, or one that exists outside your granted sites (see above). | No. |
order_item_not_found |
404 | No line matching (orderNumber, itemNumber) resolves for you – absent, outside your granted sites, or hidden from the customer, identically. |
No. |
product_not_found |
404 | No such product, or one that exists but belongs to a site outside your granted sites (same rule as order_not_found). |
No. |
quote_product_not_found |
404 | The product resolved in our own catalog but Collaterate could not price it. Rare. | No. |
template_not_found |
404 | GET /v1/products/{productId}/template found no Chili template for this product – not TEMPLATE-classified, TEMPLATE-classified with a legacy EDOC/CANVA vendor, or Chili-templated with no document configured. All three identically. |
No. |
submission_not_found |
404 | No submission exists with this id for your credential – whether it never existed or belongs to another partner, identically. | No. |
ordering_not_provisioned |
409 | POST /v1/orders’s siteId has no ordering user configured. This is set up per (partner, site) by staff at onboarding; a partner cannot provision it themselves. |
No – ask Partner Integrations to provision an ordering user for the site, then retry. |
partner_order_id_reused |
409 | POST /v1/orders’s partnerOrderId was already used, with a request body that does not match this one. Resending the identical body under the same partnerOrderId is safe and returns the original submission (see the operation description); reusing it for a materially different order is rejected instead of silently creating a second order. |
No – use a new partnerOrderId, or resend the exact original body to poll the existing submission. |
proof_not_pending_review |
409 | A proof approve or decline was sent for a line whose proofStatus is not PENDING_REVIEW – no proof yet, already decided, or a state this API does not recognise. detail says which. On a RETRY of a decision that already succeeded, this is the expected answer and means success. |
No – read the line’s current proofStatus from GET /v1/orders/{orderNumber}/items. |
order_item_cancelled |
409 | A proof approve or decline was sent for a cancelled line. It will never be produced, so its proof cannot be decided. | No. |
quote_invalid |
422 | Collaterate rejected the quote on business-validation grounds (e.g. quantity over the product’s configured maximum). detail carries Collaterate’s own validation message. |
No – fix the request body (e.g. reduce quantity). |
unknown_country_code |
422 | POST /v1/shipping/quote’s shipTo.country is not a country code Collaterate recognises. |
No – send a code Collaterate carries (US, CA, MX, …). |
unknown_state_code |
422 | POST /v1/shipping/quote’s shipTo.state is not a state or province of the country you sent. State codes are resolved WITHIN the country, since they are not unique across countries. |
No – fix the state, or the country it belongs to. |
shipping_quote_unavailable |
422 | POST /v1/shipping/quote produced no priced service at all for that destination and weight. detail carries Collaterate’s own reasons when it gave any – an invalid postal code for the country is the common one – and says so plainly when it did not. |
No – fix the destination. An empty result is never returned as a 200. |
denial_reason_not_available |
422 | POST .../proof/decline’s reason is well-formed but is not a denial reason this line’s proof offers – including a code that is valid on a different line, and UNKNOWN, which never is. detail lists what is valid here. |
No – pick a code from GET .../proof/denial-reasons for this line. |
rate_limit_exceeded |
429 | Rate limit or daily quota exceeded. | Yes – honor Retry-After. |
internal_error |
500 | Unexpected server-side failure. Detail is always the fixed string below, never the underlying error. | No, or with caution – if persistent, contact Partner Integrations. |
service_unavailable |
503 | Transient failure – typically the database path, but also a Lambda that crashed, timed out, or hit its own concurrency limit before any handler ran. | Yes, with backoff. |
Three statuses are answered by the API gateway before your request reaches application
code: 401 (authorizer denial), 429 (usage-plan throttle), and 503 (the Lambda
integration itself failed). All three are configured to return this same document with
the codes above, so one parser and one branch on code covers every error this API
produces. The 403 on GET /v1/ping is the sole exception anywhere in the API – it
comes from the edge firewall, which is not ours to shape, and carries no problem
document.
object
Always the literal string about:blank today; reserved by RFC 9457 for future use.
Short, human-readable summary of the HTTP status (e.g. “Not Found”).
The HTTP status code, repeated in the body for convenience.
Stable, machine-readable error identifier. Safe to branch on. See the table above.
Human-readable explanation. Do not parse this – it may be reworded without notice.
Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.
Example
{ "type": "about:blank", "title": "Unauthorized", "status": 401, "code": "invalid_token", "detail": "The credential presented with this request is missing, expired, or invalid.", "requestId": "8f3c1e2a-..."}The credential is valid but lacks the scope required for this operation (for
example, a token minted without partner-api/orders:read). Not retryable as-is –
request a token with the required scope. Not to be confused with an out-of-scope
order, which is a 404 (see above), or with the edge firewall’s 403 on /ping,
which carries no problem document at all.
RFC 9457 application/problem+json body. code is the published, stable,
machine-readable field to branch your integration logic on – detail is a
human-readable string that may be reworded over time and must not be parsed.
This table is the whole published set: every code this API can return appears below,
and nothing below is unreachable. test/docs/openapi-matches-reality.test.ts compares
the enum to the codes the handlers actually construct, so a code added to one side and
not the other fails the build rather than shipping.
Published code values:
code |
HTTP status | Meaning | Retry? |
|---|---|---|---|
invalid_cursor |
400 | The cursor query parameter could not be decoded. |
No – restart pagination with no cursor. Do not resend the same value. |
invalid_site_id |
400 | The siteId filter is not a positive integer, or it names a site outside your grant. detail says which. |
No – fix the parameter against GET /v1/me’s grantedSiteIds. |
invalid_order_number |
400 | The order number in the path is not a positive integer. | No – fix the path. |
invalid_product_id |
400 | The productId path segment on GET /v1/products/{productId} does not match the SLO_/SLS_-prefixed shape. |
No – fix the path. |
invalid_service_type |
400 | GET /v1/products’s serviceType filter is not one of the values this API can order (STOCK or POD). |
No – fix the parameter. |
updated_since_not_supported |
400 | An updatedSince parameter was sent. Rejected rather than ignored, on purpose – see “No updatedSince / no change polling” above. |
No – remove the parameter; there is no change-polling mechanism to switch to. |
invalid_request |
400 | POST /v1/orders’s request body failed validation – malformed JSON, a missing or malformed field, an unknown key, or a body-level rule such as partnerLineId uniqueness or a misplaced shipTo – or GET /v1/orders/submissions’s status/limit filter is malformed. detail names the exact violation. |
No – fix the request. |
invalid_submission_id |
400 | The submissionId path segment on GET /v1/orders/submissions/{submissionId} is not a well-formed UUID. |
No – fix the path. |
invalid_quantity |
400 | The quantity on POST /v1/products/{productId}/quote is missing or not a positive integer. |
No – fix the request body. |
invalid_shipping_quote_request |
400 | POST /v1/shipping/quote’s body failed validation – malformed JSON, a missing or mistyped field, an unknown key at the top level or inside shipTo, or a weightOunces outside 0 < w <= 5000000. detail names the field. |
No – fix the request body. |
invalid_item_number |
400 | The itemNumber path segment on a proof route is not a positive integer. It is the line’s itemNumber from GET /v1/orders/{orderNumber}/items, never a Collaterate internal id. |
No – fix the path. |
invalid_token |
401 | The credential is missing, expired, or invalid. | No – mint a new token, retry once, then investigate the credential. |
insufficient_scope |
403 | The token lacks the scope required for this operation. | No – mint a token with the required scope. |
order_not_found |
404 | No such order, or one that exists outside your granted sites (see above). | No. |
order_item_not_found |
404 | No line matching (orderNumber, itemNumber) resolves for you – absent, outside your granted sites, or hidden from the customer, identically. |
No. |
product_not_found |
404 | No such product, or one that exists but belongs to a site outside your granted sites (same rule as order_not_found). |
No. |
quote_product_not_found |
404 | The product resolved in our own catalog but Collaterate could not price it. Rare. | No. |
template_not_found |
404 | GET /v1/products/{productId}/template found no Chili template for this product – not TEMPLATE-classified, TEMPLATE-classified with a legacy EDOC/CANVA vendor, or Chili-templated with no document configured. All three identically. |
No. |
submission_not_found |
404 | No submission exists with this id for your credential – whether it never existed or belongs to another partner, identically. | No. |
ordering_not_provisioned |
409 | POST /v1/orders’s siteId has no ordering user configured. This is set up per (partner, site) by staff at onboarding; a partner cannot provision it themselves. |
No – ask Partner Integrations to provision an ordering user for the site, then retry. |
partner_order_id_reused |
409 | POST /v1/orders’s partnerOrderId was already used, with a request body that does not match this one. Resending the identical body under the same partnerOrderId is safe and returns the original submission (see the operation description); reusing it for a materially different order is rejected instead of silently creating a second order. |
No – use a new partnerOrderId, or resend the exact original body to poll the existing submission. |
proof_not_pending_review |
409 | A proof approve or decline was sent for a line whose proofStatus is not PENDING_REVIEW – no proof yet, already decided, or a state this API does not recognise. detail says which. On a RETRY of a decision that already succeeded, this is the expected answer and means success. |
No – read the line’s current proofStatus from GET /v1/orders/{orderNumber}/items. |
order_item_cancelled |
409 | A proof approve or decline was sent for a cancelled line. It will never be produced, so its proof cannot be decided. | No. |
quote_invalid |
422 | Collaterate rejected the quote on business-validation grounds (e.g. quantity over the product’s configured maximum). detail carries Collaterate’s own validation message. |
No – fix the request body (e.g. reduce quantity). |
unknown_country_code |
422 | POST /v1/shipping/quote’s shipTo.country is not a country code Collaterate recognises. |
No – send a code Collaterate carries (US, CA, MX, …). |
unknown_state_code |
422 | POST /v1/shipping/quote’s shipTo.state is not a state or province of the country you sent. State codes are resolved WITHIN the country, since they are not unique across countries. |
No – fix the state, or the country it belongs to. |
shipping_quote_unavailable |
422 | POST /v1/shipping/quote produced no priced service at all for that destination and weight. detail carries Collaterate’s own reasons when it gave any – an invalid postal code for the country is the common one – and says so plainly when it did not. |
No – fix the destination. An empty result is never returned as a 200. |
denial_reason_not_available |
422 | POST .../proof/decline’s reason is well-formed but is not a denial reason this line’s proof offers – including a code that is valid on a different line, and UNKNOWN, which never is. detail lists what is valid here. |
No – pick a code from GET .../proof/denial-reasons for this line. |
rate_limit_exceeded |
429 | Rate limit or daily quota exceeded. | Yes – honor Retry-After. |
internal_error |
500 | Unexpected server-side failure. Detail is always the fixed string below, never the underlying error. | No, or with caution – if persistent, contact Partner Integrations. |
service_unavailable |
503 | Transient failure – typically the database path, but also a Lambda that crashed, timed out, or hit its own concurrency limit before any handler ran. | Yes, with backoff. |
Three statuses are answered by the API gateway before your request reaches application
code: 401 (authorizer denial), 429 (usage-plan throttle), and 503 (the Lambda
integration itself failed). All three are configured to return this same document with
the codes above, so one parser and one branch on code covers every error this API
produces. The 403 on GET /v1/ping is the sole exception anywhere in the API – it
comes from the edge firewall, which is not ours to shape, and carries no problem
document.
object
Always the literal string about:blank today; reserved by RFC 9457 for future use.
Short, human-readable summary of the HTTP status (e.g. “Not Found”).
The HTTP status code, repeated in the body for convenience.
Stable, machine-readable error identifier. Safe to branch on. See the table above.
Human-readable explanation. Do not parse this – it may be reworded without notice.
Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.
Example
{ "type": "about:blank", "title": "Forbidden", "status": 403, "code": "insufficient_scope", "detail": "This request requires the \"partner-api/orders:read\" scope.", "requestId": "8f3c1e2a-..."}No such product, OR a product that exists but belongs to a site outside your granted
sites. These two cases are indistinguishable on purpose, the same rule order_not_found
applies. Not retryable as-is.
RFC 9457 application/problem+json body. code is the published, stable,
machine-readable field to branch your integration logic on – detail is a
human-readable string that may be reworded over time and must not be parsed.
This table is the whole published set: every code this API can return appears below,
and nothing below is unreachable. test/docs/openapi-matches-reality.test.ts compares
the enum to the codes the handlers actually construct, so a code added to one side and
not the other fails the build rather than shipping.
Published code values:
code |
HTTP status | Meaning | Retry? |
|---|---|---|---|
invalid_cursor |
400 | The cursor query parameter could not be decoded. |
No – restart pagination with no cursor. Do not resend the same value. |
invalid_site_id |
400 | The siteId filter is not a positive integer, or it names a site outside your grant. detail says which. |
No – fix the parameter against GET /v1/me’s grantedSiteIds. |
invalid_order_number |
400 | The order number in the path is not a positive integer. | No – fix the path. |
invalid_product_id |
400 | The productId path segment on GET /v1/products/{productId} does not match the SLO_/SLS_-prefixed shape. |
No – fix the path. |
invalid_service_type |
400 | GET /v1/products’s serviceType filter is not one of the values this API can order (STOCK or POD). |
No – fix the parameter. |
updated_since_not_supported |
400 | An updatedSince parameter was sent. Rejected rather than ignored, on purpose – see “No updatedSince / no change polling” above. |
No – remove the parameter; there is no change-polling mechanism to switch to. |
invalid_request |
400 | POST /v1/orders’s request body failed validation – malformed JSON, a missing or malformed field, an unknown key, or a body-level rule such as partnerLineId uniqueness or a misplaced shipTo – or GET /v1/orders/submissions’s status/limit filter is malformed. detail names the exact violation. |
No – fix the request. |
invalid_submission_id |
400 | The submissionId path segment on GET /v1/orders/submissions/{submissionId} is not a well-formed UUID. |
No – fix the path. |
invalid_quantity |
400 | The quantity on POST /v1/products/{productId}/quote is missing or not a positive integer. |
No – fix the request body. |
invalid_shipping_quote_request |
400 | POST /v1/shipping/quote’s body failed validation – malformed JSON, a missing or mistyped field, an unknown key at the top level or inside shipTo, or a weightOunces outside 0 < w <= 5000000. detail names the field. |
No – fix the request body. |
invalid_item_number |
400 | The itemNumber path segment on a proof route is not a positive integer. It is the line’s itemNumber from GET /v1/orders/{orderNumber}/items, never a Collaterate internal id. |
No – fix the path. |
invalid_token |
401 | The credential is missing, expired, or invalid. | No – mint a new token, retry once, then investigate the credential. |
insufficient_scope |
403 | The token lacks the scope required for this operation. | No – mint a token with the required scope. |
order_not_found |
404 | No such order, or one that exists outside your granted sites (see above). | No. |
order_item_not_found |
404 | No line matching (orderNumber, itemNumber) resolves for you – absent, outside your granted sites, or hidden from the customer, identically. |
No. |
product_not_found |
404 | No such product, or one that exists but belongs to a site outside your granted sites (same rule as order_not_found). |
No. |
quote_product_not_found |
404 | The product resolved in our own catalog but Collaterate could not price it. Rare. | No. |
template_not_found |
404 | GET /v1/products/{productId}/template found no Chili template for this product – not TEMPLATE-classified, TEMPLATE-classified with a legacy EDOC/CANVA vendor, or Chili-templated with no document configured. All three identically. |
No. |
submission_not_found |
404 | No submission exists with this id for your credential – whether it never existed or belongs to another partner, identically. | No. |
ordering_not_provisioned |
409 | POST /v1/orders’s siteId has no ordering user configured. This is set up per (partner, site) by staff at onboarding; a partner cannot provision it themselves. |
No – ask Partner Integrations to provision an ordering user for the site, then retry. |
partner_order_id_reused |
409 | POST /v1/orders’s partnerOrderId was already used, with a request body that does not match this one. Resending the identical body under the same partnerOrderId is safe and returns the original submission (see the operation description); reusing it for a materially different order is rejected instead of silently creating a second order. |
No – use a new partnerOrderId, or resend the exact original body to poll the existing submission. |
proof_not_pending_review |
409 | A proof approve or decline was sent for a line whose proofStatus is not PENDING_REVIEW – no proof yet, already decided, or a state this API does not recognise. detail says which. On a RETRY of a decision that already succeeded, this is the expected answer and means success. |
No – read the line’s current proofStatus from GET /v1/orders/{orderNumber}/items. |
order_item_cancelled |
409 | A proof approve or decline was sent for a cancelled line. It will never be produced, so its proof cannot be decided. | No. |
quote_invalid |
422 | Collaterate rejected the quote on business-validation grounds (e.g. quantity over the product’s configured maximum). detail carries Collaterate’s own validation message. |
No – fix the request body (e.g. reduce quantity). |
unknown_country_code |
422 | POST /v1/shipping/quote’s shipTo.country is not a country code Collaterate recognises. |
No – send a code Collaterate carries (US, CA, MX, …). |
unknown_state_code |
422 | POST /v1/shipping/quote’s shipTo.state is not a state or province of the country you sent. State codes are resolved WITHIN the country, since they are not unique across countries. |
No – fix the state, or the country it belongs to. |
shipping_quote_unavailable |
422 | POST /v1/shipping/quote produced no priced service at all for that destination and weight. detail carries Collaterate’s own reasons when it gave any – an invalid postal code for the country is the common one – and says so plainly when it did not. |
No – fix the destination. An empty result is never returned as a 200. |
denial_reason_not_available |
422 | POST .../proof/decline’s reason is well-formed but is not a denial reason this line’s proof offers – including a code that is valid on a different line, and UNKNOWN, which never is. detail lists what is valid here. |
No – pick a code from GET .../proof/denial-reasons for this line. |
rate_limit_exceeded |
429 | Rate limit or daily quota exceeded. | Yes – honor Retry-After. |
internal_error |
500 | Unexpected server-side failure. Detail is always the fixed string below, never the underlying error. | No, or with caution – if persistent, contact Partner Integrations. |
service_unavailable |
503 | Transient failure – typically the database path, but also a Lambda that crashed, timed out, or hit its own concurrency limit before any handler ran. | Yes, with backoff. |
Three statuses are answered by the API gateway before your request reaches application
code: 401 (authorizer denial), 429 (usage-plan throttle), and 503 (the Lambda
integration itself failed). All three are configured to return this same document with
the codes above, so one parser and one branch on code covers every error this API
produces. The 403 on GET /v1/ping is the sole exception anywhere in the API – it
comes from the edge firewall, which is not ours to shape, and carries no problem
document.
object
Always the literal string about:blank today; reserved by RFC 9457 for future use.
Short, human-readable summary of the HTTP status (e.g. “Not Found”).
The HTTP status code, repeated in the body for convenience.
Stable, machine-readable error identifier. Safe to branch on. See the table above.
Human-readable explanation. Do not parse this – it may be reworded without notice.
Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.
Example
{ "type": "about:blank", "title": "Not Found", "status": 404, "code": "product_not_found", "detail": "No product was found matching the request.", "requestId": "8f3c1e2a-..."}Your rate limit or daily quota was exceeded. Retryable – honor the Retry-After
header (seconds) before your next attempt.
Like the 401, this comes from the gateway’s usage plan rather than from application code, and is configured to carry the same problem document as every other error so a single parser covers the whole API.
RFC 9457 application/problem+json body. code is the published, stable,
machine-readable field to branch your integration logic on – detail is a
human-readable string that may be reworded over time and must not be parsed.
This table is the whole published set: every code this API can return appears below,
and nothing below is unreachable. test/docs/openapi-matches-reality.test.ts compares
the enum to the codes the handlers actually construct, so a code added to one side and
not the other fails the build rather than shipping.
Published code values:
code |
HTTP status | Meaning | Retry? |
|---|---|---|---|
invalid_cursor |
400 | The cursor query parameter could not be decoded. |
No – restart pagination with no cursor. Do not resend the same value. |
invalid_site_id |
400 | The siteId filter is not a positive integer, or it names a site outside your grant. detail says which. |
No – fix the parameter against GET /v1/me’s grantedSiteIds. |
invalid_order_number |
400 | The order number in the path is not a positive integer. | No – fix the path. |
invalid_product_id |
400 | The productId path segment on GET /v1/products/{productId} does not match the SLO_/SLS_-prefixed shape. |
No – fix the path. |
invalid_service_type |
400 | GET /v1/products’s serviceType filter is not one of the values this API can order (STOCK or POD). |
No – fix the parameter. |
updated_since_not_supported |
400 | An updatedSince parameter was sent. Rejected rather than ignored, on purpose – see “No updatedSince / no change polling” above. |
No – remove the parameter; there is no change-polling mechanism to switch to. |
invalid_request |
400 | POST /v1/orders’s request body failed validation – malformed JSON, a missing or malformed field, an unknown key, or a body-level rule such as partnerLineId uniqueness or a misplaced shipTo – or GET /v1/orders/submissions’s status/limit filter is malformed. detail names the exact violation. |
No – fix the request. |
invalid_submission_id |
400 | The submissionId path segment on GET /v1/orders/submissions/{submissionId} is not a well-formed UUID. |
No – fix the path. |
invalid_quantity |
400 | The quantity on POST /v1/products/{productId}/quote is missing or not a positive integer. |
No – fix the request body. |
invalid_shipping_quote_request |
400 | POST /v1/shipping/quote’s body failed validation – malformed JSON, a missing or mistyped field, an unknown key at the top level or inside shipTo, or a weightOunces outside 0 < w <= 5000000. detail names the field. |
No – fix the request body. |
invalid_item_number |
400 | The itemNumber path segment on a proof route is not a positive integer. It is the line’s itemNumber from GET /v1/orders/{orderNumber}/items, never a Collaterate internal id. |
No – fix the path. |
invalid_token |
401 | The credential is missing, expired, or invalid. | No – mint a new token, retry once, then investigate the credential. |
insufficient_scope |
403 | The token lacks the scope required for this operation. | No – mint a token with the required scope. |
order_not_found |
404 | No such order, or one that exists outside your granted sites (see above). | No. |
order_item_not_found |
404 | No line matching (orderNumber, itemNumber) resolves for you – absent, outside your granted sites, or hidden from the customer, identically. |
No. |
product_not_found |
404 | No such product, or one that exists but belongs to a site outside your granted sites (same rule as order_not_found). |
No. |
quote_product_not_found |
404 | The product resolved in our own catalog but Collaterate could not price it. Rare. | No. |
template_not_found |
404 | GET /v1/products/{productId}/template found no Chili template for this product – not TEMPLATE-classified, TEMPLATE-classified with a legacy EDOC/CANVA vendor, or Chili-templated with no document configured. All three identically. |
No. |
submission_not_found |
404 | No submission exists with this id for your credential – whether it never existed or belongs to another partner, identically. | No. |
ordering_not_provisioned |
409 | POST /v1/orders’s siteId has no ordering user configured. This is set up per (partner, site) by staff at onboarding; a partner cannot provision it themselves. |
No – ask Partner Integrations to provision an ordering user for the site, then retry. |
partner_order_id_reused |
409 | POST /v1/orders’s partnerOrderId was already used, with a request body that does not match this one. Resending the identical body under the same partnerOrderId is safe and returns the original submission (see the operation description); reusing it for a materially different order is rejected instead of silently creating a second order. |
No – use a new partnerOrderId, or resend the exact original body to poll the existing submission. |
proof_not_pending_review |
409 | A proof approve or decline was sent for a line whose proofStatus is not PENDING_REVIEW – no proof yet, already decided, or a state this API does not recognise. detail says which. On a RETRY of a decision that already succeeded, this is the expected answer and means success. |
No – read the line’s current proofStatus from GET /v1/orders/{orderNumber}/items. |
order_item_cancelled |
409 | A proof approve or decline was sent for a cancelled line. It will never be produced, so its proof cannot be decided. | No. |
quote_invalid |
422 | Collaterate rejected the quote on business-validation grounds (e.g. quantity over the product’s configured maximum). detail carries Collaterate’s own validation message. |
No – fix the request body (e.g. reduce quantity). |
unknown_country_code |
422 | POST /v1/shipping/quote’s shipTo.country is not a country code Collaterate recognises. |
No – send a code Collaterate carries (US, CA, MX, …). |
unknown_state_code |
422 | POST /v1/shipping/quote’s shipTo.state is not a state or province of the country you sent. State codes are resolved WITHIN the country, since they are not unique across countries. |
No – fix the state, or the country it belongs to. |
shipping_quote_unavailable |
422 | POST /v1/shipping/quote produced no priced service at all for that destination and weight. detail carries Collaterate’s own reasons when it gave any – an invalid postal code for the country is the common one – and says so plainly when it did not. |
No – fix the destination. An empty result is never returned as a 200. |
denial_reason_not_available |
422 | POST .../proof/decline’s reason is well-formed but is not a denial reason this line’s proof offers – including a code that is valid on a different line, and UNKNOWN, which never is. detail lists what is valid here. |
No – pick a code from GET .../proof/denial-reasons for this line. |
rate_limit_exceeded |
429 | Rate limit or daily quota exceeded. | Yes – honor Retry-After. |
internal_error |
500 | Unexpected server-side failure. Detail is always the fixed string below, never the underlying error. | No, or with caution – if persistent, contact Partner Integrations. |
service_unavailable |
503 | Transient failure – typically the database path, but also a Lambda that crashed, timed out, or hit its own concurrency limit before any handler ran. | Yes, with backoff. |
Three statuses are answered by the API gateway before your request reaches application
code: 401 (authorizer denial), 429 (usage-plan throttle), and 503 (the Lambda
integration itself failed). All three are configured to return this same document with
the codes above, so one parser and one branch on code covers every error this API
produces. The 403 on GET /v1/ping is the sole exception anywhere in the API – it
comes from the edge firewall, which is not ours to shape, and carries no problem
document.
object
Always the literal string about:blank today; reserved by RFC 9457 for future use.
Short, human-readable summary of the HTTP status (e.g. “Not Found”).
The HTTP status code, repeated in the body for convenience.
Stable, machine-readable error identifier. Safe to branch on. See the table above.
Human-readable explanation. Do not parse this – it may be reworded without notice.
Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.
Example
{ "type": "about:blank", "title": "Too Many Requests", "status": 429, "code": "rate_limit_exceeded", "detail": "The request rate limit has been exceeded. Retry after the indicated delay.", "requestId": "8f3c1e2a-..."}Headers
Section titled “Headers”Seconds to wait before retrying.
An unexpected server-side failure. detail is always the fixed string below and never
the underlying error – a database message quoted back to a partner is a disclosure,
so nothing is interpolated into it. The requestId resolves to the log entry that does
carry the cause; send it to Partner Integrations rather than guessing.
RFC 9457 application/problem+json body. code is the published, stable,
machine-readable field to branch your integration logic on – detail is a
human-readable string that may be reworded over time and must not be parsed.
This table is the whole published set: every code this API can return appears below,
and nothing below is unreachable. test/docs/openapi-matches-reality.test.ts compares
the enum to the codes the handlers actually construct, so a code added to one side and
not the other fails the build rather than shipping.
Published code values:
code |
HTTP status | Meaning | Retry? |
|---|---|---|---|
invalid_cursor |
400 | The cursor query parameter could not be decoded. |
No – restart pagination with no cursor. Do not resend the same value. |
invalid_site_id |
400 | The siteId filter is not a positive integer, or it names a site outside your grant. detail says which. |
No – fix the parameter against GET /v1/me’s grantedSiteIds. |
invalid_order_number |
400 | The order number in the path is not a positive integer. | No – fix the path. |
invalid_product_id |
400 | The productId path segment on GET /v1/products/{productId} does not match the SLO_/SLS_-prefixed shape. |
No – fix the path. |
invalid_service_type |
400 | GET /v1/products’s serviceType filter is not one of the values this API can order (STOCK or POD). |
No – fix the parameter. |
updated_since_not_supported |
400 | An updatedSince parameter was sent. Rejected rather than ignored, on purpose – see “No updatedSince / no change polling” above. |
No – remove the parameter; there is no change-polling mechanism to switch to. |
invalid_request |
400 | POST /v1/orders’s request body failed validation – malformed JSON, a missing or malformed field, an unknown key, or a body-level rule such as partnerLineId uniqueness or a misplaced shipTo – or GET /v1/orders/submissions’s status/limit filter is malformed. detail names the exact violation. |
No – fix the request. |
invalid_submission_id |
400 | The submissionId path segment on GET /v1/orders/submissions/{submissionId} is not a well-formed UUID. |
No – fix the path. |
invalid_quantity |
400 | The quantity on POST /v1/products/{productId}/quote is missing or not a positive integer. |
No – fix the request body. |
invalid_shipping_quote_request |
400 | POST /v1/shipping/quote’s body failed validation – malformed JSON, a missing or mistyped field, an unknown key at the top level or inside shipTo, or a weightOunces outside 0 < w <= 5000000. detail names the field. |
No – fix the request body. |
invalid_item_number |
400 | The itemNumber path segment on a proof route is not a positive integer. It is the line’s itemNumber from GET /v1/orders/{orderNumber}/items, never a Collaterate internal id. |
No – fix the path. |
invalid_token |
401 | The credential is missing, expired, or invalid. | No – mint a new token, retry once, then investigate the credential. |
insufficient_scope |
403 | The token lacks the scope required for this operation. | No – mint a token with the required scope. |
order_not_found |
404 | No such order, or one that exists outside your granted sites (see above). | No. |
order_item_not_found |
404 | No line matching (orderNumber, itemNumber) resolves for you – absent, outside your granted sites, or hidden from the customer, identically. |
No. |
product_not_found |
404 | No such product, or one that exists but belongs to a site outside your granted sites (same rule as order_not_found). |
No. |
quote_product_not_found |
404 | The product resolved in our own catalog but Collaterate could not price it. Rare. | No. |
template_not_found |
404 | GET /v1/products/{productId}/template found no Chili template for this product – not TEMPLATE-classified, TEMPLATE-classified with a legacy EDOC/CANVA vendor, or Chili-templated with no document configured. All three identically. |
No. |
submission_not_found |
404 | No submission exists with this id for your credential – whether it never existed or belongs to another partner, identically. | No. |
ordering_not_provisioned |
409 | POST /v1/orders’s siteId has no ordering user configured. This is set up per (partner, site) by staff at onboarding; a partner cannot provision it themselves. |
No – ask Partner Integrations to provision an ordering user for the site, then retry. |
partner_order_id_reused |
409 | POST /v1/orders’s partnerOrderId was already used, with a request body that does not match this one. Resending the identical body under the same partnerOrderId is safe and returns the original submission (see the operation description); reusing it for a materially different order is rejected instead of silently creating a second order. |
No – use a new partnerOrderId, or resend the exact original body to poll the existing submission. |
proof_not_pending_review |
409 | A proof approve or decline was sent for a line whose proofStatus is not PENDING_REVIEW – no proof yet, already decided, or a state this API does not recognise. detail says which. On a RETRY of a decision that already succeeded, this is the expected answer and means success. |
No – read the line’s current proofStatus from GET /v1/orders/{orderNumber}/items. |
order_item_cancelled |
409 | A proof approve or decline was sent for a cancelled line. It will never be produced, so its proof cannot be decided. | No. |
quote_invalid |
422 | Collaterate rejected the quote on business-validation grounds (e.g. quantity over the product’s configured maximum). detail carries Collaterate’s own validation message. |
No – fix the request body (e.g. reduce quantity). |
unknown_country_code |
422 | POST /v1/shipping/quote’s shipTo.country is not a country code Collaterate recognises. |
No – send a code Collaterate carries (US, CA, MX, …). |
unknown_state_code |
422 | POST /v1/shipping/quote’s shipTo.state is not a state or province of the country you sent. State codes are resolved WITHIN the country, since they are not unique across countries. |
No – fix the state, or the country it belongs to. |
shipping_quote_unavailable |
422 | POST /v1/shipping/quote produced no priced service at all for that destination and weight. detail carries Collaterate’s own reasons when it gave any – an invalid postal code for the country is the common one – and says so plainly when it did not. |
No – fix the destination. An empty result is never returned as a 200. |
denial_reason_not_available |
422 | POST .../proof/decline’s reason is well-formed but is not a denial reason this line’s proof offers – including a code that is valid on a different line, and UNKNOWN, which never is. detail lists what is valid here. |
No – pick a code from GET .../proof/denial-reasons for this line. |
rate_limit_exceeded |
429 | Rate limit or daily quota exceeded. | Yes – honor Retry-After. |
internal_error |
500 | Unexpected server-side failure. Detail is always the fixed string below, never the underlying error. | No, or with caution – if persistent, contact Partner Integrations. |
service_unavailable |
503 | Transient failure – typically the database path, but also a Lambda that crashed, timed out, or hit its own concurrency limit before any handler ran. | Yes, with backoff. |
Three statuses are answered by the API gateway before your request reaches application
code: 401 (authorizer denial), 429 (usage-plan throttle), and 503 (the Lambda
integration itself failed). All three are configured to return this same document with
the codes above, so one parser and one branch on code covers every error this API
produces. The 403 on GET /v1/ping is the sole exception anywhere in the API – it
comes from the edge firewall, which is not ours to shape, and carries no problem
document.
object
Always the literal string about:blank today; reserved by RFC 9457 for future use.
Short, human-readable summary of the HTTP status (e.g. “Not Found”).
The HTTP status code, repeated in the body for convenience.
Stable, machine-readable error identifier. Safe to branch on. See the table above.
Human-readable explanation. Do not parse this – it may be reworded without notice.
Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.
Example
{ "type": "about:blank", "title": "Internal Server Error", "status": 500, "code": "internal_error", "detail": "An unexpected error occurred.", "requestId": "8f3c1e2a-..."}Transient failure in the service or the database it reads from (for example, the Aurora reader could not be reached in time), or the request never reached application code at all because the Lambda behind it crashed, timed out, or hit its own concurrency limit. Retryable, with backoff.
RFC 9457 application/problem+json body. code is the published, stable,
machine-readable field to branch your integration logic on – detail is a
human-readable string that may be reworded over time and must not be parsed.
This table is the whole published set: every code this API can return appears below,
and nothing below is unreachable. test/docs/openapi-matches-reality.test.ts compares
the enum to the codes the handlers actually construct, so a code added to one side and
not the other fails the build rather than shipping.
Published code values:
code |
HTTP status | Meaning | Retry? |
|---|---|---|---|
invalid_cursor |
400 | The cursor query parameter could not be decoded. |
No – restart pagination with no cursor. Do not resend the same value. |
invalid_site_id |
400 | The siteId filter is not a positive integer, or it names a site outside your grant. detail says which. |
No – fix the parameter against GET /v1/me’s grantedSiteIds. |
invalid_order_number |
400 | The order number in the path is not a positive integer. | No – fix the path. |
invalid_product_id |
400 | The productId path segment on GET /v1/products/{productId} does not match the SLO_/SLS_-prefixed shape. |
No – fix the path. |
invalid_service_type |
400 | GET /v1/products’s serviceType filter is not one of the values this API can order (STOCK or POD). |
No – fix the parameter. |
updated_since_not_supported |
400 | An updatedSince parameter was sent. Rejected rather than ignored, on purpose – see “No updatedSince / no change polling” above. |
No – remove the parameter; there is no change-polling mechanism to switch to. |
invalid_request |
400 | POST /v1/orders’s request body failed validation – malformed JSON, a missing or malformed field, an unknown key, or a body-level rule such as partnerLineId uniqueness or a misplaced shipTo – or GET /v1/orders/submissions’s status/limit filter is malformed. detail names the exact violation. |
No – fix the request. |
invalid_submission_id |
400 | The submissionId path segment on GET /v1/orders/submissions/{submissionId} is not a well-formed UUID. |
No – fix the path. |
invalid_quantity |
400 | The quantity on POST /v1/products/{productId}/quote is missing or not a positive integer. |
No – fix the request body. |
invalid_shipping_quote_request |
400 | POST /v1/shipping/quote’s body failed validation – malformed JSON, a missing or mistyped field, an unknown key at the top level or inside shipTo, or a weightOunces outside 0 < w <= 5000000. detail names the field. |
No – fix the request body. |
invalid_item_number |
400 | The itemNumber path segment on a proof route is not a positive integer. It is the line’s itemNumber from GET /v1/orders/{orderNumber}/items, never a Collaterate internal id. |
No – fix the path. |
invalid_token |
401 | The credential is missing, expired, or invalid. | No – mint a new token, retry once, then investigate the credential. |
insufficient_scope |
403 | The token lacks the scope required for this operation. | No – mint a token with the required scope. |
order_not_found |
404 | No such order, or one that exists outside your granted sites (see above). | No. |
order_item_not_found |
404 | No line matching (orderNumber, itemNumber) resolves for you – absent, outside your granted sites, or hidden from the customer, identically. |
No. |
product_not_found |
404 | No such product, or one that exists but belongs to a site outside your granted sites (same rule as order_not_found). |
No. |
quote_product_not_found |
404 | The product resolved in our own catalog but Collaterate could not price it. Rare. | No. |
template_not_found |
404 | GET /v1/products/{productId}/template found no Chili template for this product – not TEMPLATE-classified, TEMPLATE-classified with a legacy EDOC/CANVA vendor, or Chili-templated with no document configured. All three identically. |
No. |
submission_not_found |
404 | No submission exists with this id for your credential – whether it never existed or belongs to another partner, identically. | No. |
ordering_not_provisioned |
409 | POST /v1/orders’s siteId has no ordering user configured. This is set up per (partner, site) by staff at onboarding; a partner cannot provision it themselves. |
No – ask Partner Integrations to provision an ordering user for the site, then retry. |
partner_order_id_reused |
409 | POST /v1/orders’s partnerOrderId was already used, with a request body that does not match this one. Resending the identical body under the same partnerOrderId is safe and returns the original submission (see the operation description); reusing it for a materially different order is rejected instead of silently creating a second order. |
No – use a new partnerOrderId, or resend the exact original body to poll the existing submission. |
proof_not_pending_review |
409 | A proof approve or decline was sent for a line whose proofStatus is not PENDING_REVIEW – no proof yet, already decided, or a state this API does not recognise. detail says which. On a RETRY of a decision that already succeeded, this is the expected answer and means success. |
No – read the line’s current proofStatus from GET /v1/orders/{orderNumber}/items. |
order_item_cancelled |
409 | A proof approve or decline was sent for a cancelled line. It will never be produced, so its proof cannot be decided. | No. |
quote_invalid |
422 | Collaterate rejected the quote on business-validation grounds (e.g. quantity over the product’s configured maximum). detail carries Collaterate’s own validation message. |
No – fix the request body (e.g. reduce quantity). |
unknown_country_code |
422 | POST /v1/shipping/quote’s shipTo.country is not a country code Collaterate recognises. |
No – send a code Collaterate carries (US, CA, MX, …). |
unknown_state_code |
422 | POST /v1/shipping/quote’s shipTo.state is not a state or province of the country you sent. State codes are resolved WITHIN the country, since they are not unique across countries. |
No – fix the state, or the country it belongs to. |
shipping_quote_unavailable |
422 | POST /v1/shipping/quote produced no priced service at all for that destination and weight. detail carries Collaterate’s own reasons when it gave any – an invalid postal code for the country is the common one – and says so plainly when it did not. |
No – fix the destination. An empty result is never returned as a 200. |
denial_reason_not_available |
422 | POST .../proof/decline’s reason is well-formed but is not a denial reason this line’s proof offers – including a code that is valid on a different line, and UNKNOWN, which never is. detail lists what is valid here. |
No – pick a code from GET .../proof/denial-reasons for this line. |
rate_limit_exceeded |
429 | Rate limit or daily quota exceeded. | Yes – honor Retry-After. |
internal_error |
500 | Unexpected server-side failure. Detail is always the fixed string below, never the underlying error. | No, or with caution – if persistent, contact Partner Integrations. |
service_unavailable |
503 | Transient failure – typically the database path, but also a Lambda that crashed, timed out, or hit its own concurrency limit before any handler ran. | Yes, with backoff. |
Three statuses are answered by the API gateway before your request reaches application
code: 401 (authorizer denial), 429 (usage-plan throttle), and 503 (the Lambda
integration itself failed). All three are configured to return this same document with
the codes above, so one parser and one branch on code covers every error this API
produces. The 403 on GET /v1/ping is the sole exception anywhere in the API – it
comes from the edge firewall, which is not ours to shape, and carries no problem
document.
object
Always the literal string about:blank today; reserved by RFC 9457 for future use.
Short, human-readable summary of the HTTP status (e.g. “Not Found”).
The HTTP status code, repeated in the body for convenience.
Stable, machine-readable error identifier. Safe to branch on. See the table above.
Human-readable explanation. Do not parse this – it may be reworded without notice.
Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.
Example
{ "type": "about:blank", "title": "Service Unavailable", "status": 503, "code": "service_unavailable", "detail": "The service is temporarily unavailable. Please retry.", "requestId": "8f3c1e2a-..."}