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

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_mismatch409The answer diverges from its commitmentDo not send. Treat as a potentially compromised resolver

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.receipt_unverifiable422The signature does not verifyRefetch the issuer's keys — an unknown kid usually means a rotation

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. Distinguishing them would make the endpoint an oracle for "is this person a customer of somebody", which is worth money to a scammer before anyone learns which provider.

That constraint extends to timing and to status codes. A faster negative is still an answer.

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