DelegusDocsv0.3 rc1

Docs/DENY reason codes

Reference

DENY reason codes

Every reason a verify call can return, in the order the checks run. The table is generated from the delegus-base-v1 profile and spec §5.3; the check text is quoted, not paraphrased.

Rule. The decision is DENY if any check is false, and reason is the first false check in this order (spec §5.4). Protocol checks (P) are reproducible by anyone from the receipt's artifacts; trust checks (T) are Delegus's own attestation.

Checks in evaluation order#

#CheckPasses whenDENY reasonNeeds
1P1 ProtocolGrant parses and conforms to §3 (incl. parentGrant absent)GRANT_MALFORMED / UNSUPPORTED_FEATURE—
2T1 Trustissuer is a registered PrincipalISSUER_UNKNOWN—
3T2 TrustPrincipal is domain-verifiedISSUER_UNVERIFIEDT1
4T3 Trustkid not marked compromisedISSUER_KEY_COMPROMISEDT1
5P2 Protocolkid names a verification method present in the DID document snapshotISSUER_KEY_NOT_FOUNDT1
6P3 ProtocolGrant signature verifiesGRANT_SIGNATURE_INVALIDP2
7P4 Protocolnow ≥ validFromGRANT_NOT_YET_VALIDP3
8P5 Protocolnow < validUntilGRANT_EXPIREDP3
9P6 ProtocolStatus credential signed by Delegus; bit at statusListIndex is 0AUTHORITY_REVOKEDP3
10P7 Protocolaudience, if present, contains RPAUDIENCE_MISMATCHP3
11P8 ProtocolProof parses and conforms to §4 (incl. jti format)PROOF_MALFORMEDP3
12P9 ProtocolProof signature verifies against Agent keyPROOF_SIGNATURE_INVALIDP8
13P10 ProtocolProof iss == Grant subPROOF_AGENT_MISMATCHP8
14P11 ProtocolProof grant == Grant idPROOF_GRANT_MISMATCHP8
15P12 ProtocolProof aud == RP DIDPROOF_AUDIENCE_MISMATCHP8
16P13 Protocoliat within windowPROOF_EXPIREDP8
17P14 Protocolaction_hash == hash of submitted ActionPROOF_ACTION_MISMATCHP8
18T5 TrustReplay: SET replay:{rp_did}:{jti} 1 NX PX {2 × window_ms} returns OK. Executed only after P1–P14 pass, as a separate internal operation from the revocation-version read that precedes P6 (§7). Store unavailable → SERVICE_UNAVAILABLE.PROOF_REPLAYED / SERVICE_UNAVAILABLEP1–P14
19P15 ProtocolAction valid for its typeACTION_MALFORMEDP14
20P16 ProtocolSome Capability matches action.typeACTION_NOT_AUTHORIZEDP15
21P17 ProtocolAll constraint keys knownUNKNOWN_CONSTRAINTP16
22P18 ProtocolCurrency authorizedCURRENCY_NOT_AUTHORIZEDP17
23P19 Protocolamount ≤ maxAmountAMOUNT_EXCEEDS_AUTHORITYP18
24P20 Protocolresource matches a patternRESOURCE_NOT_AUTHORIZEDP19

A check whose prerequisite (the Needs column) did not pass is reported as "skipped", so a receipt shows which checks passed before the first failure and which never ran. Between P5 and P6 the service reads the current revocation state, and between P14 and T5 it performs the atomic replay write; those are internal operations, not checks (§5.4). T4 and T6 do not exist; identifiers are never reused (§5.3).

Codes outside the check tables#

CodeWhen
UNSUPPORTED_FEATUREReturned by P1 for a Grant that uses a reserved field, such as parentGrant, which must be absent in v0 (§3.2).
ISSUER_NOT_TRUSTEDReserved. Defined in §5.3, not produced by any v0.2 check.
SERVICE_UNAVAILABLEDelegus failed closed because it could not establish a fact it depends on: at T1 (registry unreachable, or a registered Principal whose DID document has not been resolved yet, so there is no identity snapshot to evaluate against), at P6 (current revocation state could not be established, §7) or at T5 (replay store). The affected check is reported false with this reason, and the result is a signed DENY receipt delivered with HTTP 200, not an HTTP error. On /verify, HTTP 503 is used only for failures before evaluation starts (the API key lookup) or after it ends (receipt persistence). /revoke also answers 503 until the revocation is durably recorded and visible to every verify node (§7); a caller retries rather than assuming success.

