Errors
Failures come back in the same envelope as successes, carrying a stable code to branch on and a human message for your logs.
Shape
{
"data": null,
"error": {
"code": "CREDIT_LIMIT_EXCEEDED",
"message": "This order would exceed the account's credit limit.",
"details": {
"outstandingCents": 128400,
"limitCents": 150000,
"availableCents": 21600,
"orderCents": 41700
}
}
}code is the contract - stable, enumerated, and additive-only. message is written for a person and may change at any time, so never branch on it. details is present only where there is something structured worth adding, and its shape follows the code.
Handling
Branch on the code, and always keep a default arm - we may add codes within v1, so an unfamiliar one is expected rather than exceptional. When you do not recognise a code, fall back to the status: 4xx is something to fix in the request, 5xx is ours and may be retried.
const res = await fetch(url, init);
const body = await res.json();
if (body.error) {
switch (body.error.code) {
case "QUOTE_EXPIRED":
return reprice();
case "CREDIT_LIMIT_EXCEEDED":
return notifyAccounts(body.error.details);
case "RATE_LIMITED":
return retryAfter(Number(res.headers.get("Retry-After") ?? 5));
default:
// Always a default arm: the code list is additive, so an unfamiliar
// code is expected rather than exceptional. Fall back to the status.
if (res.status >= 500) return retryWithBackoff();
throw new Error(`${body.error.code}: ${body.error.message}`);
}
}Retrying POST /orders is safe as long as you reuse the same Idempotency-Key. See orders.
Code reference
Every code the API publishes. This list may grow; it will not shrink or be renamed without a new API version.
| Code | Status | Meaning |
|---|---|---|
ARTWORK_NOT_ALLOWED | 400 | A line named artwork that this account did not upload. Only URLs returned by your own |
ARTWORK_REQUIRED | 400 | A line carried no artwork for a product that has to print something. The message names the line.
|
IDEMPOTENCY_KEY_REQUIRED | 400 |
It is required on the one irreversible call in the API. Send a unique value per order attempt. |
INVALID_QUANTITY | 400 | The quantity is outside what the product allows. |
MAT_BELOW_MINIMUM | 400 | A mat border falls under the studio minimum on at least one side. The board is unmakeable, not merely unusual - too narrow a border has nothing to hold the artwork. Widen every side to at least the minimum, or order the piece unmatted. |
PRODUCT_UNAVAILABLE | 400 | A material named on a line is not available for that product - most often a paper id used on a canvas line, or a moulding the studio has withdrawn. Re-read |
PRODUCT_UNSUPPORTED | 400 | The
|
QUOTE_ENVIRONMENT_MISMATCH | 400 | The quote was priced by a key from the other environment. A test-priced basket can never become a live job, nor the reverse. Re-price with the key you intend to order with. |
SHIPPING_ADDRESS_INCOMPLETE | 400 | The destination is not deliverable as given.
|
SHIPPING_UNAVAILABLE | 400 | The delivery method is not available for this basket - or you omitted
|
VALIDATION_ERROR | 400 | The request body did not match the schema. Unknown fields are ignored rather than rejected, so a misspelled field name shows up as a missing value elsewhere, not as a complaint about the typo. |
UNAUTHORIZED | 401 | The key is missing, malformed, revoked, expired, or belongs to a disabled account. Deliberately indistinguishable - the response will not tell you which. If the key worked yesterday, contact the studio. |
CREDIT_LIMIT_EXCEEDED | 402 | The order would take the account past its credit limit.
|
FORBIDDEN | 403 | The key authenticated but lacks the scope this endpoint needs. Scopes are fixed when a key is minted. Ask the studio for a key with the scope. |
NOT_FOUND | 404 | No such quote or order for this account. Another partner's identifier answers 404, not 403 - the API will not confirm that someone else's record exists. A malformed identifier answers 404 too. |
IDEMPOTENCY_KEY_REUSED | 409 | The key was already used for a different request body, or the first request with it is still in flight. A retry of the same request replays the original response instead. If the first attempt is still running, the response carries |
ORDER_NOT_CANCELLABLE | 409 | The studio has already done something irreversible - shipped it, printed it, or recorded money against it. The message says which. Contact the studio. |
QUOTE_ALREADY_ORDERED | 409 | That quote has already produced an order. A quote is single-use, and stays spent even if the order it produced was later cancelled.
|
QUOTE_EXPIRED | 409 | The quote lapsed before it was ordered. Quotes hold for 24 hours.
|
RATE_LIMITED | 429 | Too many requests on this key for the endpoint's bucket. Honour |
INTERNAL_ERROR | 500 | Something failed on our side. Safe to retry with backoff. On |