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:
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

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.