Skip to content

Submit an order

POST
/orders
curl --request POST \
--url https://api.partners.collaterate.com/v1/orders \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "siteId": 7, "partnerOrderId": "acme-po-4471", "shipTo": { "name": "Jane Doe", "address1": "123 Main St", "city": "Springfield", "state": "IL", "postalCode": "62701", "country": "US" }, "lines": [ { "productId": "SLO_918273", "partnerLineId": "acme-line-1", "quantity": 500, "variantId": 55012 } ] }'

Submits an order for one of your granted sites. Submission is asynchronous underneath, synchronous-feeling on top: this handler validates the request, resolves the site’s configured ordering user, durably records the submission, and enqueues it for a worker to send on to Collaterate – then waits up to 20 seconds for a terminal outcome before answering. Measured wall time for the monolith’s own order-creation call is median 5-8s and p95 10-20s, so the large majority of calls return completed or failed in this one round trip, indistinguishable from a synchronous API.

One response shape, always the Submission resource – whether this call happened to catch the terminal state or not:

{ "submissionId": "…", "status": "completed", "order": { "orderNumber": , "status": "…", "totalPrice": , "projectId": , "lines": [  ] } }
{ "submissionId": "…", "status": "queued" }
{ "submissionId": "…", "status": "failed", "errors": [ { "code": "order_rejected", "detail": "…" } ] }

If the wait window elapses before a terminal state is reached, the response still carries 201/200 and the submission resource, now with status: queued or status: processing, a Retry-After: 2 header, and a Location header naming GET /v1/orders/submissions/{submissionId} – poll that endpoint, which returns the identical resource shape, until status is completed or failed. This is deliberately the same shape and the same fields either way, so the polling path is never a second, rarely-exercised contract that can drift from the fast path.

partnerOrderId is the idempotency key. Resubmitting the exact same request body with a partnerOrderId you already used returns the ORIGINAL submission – 200, not 201 – at whatever stage it has reached, including mid-flight. Resubmitting a DIFFERENT body under a partnerOrderId you already used is rejected as 409 partner_order_id_reused rather than silently creating a second order or silently discarding the new request. 201 means this call is the one that created the record; 200 means it already existed.

Every order needs a shipTo, at order level or on every line – never both. An order-level shipTo.email becomes the order’s ship-notification address; there is no per-line equivalent, so a line-level shipTo.email is rejected.

Prices are never sent. There is no unitPrice field anywhere in this request: Collaterate always prices from its own catalog and site rules, and the priced result comes back on the order object once the submission completes.

Media typeapplication/json

The body of POST /v1/orders. There is deliberately no price field anywhere in this shape – Collaterate always prices from its own catalog and site rules, and the priced result comes back on the submission’s order once it completes. Unknown top-level keys are rejected as invalid_request, as is a body over 256KB.

object
siteId
required

Must be one of the sites granted to you (see GET /v1/me).

integer
partnerOrderId
required

Your own identifier for this order – the idempotency key. Becomes the monolith’s extOrderId. Printable ASCII only. See the operation description for the exact resubmission rules.

string
<= 100 characters
shipTo

Order-level ship-to. Mutually exclusive with a shipTo on every line – exactly one of the two must be present, never neither and never both.

object
name
required
string
company
string
address1
required
string
address2
string
city
required
string
state
required
string
postalCode
required
string
country
required

ISO 3166-1 alpha-2, e.g. US.

string
>= 2 characters <= 2 characters
phone
string
email

Order-level only – see the schema description.

string
lines
required
Array<object>
>= 1 items <= 100 items

One line of an order submission.

object
productId
required

The catalog identifier from GET /v1/products. Prefixed SLO_ for a product the site owns, SLS_ for one shared to it – an entry absent from that endpoint is rejected here.

string
/^(SLO|SLS)_\d{1,18}$/
partnerLineId
required

Your own identifier for this line, echoed back on the submitted order’s lines[].partnerLineId and in any per-line error. Must be unique within the request. Printable ASCII only.

string
<= 100 characters
quantity
required

The quantity to produce for this line.

integer
>= 1
variantId

Required for a STOCK product (see GET /v1/products); omit for POD, which has no variant until the job is configured.

integer
sku
string
jobName

Customer-facing name for this job, if any.

string
<= 200 characters
artworkUrl
string
<= 2000 characters
templateVariables

Flat string-to-string map, up to 50 entries, consumed by template-based artwork. Reserved JavaScript prototype keys (__proto__, constructor, prototype) are rejected as invalid_request.

