GitHub

Docs

Errors

One error envelope, a namespaced registry of codes, and what a caller should actually do with each.

LIVE

One envelope

{
  "error": {
    "code": "path.request.expired",
    "message": "This request has expired",
    "detail": { "expired_at": "2026-09-08T10:00:00Z" },
    "request_id": "req_8f21c4"
  }
}

Every error, everywhere. A caller writes one handler.

code is stable and machine-readable. message is for a developer reading a log and MUST NOT be parsed. detail is optional and structured. request_id is echoed in the Path-Request-Id header and is what you quote in a support conversation.

Codes are part of the protocol

Namespaced path.<area>.<condition>, so the code alone tells you which part of the specification to open.

Adding a code is additive. Changing what one means is breaking, and requires a major protocol bump.

The registry

Envelope and versioning

CodeStatusWhat it meansWhat to do
path.core.unsupported_version400Unknown API versionPin a version the operator lists in discovery
path.core.malformed_request400The body failed validationRead detail; fix the caller
path.core.not_found404No such objectNot retryable
path.core.rate_limited429Too many requestsBack off, then retry
path.core.internal500Unexpected failureRetry with backoff; quote request_id

Authentication and membership

CodeStatusWhat it meansWhat to do
path.auth.unauthenticated401Missing, stale or invalid signatureCheck clock skew first — it is usually the timestamp window
path.auth.credential_revoked401The credential was revokedStop retrying. Get a new one
path.auth.member_not_active403The member is suspended or excludedA membership matter, not a technical one

Reachability

CodeStatusWhat it meansWhat to do
path.finder.budget_exhausted429Reciprocity budget spentNot a rate limit — do not simply retry slower. Reduce lookups or discuss the budget
path.finder.key_unresolvable404Nothing reachable at this keyIndistinguishable from "not visible to you", by design
path.finder.holder_unavailable503The holder is listed but not answeringRetryable. Distinct from not found — someone does hold this

Identity

CodeStatusWhat it meansWhat to do
path.id.attestation_revoked410The claim was withdrawnAsk the issuer for a current one. Not the same as unknown
path.id.attestation_expired410Past its expirySame — do not reuse it
path.id.binding_revoked410The ownership binding was withdrawnRe-prove control
path.id.binding_expired410The binding has expiredRe-prove control

These are execution-time answers. A 410 here means the object existed and is no longer usable; a 404 means it never did. Collapsing them would hide a revocation.

Address

CodeStatusWhat it meansWhat to do
path.address.revoked410The address was revokedAsk the recipient for a current one. Not the same as unknown
path.address.commitment_mismatch409A resolver answer diverges from the commitment published for that addressDo not send. Treat as a potentially compromised resolver

Raised client-side, by checkCommitment, not returned by an endpoint: the divergence is between two documents the payer fetched, and only the payer holds both.

Request

CodeStatusWhat it meansWhat to do
path.request.expired410Past its expiryAsk the issuer for a new one
path.request.already_paid409Already paidNot an error to retry — check your own state
path.request.revoked410Revoked by the issuerStop
path.request.amount_required400Open amount, and none suppliedPrompt the payer

Connect

CodeStatusWhat it meansWhat to do
path.connect.delegation_out_of_scope403Outside the granted capabilitiesRequest a wider grant, explicitly
path.connect.delegation_limit_reached403The counter is spentSurface it to the user; do not retry

Settlement

CodeStatusWhat it meansWhat to do
path.settlement.transition_invalid409The receipt cannot move from the state it is in to the one asked for — failed and reversed are terminal, and reversed follows only settledRead the receipt; if it is already where you wanted it, you lost a race and there is nothing to do
path.settlement.receipt_unverifiable500The issuer stored this receipt without its signed envelope and cannot reproduce the bytes it signedNothing the caller can retry. The receipt exists and its proof does not; ask the issuer

A signature that simply fails to verify is not this code. POST /receipts/verify answers { "valid": false, "reason": … } with a 200 — the verification ran and came back negative, which is an answer, not a failure of the request.

What errors never contain

No stack traces. Ever, in any environment reachable from outside.

No hint about why a lookup found nothing. key_unresolvable is returned identically for a key that does not exist and for one that exists but is not visible to the caller.

Retrying

if (err instanceof PathApiError && err.retryable) {
  // 5xx and rate limits: exponential backoff, full jitter
}

retryable covers 5xx and path.core.rate_limited. Everything else is a decision, not a hiccup — retrying an expired request produces the same answer more expensively.

path.finder.budget_exhausted deserves special mention: it looks like a rate limit and is not one. Retrying more slowly does not help, because the allowance is monthly and tied to what you contribute to the index. The fix is to look up fewer things, or to talk to the network.


On this page