Errors
The error object, error types and HTTP statuses, which errors to retry, and how to debug with request ids.
Errors use conventional HTTP statuses (2xx success, 4xx a problem with the request, 5xx a problem on our side) and always have the same JSON body:
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "Invalid items[0].quantity: must be greater than or equal to 1.",
"param": "items[0].quantity",
"doc_url": "https://swaglists.com/docs/errors#parameter_invalid",
"request_id": "req_01k8x4b2q7w9e1r3t5y7u9i1o3",
"errors": [
{ "param": "items[0].quantity", "code": "parameter_invalid", "message": "Invalid items[0].quantity: must be greater than or equal to 1." },
{ "param": "recipient.country_code", "code": "parameter_invalid", "message": "Invalid recipient.country_code: must be an ISO 3166-1 alpha-2 country code." }
]
}
}| Field | Meaning |
|---|---|
type |
Category of the error (table below). |
code |
Stable, machine-readable code. Branch on this, not on the message. Every code is documented in the error reference. |
message |
Human-readable explanation for developers (not meant for your end customers; wording may change). |
param |
The parameter at fault, as a path into your request: recipient.postal_code, items[1].sku, designs[0].layers[0].url. |
doc_url |
Link to the code's documentation. |
request_id |
Id of the request, also in the Request-Id response header. Include it when you contact support. |
errors |
Validation errors only: every problem found in the request (at most 50), each with param, code and message. The first entry is the top-level code / param / message. Fix them all before you retry — a 100-line order with five bad SKUs needs one round trip, not five. |
Some errors carry extra fields:
| Field | Sent with | Meaning |
|---|---|---|
allowed_values |
a value outside a fixed set (e.g. unknown_placement, an enum such as shipping_method or the status filter) |
The values that are accepted. |
available_shipping_methods |
shipping_method_unavailable |
The shipping methods you can use for this destination and these items. |
max_length |
a field that is too long for every way we can fulfill the order | The longest value (in characters) that can be accepted — shorten the field to at most this. |
max_quantity |
a quantity that is too large | The largest quantity that can be accepted. |
existing_id |
duplicate_external_id |
The id of the object that already has this external id. |
required_amount / available_amount / currency |
insufficient_funds |
Credits needed and available (cents). |
required_scope |
insufficient_scope |
The scope the API key is missing. |
New fields may be added to the error object at any time — ignore the ones you don't use.
Types and statuses
| Status | type |
Typical codes | Retry? |
|---|---|---|---|
| 400 | invalid_request_error |
parameter_invalid, resource_missing (a referenced object does not exist), invalid_json, business rules like variant_unavailable |
No — fix the request. |
| 401 | authentication_error |
missing_api_key, invalid_api_key |
No — fix the key. |
| 402 | insufficient_funds_error |
insufficient_funds |
After adding credits, with a new Idempotency-Key (why). |
| 403 | permission_error |
insufficient_scope, live_mode_disabled, ip_not_allowed, test_mode_only |
No. |
| 404 | not_found_error |
resource_missing, route_not_found |
No. |
| 409 | conflict_error, idempotency_error, invalid_request_error |
duplicate_external_id, sku_taken, idempotency_key_reused, idempotency_in_progress, order_not_cancelable |
Only idempotency_in_progress (and "try again in a minute" cases). |
| 413 / 415 | invalid_request_error |
request_too_large, unsupported_media_type |
No. |
| 429 | rate_limit_error |
rate_limited |
Yes, after Retry-After. |
| 500–503 | api_error |
internal_error |
Yes, with backoff and the same Idempotency-Key. |
404 vs 400 for missing objects: a 404 means the object in the URL does not exist (GET /orders/ord_unknown). An unknown object referenced in the body (an unknown sku, file_id, catalog_variant_id) is a 400 with code: resource_missing and param pointing at it.
Handling errors in code
const res = await fetch("https://swaglists.com/api/v1/orders", { method: "POST", headers, body: JSON.stringify(order) });
const body = await res.json();
if (!res.ok) {
const { code, message, param, request_id } = body.error;
switch (code) {
case "duplicate_external_id":
return body.error.existing_id; // already forwarded — treat as success
case "resource_missing":
if (param?.endsWith(".sku")) return skipUnknownSku(param); // not one of your products
break;
case "insufficient_funds":
return alertFinance(body.error.required_amount, body.error.available_amount);
}
console.error(`API error ${res.status} ${code} (${param ?? "-"}): ${message} [${request_id}]`);
throw new Error(code);
}res = requests.post("https://swaglists.com/api/v1/orders", json=order, headers=headers)
if not res.ok:
error = res.json()["error"]
if error["code"] == "duplicate_external_id":
return error["existing_id"] # already forwarded — treat as success
logger.error("API error %s %s (%s): %s [%s]", res.status_code, error["code"], error.get("param"), error["message"], error["request_id"])
raise RuntimeError(error["code"])What to retry
- Retry network errors, timeouts,
429(waitRetry-Afterseconds),409 idempotency_in_progress(wait 1 second) and5xx— with exponential backoff and jitter, and the same Idempotency-Key, so a request that actually succeeded is not repeated. See the retry recipe. - Don't retry other
4xxerrors unchanged — they will fail again. Fix the request, or surface the problem (log it withrequest_id, alert, or show it in your back office). - After fixing a
4xx, send the request with a new Idempotency-Key — also when the fix happened outside the request, such as adding credits after402 insufficient_fundsor creating a missing SKU. A request that ran is stored with its key for 24 hours, so a retry with the original key returns the original error again (withIdempotent-Replayed: true). Derive the new key from the old one (order-1042:2);external_idstill prevents a duplicate order.
Validation messages
parameter_invalid messages name the rule that failed:
| Message | Meaning |
|---|---|
Missing required parameter: recipient.city. |
Required field absent. |
Received unknown parameter: items[0].colour. |
Request bodies are strict: unknown fields are rejected (check spelling). |
Received unknown parameter: updated_since (query parameter). |
Query strings are strict too (only _, a cache-buster, is ignored) — see Pagination. |
Invalid metadata.note: must not contain NUL characters or invalid (unpaired surrogate) Unicode. |
Text can't contain U+0000 or half of a surrogate pair (a broken emoji, usually from cutting a string by UTF-16 units). Applies to every field, query parameter, path id and the Idempotency-Key. |
Invalid starting_after: must be the id of an object of this list (ids start with "ord_")… |
A cursor from another list. |
Invalid shipping_method: must be one of: economy, standard, express, priority. |
Wrong enum value. |
Identify each item with exactly one of product_variant_id, sku, or catalog_variant_id (+ designs). |
Item reference missing or ambiguous. |
Debugging
- Every response has a
Request-Idheader (req_…); errors repeat it asrequest_id. - Developers → Logs in the dashboard shows recent requests with status, error code, duration and (redacted) bodies — search by request id.
- Responses also carry
Api-Version(the API version that served the request).
The complete list of codes, with what each means and how to fix it, is the error reference.