Errors
Every error is an RFC 9457 problem
document served as application/problem+json. There is no second error shape, and
no endpoint that returns a bare 400 Bad Request with an empty body.
The shape
Section titled “The shape”{ "type": "https://developers.vaki.co/errors/vaki_not_found", "title": "Not found", "status": 404, "code": "vaki_not_found", "detail": "No vaki exists with key \"clinicalaligaa\".", "instance": "/v1/vakis/clinicalaligaa"}type, title, status, detail, code and instance are always present.
| Member | Stable? | Use it for |
|---|---|---|
code | Forever | Branching. The only member your code should compare. Never renamed, never repurposed. |
status | Yes | Transport handling — retry on 429/5xx, not on 4xx. |
type | Yes | A URI you can open: https://developers.vaki.co/errors/vaki_not_found is a real page. |
title | No | Showing a developer. Derived from the status; wording may change. |
detail | No | Logs and debugging. Written for a human, per occurrence. |
instance | Per request | The request path that failed, e.g. /v1/vakis/clinicalaligaa. Log it with your own request id. |
Two rules follow from the table, and both are load-bearing:
- Branch on
code, never ontitleordetail. Those are prose. They will be reworded, and one day translated. - Treat an unknown
codeas itsstatusclass. Codes are added without a version bump (they are additive — see Versioning), so your handler needs a default branch. Retry an unknown5xx; do not retry an unknown4xx.
Validation failures carry a field list
Section titled “Validation failures carry a field list”A validation error includes errors, one entry per invalid field, so you do not
have to bisect your payload:
{ "type": "https://developers.vaki.co/errors/validation_failed", "title": "Bad request", "status": 400, "code": "validation_failed", "detail": "amount must be a positive integer; currency must be one of: COP, USD", "instance": "/v1/checkout_links", "errors": [ { "field": "amount", "message": "amount must be a positive integer" }, { "field": "currency", "message": "currency must be one of: COP, USD" } ]}Each entry has exactly two members: field, a dotted path into the request
body (goal.amount, owner.email), and message, the human explanation.
detail is the same messages joined together, for when you only log one line.
There is no per-field machine code. Map field back onto your form field and show
message; do not pattern-match on message.
Every code
Section titled “Every code”Each code links to its own page — the same page the type member points at.
Authentication and authorization
Section titled “Authentication and authorization”| Status | code | Condition |
|---|---|---|
401 | unauthorized | No usable API key: header missing, key unknown, or key expired. |
403 | forbidden | Valid key, not allowed — a missing permission or an IP allowlist miss. The API does not say which. |
403 | insufficient_permissions | A handler rejected the call for a named missing permission. |
Request shape
Section titled “Request shape”| Status | code | Condition |
|---|---|---|
400 / 422 | validation_failed | A field is missing, the wrong type or out of range. Includes errors[]. Also the fallback for any other 400/422. |
415 | unsupported_media_type | A request with a body arrived without Content-Type: application/json. |
Idempotency
Section titled “Idempotency”| Status | code | Condition |
|---|---|---|
409 | idempotency_key_reused | Same Idempotency-Key, different request body. Nothing is created. |
409 | idempotency_key_in_progress | A request with this key is still in flight. Retry shortly. |
See Idempotency for the full replay semantics.
Rate limiting
Section titled “Rate limiting”| Status | code | Condition |
|---|---|---|
429 | rate_limit_exceeded | The per-minute or per-day limit on your key was exceeded. |
See Rate limits.
Resources
Section titled “Resources”| Status | code | Condition |
|---|---|---|
404 | not_found | Generic not-found, for paths without a more specific code. |
404 / 422 | vaki_not_found | No cause with that key. Surfaces as 422 on POST /v1/checkout_links, where the request was well-formed but could not be applied. |
404 | checkout_link_not_found | No checkout link with that id belongs to your account. “Does not exist” and “is not yours” are not distinguished. |
422 | owner_not_found | owner.email on POST /v1/vakis has no Vaki account. |
Not built, and us
Section titled “Not built, and us”| Status | code | Condition |
|---|---|---|
501 | not_implemented | The route is documented and routed, but its implementation has not landed. See Known limitations. |
500 | internal_error | An unhandled failure on our side. detail is generic on purpose — a raw downstream error can leak identifiers and other partners’ data. |
What to retry
Section titled “What to retry”| Status | Retry? | How |
|---|---|---|
400, 401, 403, 404, 415, 422 | No | The same request produces the same answer. Fix it or surface it. |
409 idempotency_key_reused | No | Your two requests genuinely differ. Fix the key or the body. |
409 idempotency_key_in_progress | Yes | Wait a second or two, retry with the same key. |
429 | Yes | Exponential backoff with jitter. |
501 | No | It will not start working on the next attempt. |
500, 503 | Yes | Exponential backoff, same Idempotency-Key, so a retry cannot double-create. |
Reporting an error
Section titled “Reporting an error”Mail soporte@vaki.co with the code, the instance
path, your own request id if you have one, and the timestamp with its timezone.