Skip to content

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.

{
"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.

MemberStable?Use it for
codeForeverBranching. The only member your code should compare. Never renamed, never repurposed.
statusYesTransport handling — retry on 429/5xx, not on 4xx.
typeYesA URI you can open: https://developers.vaki.co/errors/vaki_not_found is a real page.
titleNoShowing a developer. Derived from the status; wording may change.
detailNoLogs and debugging. Written for a human, per occurrence.
instancePer requestThe 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:

  1. Branch on code, never on title or detail. Those are prose. They will be reworded, and one day translated.
  2. Treat an unknown code as its status class. Codes are added without a version bump (they are additive — see Versioning), so your handler needs a default branch. Retry an unknown 5xx; do not retry an unknown 4xx.

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.

Each code links to its own page — the same page the type member points at.

StatuscodeCondition
401unauthorizedNo usable API key: header missing, key unknown, or key expired.
403forbiddenValid key, not allowed — a missing permission or an IP allowlist miss. The API does not say which.
403insufficient_permissionsA handler rejected the call for a named missing permission.
StatuscodeCondition
400 / 422validation_failedA field is missing, the wrong type or out of range. Includes errors[]. Also the fallback for any other 400/422.
415unsupported_media_typeA request with a body arrived without Content-Type: application/json.
StatuscodeCondition
409idempotency_key_reusedSame Idempotency-Key, different request body. Nothing is created.
409idempotency_key_in_progressA request with this key is still in flight. Retry shortly.

See Idempotency for the full replay semantics.

StatuscodeCondition
429rate_limit_exceededThe per-minute or per-day limit on your key was exceeded.

See Rate limits.

StatuscodeCondition
404not_foundGeneric not-found, for paths without a more specific code.
404 / 422vaki_not_foundNo cause with that key. Surfaces as 422 on POST /v1/checkout_links, where the request was well-formed but could not be applied.
404checkout_link_not_foundNo checkout link with that id belongs to your account. “Does not exist” and “is not yours” are not distinguished.
422owner_not_foundowner.email on POST /v1/vakis has no Vaki account.
StatuscodeCondition
501not_implementedThe route is documented and routed, but its implementation has not landed. See Known limitations.
500internal_errorAn unhandled failure on our side. detail is generic on purpose — a raw downstream error can leak identifiers and other partners’ data.
StatusRetry?How
400, 401, 403, 404, 415, 422NoThe same request produces the same answer. Fix it or surface it.
409 idempotency_key_reusedNoYour two requests genuinely differ. Fix the key or the body.
409 idempotency_key_in_progressYesWait a second or two, retry with the same key.
429YesExponential backoff with jitter.
501NoIt will not start working on the next attempt.
500, 503YesExponential backoff, same Idempotency-Key, so a retry cannot double-create.

Mail soporte@vaki.co with the code, the instance path, your own request id if you have one, and the timestamp with its timezone.