Skip to main content
Every error the API returns is built to help you answer three questions: what happened (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:
{
  "title": "Bad Request",
  "message": "Validation failed for 1 field.",
  "docs": "https://quo.com/docs",
  "trace": "6897907457496870895",
  "errors": [
    {
      "path": "/query/limit",
      "message": "must be <= 50",
      "value": 200,
      "schema": { "type": "integer" }
    }
  ]
}
FieldAlways presentMeaning
titleYesThe error class. Matches the HTTP status text (Bad Request, Unauthorized, and so on).
messageYesA human-readable summary, written for the person reading your logs rather than for end users.
docsYesWhere to read more. You’re here.
traceNoA unique id for this request. See below.
errorsNoField-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.
Handle errors by HTTP status code first and errors[].path second. The message strings are for humans and may be reworded; the structure is the contract.

Status codes

CodeWhen you’ll see itRetry?
200Success.
400The 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.
401The Authorization header is missing or doesn’t contain a valid API key.Not until you’ve fixed it.
403Your key is valid but this action isn’t allowed: insufficient permissions, or a workspace setting that isn’t enabled.Not until something changes.
404The resource doesn’t exist, or its id belongs to a different workspace than your key.No.
422The request is well-formed but semantically wrong. Every field parses, yet the combination can’t be processed.Not until you’ve fixed it.
429You’ve exceeded 10 requests per second.Yes, with backoff.
500Something failed on our side. If the response includes a trace id, that’s your fast path to an answer.Yes, with backoff.
A useful asymmetry to build into your integration: 4xx errors are yours, 5xx errors are ours. Retrying a 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:
  • path points to the offending part of the request.
  • message says what’s wrong with it, in one sentence.
  • value echoes what you actually sent, so you can spot the gap between what your code meant and what it did.
  • schema shows the constraint it was validated against.
Multiple invalid fields arrive as multiple entries in one response, so a single round trip shows everything that needs fixing.

The trace id

When an error includes a trace, 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

For 429 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.