object
key
additional properties
string
shipTo

Line-level ship-to. Present only when every line carries its own shipTo and the order level carries none – see ShipTo’s description.

object
name
required
string
company
string
address1
required
string
address2
string
city
required
string
state
required
string
postalCode
required
string
country
required

ISO 3166-1 alpha-2, e.g. US.

string
>= 2 characters <= 2 characters
phone
string
email

Order-level only – see the schema description.

string
Examples
Exampledefault
{
"siteId": 7,
"partnerOrderId": "acme-po-4471",
"shipTo": {
"name": "Jane Doe",
"address1": "123 Main St",
"city": "Springfield",
"state": "IL",
"postalCode": "62701",
"country": "US"
},
"lines": [
{
"productId": "SLO_918273",
"partnerLineId": "acme-line-1",
"quantity": 500,
"variantId": 55012
}
]
}

This partnerOrderId was already used, with the identical request body. Returns the ORIGINAL submission, at whatever stage it has reached – never a second order.

Media typeapplication/json

The resource both POST /v1/orders and GET /v1/orders/submissions/{submissionId} return – the identical shape from both, described in full on the create operation.

object
submissionId
required

Opaque id to poll with. Belongs to the partner that created it – see submission_not_found.

string format: uuid
status
required

queued and processing are transient; poll again. completed and failed are terminal and never revert – completed populates order, failed populates errors.

string
Allowed values: queued processing completed failed
order

Present only when status is completed.

object
orderNumber
required
integer
status
required
string
totalPrice
required
number
projectId
required
integer
lines
required
Array<object>

One priced line of a completed submission’s order, in Submission.order.lines.

Every money field here is a whole-line amount and a JSON number. None is a per-unit price, so never multiply by quantity. Treat this object as a receipt: for money you intend to reconcile against, read GET /v1/orders/{orderNumber}/items, which publishes decimal strings and the authoritative post-adjustment total.

object
partnerLineId
required

Echoes the partnerLineId you submitted, so you can match this line back to your request.

string
quantity
required

The quantity being produced for this line. Published so the amounts below can be interpreted at all - they are line totals, not unit prices.

integer
lineListTotal
required

The whole line’s extended list amount, before discounts and adjustments. Priced by Collaterate; you never send a price on submission. Called unitPrice before 2026-07-29, which was wrong - it has never been a per-unit figure. Same value as OrderItem.lineListTotal, which publishes it as an exact decimal string.

number
lineTotalBeforeAdjustments
required

The whole line’s total before adjustments, and deliberately not called lineTotal because it is a different number from OrderItem.lineTotal, which is measured after them. Collaterate reports the ORDER total after adjustments while reporting each LINE before them, so on an adjusted line these will not sum to SubmittedOrder.totalPrice. Reconcile against GET /v1/orders/{orderNumber}/items.

number
errors

Present only when status is failed.

Array<object>

One reason a submission was rejected, in Submission.errors. code is our own stable identifier, mapped from whatever Collaterate rejected the order for – not a Problem.code: a SubmissionError describes why an accepted, well-formed request could not be turned into an order, which is a different kind of failure from a malformed request or an authentication problem.

object
code
required

Stable identifier for this rejection reason. order_rejected is the value in practice today – one entry per problem Collaterate reported, per-line where the rejection names a line.

string
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
field

The request field this rejection concerns, if it names one.

string
partnerLineId

The partnerLineId this rejection concerns, if it is about one line rather than the whole order.

string
Example
{
"status": "queued",
"errors": [
{
"code": "order_rejected"
}
]
}

A new submission was created (this partnerOrderId had not been used before). status may already be terminal, or may still be queued/processing if the wait window elapsed first – see the operation description for how to tell and what to do next.

Media typeapplication/json

The resource both POST /v1/orders and GET /v1/orders/submissions/{submissionId} return – the identical shape from both, described in full on the create operation.

object
submissionId
required

Opaque id to poll with. Belongs to the partner that created it – see submission_not_found.

string format: uuid
status
required

queued and processing are transient; poll again. completed and failed are terminal and never revert – completed populates order, failed populates errors.

string
Allowed values: queued processing completed failed
order

Present only when status is completed.

object
orderNumber
required
integer
status
required
string
totalPrice
required
number
projectId
required
integer
lines
required
Array<object>

One priced line of a completed submission’s order, in Submission.order.lines.

Every money field here is a whole-line amount and a JSON number. None is a per-unit price, so never multiply by quantity. Treat this object as a receipt: for money you intend to reconcile against, read GET /v1/orders/{orderNumber}/items, which publishes decimal strings and the authoritative post-adjustment total.

