Response shapes and errors
The two response envelopes, how to tell success from failure, and the error cases that do not look like errors.
Every JSON response wraps its result in an envelope. Reading the envelope correctly
is the difference between a robust integration and one that reports success on a
failed order — because a few failures on this API arrive inside a 200.
Check status before you trust the payload. An HTTP 200 is not on its own
proof that the operation succeeded. See Failures that arrive as 200.
The two envelopes
Order operations use one shape and the remaining operations use another. They overlap, so code against the fields you need rather than assuming one shape everywhere.
| Field | Orders | Conversions, automations, fonts, templates, address book, profile | Meaning |
|---|---|---|---|
payload | ✓ | ✓ | The result. Always present; check errors before reading it. |
errors | ✓ | ✓ | Array of human-readable messages. Empty on success. |
hasErrors | ✓ | ✓ | Read-only, true when errors is non-empty. |
warnings | ✓ | ✓ | Non-fatal messages. The operation still succeeded. |
hasWarnings | ✓ | ✓ | Read-only, true when warnings is non-empty. |
errorDetails | ✓ | — | Structured error objects. The shape varies by operation, so treat it as opaque unless that operation documents it. |
profile | — | ✓ | The account the API key belongs to. |
metadata | — | ✓ | Free-form. Populated only where the operation says so — see below. |
orderId | ✓ | — | The order the call was about, or 0 when the operation is not about one order. |
status | ✓ | — | The call's outcome. Not the order's status. |
metadata is usually empty
Only create conversions populates metadata, where it carries validation
counts (TotalRecords, ValidRecords, InvalidRecords, and per-identifier
counts). Everywhere else it is an empty object — including the conversions list and
conversion status operations. Do not look for totals or paging in it.
orderId of 0 means "not applicable"
Operations scoped to one order set it. The order listing and the multi-order
analytics and transaction operations leave it at 0. That is not order 0 — read
each item's own identifier from the payload instead.
Failures that arrive as 200
Three cases return a success status code for a request that did not fully succeed.
1. status is not "Success". On order operations this field carries "Success"
or "Error". On the per-order analytics and transaction operations it can also
carry "Error: " followed by the underlying exception message, for an individual
order that failed while others succeeded. Always branch on it:
const body = await response.json();
if (body.status && body.status !== "Success") {
throw new Error(`Order ${body.orderId} failed: ${body.status}`);
}2. errors is non-empty. Partial-success operations report per-record problems
here while still returning 200. Creating conversions is the clearest example: it
processes the valid records and tells you about the invalid ones.
3. A filter matched nothing. Filter values on the order listing are not
validated — they are compared for equality. A misspelled status returns 200 with
an empty list, not a 400. If you get an empty list unexpectedly, check the
spelling against the published values before assuming the
account has no such orders.
Status codes
| Code | What it means | Body |
|---|---|---|
200 | Request was accepted. Still check status and errors. | Envelope, except the proof operations — see below |
400 | Malformed request, an unparseable value, or a batch over its size limit | Envelope, with the reason in errors |
401 | API key missing or invalid | Varies: an envelope on some operations, a plain string on others |
403 | Your subscription tier does not include this feature | None — see the warning below |
404 | The referenced object does not exist, or is not yours | Envelope |
409 | Conflicts with the object's current state | Envelope |
422 | Nothing in the request was usable | Envelope, with detail in metadata and warnings |
500 | Unhandled server error | Usually none |
A bodiless 500 on an entitlement-gated operation is a permissions failure, not
a transient one. When your subscription tier lacks API access the intended
response is a 403, but it currently surfaces as a 500 with an empty body. Do
not retry it — retrying will fail identically. Check your plan, or contact
support. This is a known defect and is being fixed; a 403 with no body means the
same thing.
Not every response is JSON
The two proof operations return the PDF itself — application/pdf, raw bytes.
Write the body to a file; do not parse it. Their error responses are still JSON,
so branch on the status code before deciding how to read the body:
const response = await fetch(url, { headers: { "X-API-KEY": key } });
if (response.ok) {
await writeFile("proof.pdf", Buffer.from(await response.arrayBuffer()));
} else {
const { errors } = await response.json();
throw new Error(errors.join("; "));
}Enumerated values
Fields with a fixed set of accepted or returned values publish that set in the reference, in the exact spelling the API uses. Two things to know:
- Order status uses spaced display text, not identifiers: send
"Ready For Production", notReadyForProduction. One value breaks the pattern and has no space —InShipping— which is why you should copy values from the reference rather than deriving them. productis deliberately not enumerated. It is a server-defined list that changes as products are added and retired, so treat it as an opaque string and do not switch exhaustively on it.