Errors use normal HTTP status codes and a JSON body with a stable code you can check in your code, and a message written for people.
{
"error": {
"code": "validation_error",
"message": "Some fields are invalid.",
"issues": [
{
"path": "products.0.listPrice",
"message": "Too small: expected number to be >=0"
}
]
}
}
issues is only there for validation_error. Each issue’s path points at the field, with list positions counted from 0.
Error codesLink to this section#
| Status | Code | What happened | What to do |
|---|---|---|---|
| 400 | validation_error | A query parameter or body field is missing or invalid. | Fix the fields listed in issues. |
| 400 | invalid_json | The body isn’t valid JSON. | Send JSON with Content-Type: application/json. |
| 400 | invalid_request | The request is valid but not allowed right now, e.g. marking a new order as completed. | Read message; don’t retry unchanged. |
| 401 | unauthorized | Missing, unknown or revoked API key. | Check the Authorization: Bearer header and that the key is still live. |
| 403 | insufficient_scope | A read-only key tried to write. | Use a Read & write key. |
| 403 | read_only | The workspace’s free trial or subscription has ended, so it’s read-only. | The owner subscribes in Settings. Reads still work. |
| 403 | forbidden | Not allowed for this key. | Contact support if you think this is wrong. |
| 404 | not_found | No such endpoint, or no such product, customer, order or price change in this workspace. | Check the id, SKU or account code. |
| 405 | method_not_allowed | The path exists but not with this method. | Use a method from the Allow header. |
| 429 | rate_limited | More than 120 requests a minute with this key. | Wait for Retry-After seconds (60), then retry. |
| 500 | internal_error | Something went wrong on our side. | Retry with backoff. If it keeps happening, contact support. |
Ids from another workspace always give 404 not_found, never someone else’s data.
RetryingLink to this section#
Retry 429 and 5xx responses with backoff. Don’t retry other 4xx responses: they’ll fail the same way.
All API writes are safe to repeat: PUT matches rows by SKU or account code, and PATCH /orders/{id} fails with invalid_request if the order has already moved on. So a retry after a network timeout won’t duplicate anything.
async function api(path, init = {}, attempt = 0) {
const res = await fetch(`https://tradecatalog.app/api/v1${path}`, {
...init,
headers: {
Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}`,
'Content-Type': 'application/json',
...init.headers,
},
})
if ((res.status === 429 || res.status >= 500) && attempt < 5) {
const wait = Number(res.headers.get('Retry-After')) || 2 ** attempt
await new Promise((resolve) => setTimeout(resolve, wait * 1000))
return api(path, init, attempt + 1)
}
const body = await res.json()
if (!res.ok) {
const error = new Error(`${body.error.code}: ${body.error.message}`)
error.status = res.status
error.code = body.error.code
error.issues = body.error.issues
throw error
}
return body
}
Common messagesLink to this section#
| Request | Status and message |
|---|---|
GET /prices with an unknown SKU | 404 “No product with SKU HX-9999” |
| Any endpoint with an unknown customer | 404 “Customer OAK99 not found” |
PATCH /orders/{id} new → completed | 400 “An order that is received can’t be marked completed” |
PATCH /orders/{id} to cancelled with no note | 400 “Give a reason for cancelling” |
PUT /products with the same SKU twice | 400 validation error “Duplicate SKU hx-8510” |