guides
Intended Documentation
Verify Decision Tokens
Verify Ed25519 Authority Tokens locally with @intended/verify — pin the kid, validate claims, enforce the 300s TTL, and reject on the exact failure reasons the verifier returns.
Verify Authority Tokens#
The authorityDecisionToken returned by POST /intent is an Ed25519 JWT bound to exactly one intent, tenant, and execution target. Before the action runs, the executing system verifies the token locally — it never needs to call back to the Intended runtime. Verification is fail-closed: any failed check rejects the action.
This page uses @intended/verify's verifyToken, which is built on jose. The connector SDK's BaseAdapter performs the equivalent checks (plus single-use nonce consumption) automatically — see the Connector SDK.
Minted on APPROVED, verified locally, consumed once. Kid mismatch, bad signature, claim mismatch, and expiry are all rejected.
Token claims#
The validated claim set (from AuthorityDecisionTokenClaimsSchema):
| Claim | Meaning |
|---|---|
intentId | The intent this token authorizes (UUID). |
tenantId | Tenant scope. |
adapterId | The adapter permitted to consume the token. |
adapterTarget | The specific target the adapter acts on. |
targetSystem | The system the action targets. |
proposedAction | The action that was approved. |
decision | Always APPROVED — only approvals mint tokens. |
issuedAt / expiresAt | ISO-8601 validity window. |
nonce | ≥32-character value enforcing single use. |
At signing time the runtime also sets the registered JWT claims iss: "intended-authority", aud: <adapterId>, and integer iat / exp. The header is { "alg": "EdDSA", "typ": "JWT", "kid": "<tenant-key-id>" } — kid is mandatory.
Local verification flow#
Decode the header and read kid
Read the kid from the JWT header and load the tenant's public key for that kid. Pinning kid is mandatory; an unpinned or unknown kid is rejected with KID_MISMATCH.
Verify the Ed25519 signature
Verify against the public key, restricted to the EdDSA algorithm, with the expected issuer and audience. A failed signature check returns SIGNATURE_VERIFICATION_FAILED.
Validate the claims
The verifier parses the claims against the schema, then asserts tenantId and adapterId match what you expect, the issuedAt/expiresAt window is well-formed, the token is not used before valid or past expiry (within clockSkewSeconds), and the TTL does not exceed maxTokenTtlSeconds.
Consume the nonce (connector SDK only)
verifyToken itself does not touch a datastore — it is pure verification. Single-use enforcement happens when the connector SDK's BaseAdapter consumes the nonce atomically. If you verify outside the connector SDK, you are responsible for replay protection.
Verifying in code#
verifyToken returns { valid, reason, claims, header }. On success reason is null and claims is the validated claim set; on failure valid is false and reason names the failure.
Failure reasons#
These are the exact reason values verifyToken returns. Treat every one as a hard reject.
| Reason | Cause |
|---|---|
KID_MISMATCH | Token kid does not match the pinned expectedKid. |
SIGNATURE_VERIFICATION_FAILED | Signature, issuer, or audience check failed. |
TOKEN_CLAIMS_INVALID | Claims fail schema validation. |
TOKEN_TENANT_MISMATCH | Token tenantId ≠ expectedTenantId. |
TOKEN_ADAPTER_MISMATCH | Token adapterId ≠ expectedAdapterId. |
TOKEN_CLAIMS_INVALID_TIME | issuedAt / expiresAt not parseable. |
TOKEN_TIME_WINDOW_INVALID | expiresAt ≤ issuedAt. |
TOKEN_USED_BEFORE_VALID | Now + skew is before issuedAt. |
TOKEN_EXPIRED | Now − skew is past expiresAt. |
TOKEN_TTL_EXCEEDS_LIMIT | TTL exceeds maxTokenTtlSeconds (the 300s cap). |
Warning
verifyToken is signature + claim verification only. It does not enforce single use — that requires consuming the nonce. Inside a connector, BaseAdapter does this for you; outside one, track and reject seen nonces yourself.
On-robot verification (Physical AI)#
For physical-AI runtimes, the Authority Token is verified at the robot before actuation lands, so a compromised network path or stale cloud session can never let an actuation through. The edge verifiers ship as language SDKs — Go (intended-go) and Python / ROS2 (intended-ros2) — that fetch the signer's JWKS, pin the kid, verify the Ed25519 signature, and check tenant / audience / issuer / expiry locally.
Note
Roadmap. The sub-50ms Rust edge verifier referenced for the hot path is not yet in the repo. The JWKS route at /.well-known/jwks.json currently serves the physical-AI signer using an ephemeral per-process dev keypair until the production signer GAs; treat published robot-verifier examples as the target shape, and confirm the audience your verifier expects matches the token's aud before relying on it in production.
Next steps#
- Authority Token Model — full structure, keys, and lifetime.
- Connector SDK — fail-closed validation with nonce consumption.
- Enforcement SDK — the SDK method reference.
- Error Patterns — runtime error codes. </content>