Concepts
Errors
The error envelope, the stable codes, and what to do with each.
Every error comes out in the same shape:
{
"error": {
"code": "validation_error",
"message": "…",
"details": []
}
}
code is the contract. Branch on it, never on the HTTP status or the
message: the message is written for a person and its wording can change without
notice.
code | HTTP | when |
|---|---|---|
unauthorized | 401 | missing, malformed, unknown or revoked credential |
forbidden | 403 | the credential lacks the required permission |
not_found | 404 | no such row in this brokerage |
validation_error | 422 | bad payload, unknown field, illegal transition |
rate_limited | 429 | over the credential's budget |
internal_error | 500 | unexpected failure (no details echoed) |
404 instead of 403, deliberately
An id that exists in another brokerage is answered 404 not_found, never
403. A 403 would confirm that the row exists somewhere, and that is already
information: you could walk an id space and learn the size of a competitor's
book. The API confirms nothing outside your own brokerage.
Bodies are strict
A field the API does not know is a 422, not a silently ignored key. A
misspelled budget_maxx fails now, instead of being half-saved and discovered
three weeks later when the funnel stops adding up.
System columns — brokerage_id, id, timestamps, public_slug — are not
reachable from any payload.
When a stage is blocked
It is the most frequent 422, and the one that carries the most information:
{
"error": {
"code": "validation_error",
"message": "La etapa actual tiene 4 paso(s) pendiente(s).",
"details": {
"reason": "stage_gate_blocked",
"pending_actions": [
{ "id": "ofertaPreparada", "label": "Oferta preparada con el comprador" },
{ "id": "ofertaEnviada", "label": "Oferta enviada al vendedor" },
{ "id": "respuestaVendedor", "label": "Respuesta del vendedor recibida" },
{ "id": "precioAcordado", "label": "Precio acordado" }
]
}
}
}
Nothing was written. The transition is validated before the row is touched, so a refusal leaves the operation exactly where it was. See Stages.
Action labels come back in the brokerage's own language — they are the same strings the app shows its agents, not a translation layer.
In MCP, errors have two levels
The connector separates two things that do not mean the same:
- a JSON-RPC error — the frame makes no sense: unknown method, malformed
params. That is a protocol failure. isError: trueinside the result — the call worked, and the answer is "no, and here is why": a file that does not exist, a stage gate, a permission that was not granted.
The second form is deliberate: the caller is a language model, and a protocol code hides the reason from it. Told the reason, it can explain to the agent what is missing instead of retrying the same thing.