What the broad codes cover#

Three codes cover more than their one-line check suggests. These are the engine's readings of the specification, recorded with the engine's source.

Refusals without a receipt#

Some requests are refused before any check runs, or because a receipt can’t be issued. These come back as an HTTP error with a JSON body { "error", "message" }, not as a DENY: there is no receipt and no reason. Where the text says so, the Proof is not spent and the agent can retry with it. From the API reference for POST /verify:

HTTPWhen
400Malformed envelope (strings inside it are the engine's business and yield a DENY receipt)
401Unknown or non-RP API key
402Production checks need a plan for this account (error PLAN_REQUIRED). An access refusal before evaluation, like 401 or 429: no receipt and no DENY reason, and the proof is not spent. Sandbox checks are never refused this way.
413Body over 64 KB
422The receipt for this decision would exceed the signer's 4,096-byte message limit (error RECEIPT_TOO_LARGE): the Grant's matching capability is too large (for example a very long resources list). Refused before the replay check: no receipt, the Proof is not spent. Narrow the capability.
503Backing service unavailable before or after evaluation

New in v0.3#

These are the codes the v0.3 release candidate adds to the DENY reason codes page. They apply only to a relying party registered on the profile delegus-base-v3, unless a row says otherwise. The v0.2 codes on the existing page keep their meaning.

Release candidate. This describes v0.3-rc1, the current release candidate. New and existing relying parties are on it (profile delegus-base-v3). It is not frozen: the commitment construction is awaiting an external cryptography review, and the v0.2 specification stays the normative text until v0.3 is frozen. Details can still change.

A code reaches you in one of two ways:

Codes in a signed receipt#

Evaluation order under delegus-base-v3: P1 → T1 → T2 → T3 → P2 … P14 → T5 → P15 … P20 → P21 → D1 → D2 … D7 → T7. reason is the first false check in that order.

CodeCheckPlain meaning
ISSUER_NOT_TRUSTEDT1The company that signed the Grant is registered with Delegus but not in good standing: Delegus staff have suspended it or marked it disputed.
AUTHORITY_EXHAUSTEDP21The Grant has a budget, and this action does not fit in what is left (total, this period's amount, or number of uses). Nothing was spent. See Budgets.
AUTHORITY_EXHAUSTEDT7The action fitted when checked, but another request spent the last of the budget first. Nothing was spent for this request.
SERVICE_UNAVAILABLEP21, T7 (new places)Delegus failed closed: it could not read the budget counts for a budgeted Grant (P21), or could not reach the store to spend (T7).
DEPENDENCY_NOT_DECLAREDD1The relying party required a prior decision (in requires) that the agent's action does not declare, or declares with a different mode.
DEPENDENCY_COMMITMENT_MISMATCHD2A prior decision the action names does not match any receipt Delegus Check issued.
DEPENDENCY_RECEIPT_INVALIDD3The prior receipt's signature does not verify against a Delegus Check receipt key, or its body does not match what was signed.
DEPENDENCY_NOT_ALLOWD4The prior decision the action rests on was a DENY.
DEPENDENCY_TRANSACTION_MISMATCHD5The prior decisions the action names do not all belong to the same transaction.
DEPENDENCY_AUTHORITY_EXPIREDD6For a CURRENT_AUTHORITY dependency: the Grant behind the prior decision has expired since.
DEPENDENCY_ISSUER_NOT_TRUSTEDD7For a CURRENT_AUTHORITY dependency: the company behind the prior decision has since been marked suspended or disputed.
DEPENDENCY_ISSUER_KEY_COMPROMISEDD7For a CURRENT_AUTHORITY dependency: the key that signed the prior decision's Grant has since been marked compromised.
DEPENDENCY_AUTHORITY_REVOKEDD7For a CURRENT_AUTHORITY dependency: the Grant behind the prior decision has been revoked since.

Notes on these rows:

Prerequisites added by v0.3#

A check whose prerequisite did not pass is reported "skipped". v0.3 adds three rules (§5.4):

CheckNeeds
P21Every earlier check true: P1–P20 and T1, T2, T3, T5
D1–D7P21 true (P21 is true without any read when the matched Grant has no budget)
T7Every other check true

One consequence: when a trust check fails (for example PROOF_REPLAYED at T5), a v0.3 receipt shows P21 and D1–D7 as "skipped" and protocol.result: "FAIL", even though no P-check is false. Under v0.2 the same replay shows protocol.result: "PASS". The decision and reason are the same in both. Always read the reason from reason, never from protocol.result.

HTTP refusals, no receipt#

On /verify#

These rows extend the existing "Refusals without a receipt" table.

CodeHTTPWhenProfiles
RP_SUSPENDED403The relying party making the call is not in good standing: Delegus staff have suspended it or marked it disputed.every profile
RP_UNVERIFIED403The relying party is on its own domain (not a Delegus-managed identity), and its organization does not hold a verified claim on that domain. Verify the domain claim first.every profile
REQUIREMENTS_MALFORMED400The request's requires list is not well formed: it must have 1 to 8 distinct entries, each exactly { "fingerprint", "mode" }, with mode either PRIOR_DECISION or CURRENT_AUTHORITY. The Proof is not used up.any (the body is checked first)
REQUIREMENTS_NOT_SUPPORTED400The request carries requires, but the relying party is not on delegus-base-v3.any other than delegus-base-v3
RECEIPT_TOO_LARGE422Already on the page today. v0.3 makes it part of the specification (§6.4) and adds: no budget is spent, and the size is measured for the complete receipt before anything is written.every profile

Notes:

When claiming a company domain#

A company proves it controls a domain before Delegus Check trusts a company identity or a relying party on that domain. These codes come from starting a domain claim (through the console). Each is an HTTP error with no receipt.

CodeHTTPWhen
TERMS_REQUIRED400The claim does not record acceptance of the acceptable-use terms as exactly { accepted_by, accepted_at, version }.
CLAIM_DOMAIN_INVALID400The domain is not a valid DNS hostname, is managed by Delegus itself, or is a public suffix (such as co.uk) that no one can claim.
CLAIM_FREE_EMAIL403The person starting the claim signed in with a free email provider. Only a company email can claim a domain.
CLAIM_EMAIL_MISMATCH403The person's email is not on the claimed domain or a parent of it within the same registrable domain.
CLAIM_DOMAIN_VERIFIED409Another organization has already verified this domain.
CLAIM_REQUIRED403Registering a company identity or a relying party on a domain the organization has not claimed. Claim the domain first.

The 403 claim errors carry a terms_url member pointing to delegus.ai/terms#acceptable-use.

These come from the onboarding routes (/domain-claims), not from a decision; CLAIM_REQUIRED is in spec §5.1. They are listed here so every code a caller can meet is in one place.

Not a Delegus reason#

PROOF_BINDING_MISMATCH is not a reason code and never appears in a receipt. It is the SDK's ProofBindingError, raised on the relying party's side before /verify is called, when the Proof's htm/htu do not match the request the service actually received. Handle it as a client-side rejection; there is no receipt for it.

Reading a DENY#

A DENY is a signed receipt like any ALLOW (§6). It carries both protocol.result and trust.result, and the per-check outcomes, so you can see which checks passed before the first failure and which were skipped after it. When the DENY happens before P3 passes, the receipt omits principal, agent, grant_id, authority and expires_at (§6.4), because nothing about the Grant has been verified yet. Codes that begin with GRANT_ or ISSUER_ are about the company and its Grant; PROOF_ codes are about the agent's request; ACTION_, AMOUNT_, CURRENCY_, RESOURCE_ and UNKNOWN_CONSTRAINT are about whether this action fits the granted authority.