object
partnerLineId
required

Echoes the partnerLineId you submitted, so you can match this line back to your request.

string
quantity
required

The quantity being produced for this line. Published so the amounts below can be interpreted at all - they are line totals, not unit prices.

integer
lineListTotal
required

The whole line’s extended list amount, before discounts and adjustments. Priced by Collaterate; you never send a price on submission. Called unitPrice before 2026-07-29, which was wrong - it has never been a per-unit figure. Same value as OrderItem.lineListTotal, which publishes it as an exact decimal string.

number
lineTotalBeforeAdjustments
required

The whole line’s total before adjustments, and deliberately not called lineTotal because it is a different number from OrderItem.lineTotal, which is measured after them. Collaterate reports the ORDER total after adjustments while reporting each LINE before them, so on an adjusted line these will not sum to SubmittedOrder.totalPrice. Reconcile against GET /v1/orders/{orderNumber}/items.

number
errors

Present only when status is failed.

Array<object>

One reason a submission was rejected, in Submission.errors. code is our own stable identifier, mapped from whatever Collaterate rejected the order for – not a Problem.code: a SubmissionError describes why an accepted, well-formed request could not be turned into an order, which is a different kind of failure from a malformed request or an authentication problem.

object
code
required

Stable identifier for this rejection reason. order_rejected is the value in practice today – one entry per problem Collaterate reported, per-line where the rejection names a line.

string
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
field

The request field this rejection concerns, if it names one.

string
partnerLineId

The partnerLineId this rejection concerns, if it is about one line rather than the whole order.

string
Example
{
"status": "queued",
"errors": [
{
"code": "order_rejected"
}
]
}

The request body failed validation, or siteId is not one of your granted sites.

Media typeapplication/problem+json

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 shipToor 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_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.
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.
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.
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).
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
type
required

Always the literal string about:blank today; reserved by RFC 9457 for future use.

string
Allowed value: about:blank
title
required

Short, human-readable summary of the HTTP status (e.g. “Not Found”).

string
status
required

The HTTP status code, repeated in the body for convenience.

integer
code
required

Stable, machine-readable error identifier. Safe to branch on. See the table above.

string
Allowed values: invalid_cursor invalid_site_id invalid_order_number invalid_product_id invalid_quantity invalid_service_type updated_since_not_supported invalid_request invalid_submission_id invalid_token insufficient_scope order_not_found product_not_found quote_product_not_found submission_not_found ordering_not_provisioned partner_order_id_reused quote_invalid rate_limit_exceeded internal_error service_unavailable
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
requestId
required

Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.

string
Examples
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"code": "invalid_request",
"detail": "\"lines\" must not be empty",
"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.

Media typeapplication/problem+json

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 shipToor 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_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.
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.
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.
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).
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
type
required

Always the literal string about:blank today; reserved by RFC 9457 for future use.

string
Allowed value: about:blank
title
required

Short, human-readable summary of the HTTP status (e.g. “Not Found”).

string
status
required

The HTTP status code, repeated in the body for convenience.

integer
code
required

Stable, machine-readable error identifier. Safe to branch on. See the table above.

string
Allowed values: invalid_cursor invalid_site_id invalid_order_number invalid_product_id invalid_quantity invalid_service_type updated_since_not_supported invalid_request invalid_submission_id invalid_token insufficient_scope order_not_found product_not_found quote_product_not_found submission_not_found ordering_not_provisioned partner_order_id_reused quote_invalid rate_limit_exceeded internal_error service_unavailable
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
requestId
required

Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.

string
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.

Media typeapplication/problem+json

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 shipToor 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_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.
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.
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.
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).
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
type
required

Always the literal string about:blank today; reserved by RFC 9457 for future use.

string
Allowed value: about:blank
title
required

Short, human-readable summary of the HTTP status (e.g. “Not Found”).

string
status
required

The HTTP status code, repeated in the body for convenience.

integer
code
required

Stable, machine-readable error identifier. Safe to branch on. See the table above.

string
Allowed values: invalid_cursor invalid_site_id invalid_order_number invalid_product_id invalid_quantity invalid_service_type updated_since_not_supported invalid_request invalid_submission_id invalid_token insufficient_scope order_not_found product_not_found quote_product_not_found submission_not_found ordering_not_provisioned partner_order_id_reused quote_invalid rate_limit_exceeded internal_error service_unavailable
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
requestId
required

Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.

