title, message), where (errors[].path), and, when a trace is included, how to find this exact request again.
The envelope
Every error, regardless of status code, uses the same shape:| Field | Always present | Meaning |
|---|---|---|
title | Yes | The error class. Matches the HTTP status text (Bad Request, Unauthorized, and so on). |
message | Yes | A human-readable summary, written for the person reading your logs rather than for end users. |
docs | Yes | Where to read more. You’re here. |
trace | No | A unique id for this request. See below. |
errors | No | Field-level detail for validation failures. Each entry names a path, what’s wrong with it, the value you sent, and the schema it was checked against. |
errors[].path second. The message strings are for humans and may be reworded; the structure is the contract.
Status codes
| Code | When you’ll see it | Retry? |
|---|---|---|
200 | Success. | — |
400 | The request is malformed: a missing required header, an unparseable body, a parameter outside its bounds. Check errors[] for the exact field. | Not until you’ve fixed it. |
401 | The Authorization header is missing or doesn’t contain a valid API key. | Not until you’ve fixed it. |
403 | Your key is valid but this action isn’t allowed: insufficient permissions, or a workspace setting that isn’t enabled. | Not until something changes. |
404 | The resource doesn’t exist, or its id belongs to a different workspace than your key. | No. |
422 | The request is well-formed but semantically wrong. Every field parses, yet the combination can’t be processed. | Not until you’ve fixed it. |
429 | You’ve exceeded 10 requests per second. | Yes, with backoff. |
500 | Something failed on our side. If the response includes a trace id, that’s your fast path to an answer. | Yes, with backoff. |
400 in a loop won’t change the answer. It just spends your rate limit telling us the same thing.
Reading a validation error
errors[] exists so that “fix the request” never requires guesswork. Each entry is one problem:
pathpoints to the offending part of the request.messagesays what’s wrong with it, in one sentence.valueechoes what you actually sent, so you can spot the gap between what your code meant and what it did.schemashows the constraint it was validated against.
The trace id
When an error includes atrace, log it. When you contact support+developers@quo.com with a trace id, we can pull up the exact request: what arrived, how it was interpreted, what happened next. Without it, we’re both starting from “can you describe what you sent?”
One line of logging now saves a day of email archaeology later.
Retrying
For429 and 5xx responses, retry with exponential backoff and jitter: first retry after about a second, doubling thereafter, with a cap on attempts that fits how time-sensitive the work is. Read requests (GET) are always safe to retry. For writes, check the endpoint’s reference page first — a retried POST can repeat its effect, so re-attempting something like a message send needs more care than re-running a read.