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