string
Example
{
"type": "about:blank",
"title": "Forbidden",
"status": 403,
"code": "insufficient_scope",
"detail": "This request requires the \"partner-api/orders:read\" scope.",
"requestId": "8f3c1e2a-..."
}

POST /v1/orders could not proceed as requested. Not retryable as-is – see code for which of the two distinct causes applies and what to do about it.

Media typeapplication/problem+json

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 shipToor 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_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.
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.
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.
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).
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
type
required

Always the literal string about:blank today; reserved by RFC 9457 for future use.

string
Allowed value: about:blank
title
required

Short, human-readable summary of the HTTP status (e.g. “Not Found”).

string
status
required

The HTTP status code, repeated in the body for convenience.

integer
code
required

Stable, machine-readable error identifier. Safe to branch on. See the table above.

string
Allowed values: invalid_cursor invalid_site_id invalid_order_number invalid_product_id invalid_quantity invalid_service_type updated_since_not_supported invalid_request invalid_submission_id invalid_token insufficient_scope order_not_found product_not_found quote_product_not_found submission_not_found ordering_not_provisioned partner_order_id_reused quote_invalid rate_limit_exceeded internal_error service_unavailable
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
requestId
required

Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.

string
Examples
{
"type": "about:blank",
"title": "Conflict",
"status": 409,
"code": "ordering_not_provisioned",
"detail": "No ordering user is provisioned for site 7. Grant the site an ordering user in the admin UI, then retry.",
"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.

Media typeapplication/problem+json

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 shipToor 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_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.
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.
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.
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).
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
type
required

Always the literal string about:blank today; reserved by RFC 9457 for future use.

string
Allowed value: about:blank
title
required

Short, human-readable summary of the HTTP status (e.g. “Not Found”).

string
status
required

The HTTP status code, repeated in the body for convenience.

integer
code
required

Stable, machine-readable error identifier. Safe to branch on. See the table above.

string
Allowed values: invalid_cursor invalid_site_id invalid_order_number invalid_product_id invalid_quantity invalid_service_type updated_since_not_supported invalid_request invalid_submission_id invalid_token insufficient_scope order_not_found product_not_found quote_product_not_found submission_not_found ordering_not_provisioned partner_order_id_reused quote_invalid rate_limit_exceeded internal_error service_unavailable
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
requestId
required

Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.

string
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-..."
}
Retry-After
integer

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.

Media typeapplication/problem+json

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 shipToor 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_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.
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.
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.
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).
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
type
required

Always the literal string about:blank today; reserved by RFC 9457 for future use.

string
Allowed value: about:blank
title
required

Short, human-readable summary of the HTTP status (e.g. “Not Found”).

string
status
required

The HTTP status code, repeated in the body for convenience.

integer
code
required

Stable, machine-readable error identifier. Safe to branch on. See the table above.

string
Allowed values: invalid_cursor invalid_site_id invalid_order_number invalid_product_id invalid_quantity invalid_service_type updated_since_not_supported invalid_request invalid_submission_id invalid_token insufficient_scope order_not_found product_not_found quote_product_not_found submission_not_found ordering_not_provisioned partner_order_id_reused quote_invalid rate_limit_exceeded internal_error service_unavailable
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
requestId
required

Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.

string
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.

Media typeapplication/problem+json

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 shipToor 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_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.
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.
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.
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).
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
type
required

Always the literal string about:blank today; reserved by RFC 9457 for future use.

string
Allowed value: about:blank
title
required

Short, human-readable summary of the HTTP status (e.g. “Not Found”).

string
status
required

The HTTP status code, repeated in the body for convenience.

integer
code
required

Stable, machine-readable error identifier. Safe to branch on. See the table above.

string
Allowed values: invalid_cursor invalid_site_id invalid_order_number invalid_product_id invalid_quantity invalid_service_type updated_since_not_supported invalid_request invalid_submission_id invalid_token insufficient_scope order_not_found product_not_found quote_product_not_found submission_not_found ordering_not_provisioned partner_order_id_reused quote_invalid rate_limit_exceeded internal_error service_unavailable
detail
required

Human-readable explanation. Do not parse this – it may be reworded without notice.

string
requestId
required

Echoes the request’s id. Include this when contacting Partner Integrations about a specific failed call – it resolves directly to one log entry.

string
Example
{
"type": "about:blank",
"title": "Service Unavailable",
"status": 503,
"code": "service_unavailable",
"detail": "The service is temporarily unavailable. Please retry.",
"requestId": "8f3c1e2a-..."
}