The Agent Authorization Rail

The card that proves an agent may act.

One call, POST /v1/verify, tells any counterparty that a specific agent is authorized to take a specific action for a specific principal. Scoped grants, spend caps, instant revocation, step-up approvals, a tamper-evident hash-chained ledger. It authorizes. It never moves money.

Agents are transacting. Authority is assumed.

An AI agent arrives at your checkout holding someone's intent. You can check a card. You cannot check its authority. Was this action granted? Under what cap? Was it revoked a minute ago? Today authority is claimed, not proven: blanket API keys that express no scope, no limits, no expiry, and leave no evidence.

Vera AI is the neutral layer between principals, agents, and counterparties. The principal issues a scoped, Ed25519-signed grant. The agent presents it. You verify it against live state, at decision time.

The Rail takes no side of any transaction · it holds no funds

POST /v1/verifyThe entire demand-side surface.

Send the agent's grant token, the intended action, and the amount. The Rail evaluates against live state (revocation, remaining caps, velocity) and answers in one round trip. Every decision is HTTP 200: a refusal is a policy answer, not a transport error. Branch on the decision, never on the status code.

allowProceed. A hold reserves the amount against the grant's cap, so a second agent cannot spend it out from under you.
denyRefuse. Exactly one machine-readable reason. Evaluation short-circuits at the first failure in a fixed order.
step_up_requiredWait. The amount crossed the principal's threshold for unattended spending. A human approves, out of band.

Nine checks. Fixed order. One reason.

Every verification runs the same nine checks in the same sequence. Evaluation short-circuits at the first failure and returns exactly one deny reason. The order is part of the protocol, not an implementation detail. A test fails if it changes.

0 / 9
Film · Nine checks — fail one, everything after goes dark · 0:12
01Signature & expiryAn unexpired Ed25519-signed JWT the Rail issued. Fails as signature_invalid. Or grant_expired when only the clock ran out.
02RevocationChecked at three levels: grant, then agent, then account. Live state, not cache.
03CounterpartyThe grant's audience names you, and the principal meets the minimum tier you set.
04Action in scopesThe requested action exact-matches a granted scope. commerce:purchase is not booking:reserve.
05Currency & categoryCurrency and category tags match what the principal authorized. Nothing adjacent.
06Per-transaction capThe amount sits within the single-transaction limit. Integers, in minor units.
07Total capThe remaining budget covers it, decremented atomically by a hold, at decision time.
08VelocityThe rolling window has room. Ten actions in 24 hours means ten.
09Step-up thresholdAbove the line the principal drew, allow is withheld until a human approves.

Every decision leaves a receipt.

Allow, deny, revoke, confirm: each writes an entry to an append-only ledger, hash-chained per account. Fetch a proof and re-derive the hashes yourself, on your own machine. Alter one byte and the chain breaks. This is the record disputes and audits are built on.

Append-only is enforced below the application: the runtime database role has UPDATE, DELETE, and TRUNCATE revoked on the ledger, and the API task never receives the owner credentials. What the chain does not yet prove is us: signed checkpoints that would bind the head against the operator are on the roadmap, and SECURITY.md says so in plain terms.

GET /v1/ledger/{id}/proof · tamper-evident below the application role. Revocation wins at the next verify.

Take the card.

Drag to orbit  ·  double-click to reset

Eight invariants, guarded by named tests in CI.

Auditable at a glance. Alongside them, a contract suite validates every documented response against the OpenAPI spec, so drift fails the build. One disclosed gap, invariant 7's current test cannot yet fail, is tracked openly in SECURITY.md, because a guarantee you cannot audit is not one.

01

No double-spend, ever

Holds reserve spend atomically. Two concurrent verifies cannot reserve the same dollar.

invariant-1-no-double-spend.test.ts
02

The evaluation order is law

Nine checks, one fixed sequence, one deny reason per failure.

invariant-2-deny-reasons.test.ts
03

The ledger is append-only

Hash-chained per account. Tamper-evident by construction, independently verifiable.

invariant-3-ledger-chain.test.ts
04

Idempotency is mandatory

Every POST carries a key. A retry returns the original answer, never a second hold.

invariant-4-idempotency.test.ts
05

Secrets never touch disk

Or logs, in plaintext. API keys are stored as SHA-256 hashes with a prefix and last four.

invariant-5-secrets.test.ts
06

Revocation wins instantly

Effective at the next verify, by construction. Webhooks are a courtesy, not the mechanism.

invariant-6-revocation.test.ts
07

Holds settle exactly once

Confirm finalizes spend. Cancel releases it. Silence lets it expire. Never twice.

invariant-7-hold-settlement.test.ts
08

Denials are 200

A refusal is policy, carried in the body. Errors are deterministic.

invariant-8-errors.test.ts

Integration

Two runtime calls. An afternoon.

Register once

Receive an sk_cpt_ key, shown one time, then declare your category tags and, optionally, a minimum verification tier.

Verify at checkout

POST /v1/verify with the agent's token, the action, and the amount. Proceed on allow. Refuse on deny. Wait on step-up.

Confirm after fulfillment

POST /v1/verifications/{id}/confirm. Cancel releases the hold; silence lets it expire and release itself.

Fail closed. The TypeScript SDK generates idempotency keys, retries with the same key, and returns a denial rather than throwing when the Rail is unreachable, so a missing try/catch cannot become an unauthorized purchase. With or without it, the rule stands: treat any 5xx as a deny. Never fail open.
Film · The checkout — verify, hold, confirm · 0:20

Issuing agents on the supply side? Each user maps to an account, each of their agents to an agent under it. Issue grants scoped to the task's budget and expiry on task start; revoke on task end or anomaly. Listen for verification.* and grant.threshold_reached.

POST /v1/verify HTTP/1.1
Authorization: Bearer sk_cpt_····
Idempotency-Key: chk_order_58821
Content-Type: application/json

{
  "grant_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "action": "commerce:purchase",
  "amount": 4599,
  "currency": "USD",
  "categories": ["office_supplies"],
  "reference": "order_58821",
  "hold_ttl_seconds": 1800
}

Three audiences. One console.

Principal · account owners

Own the authority

Register agents. Issue and revoke grants. Approve step-ups. Read the ledger. Freeze everything, instantly.

Counterparty · the demand side

Trust the answer

Set minimum tier, categories, and hold TTL. Read your own verifications. Manage API keys.

Admin · platform operators

Run the rail

Set verification tiers with recorded evidence. Unfreeze accounts. The billing meter. Memberships.

Sign-in is passwordless: an emailed link, single-use, valid for fifteen minutes. Approving a step-up, freezing an account, and changing a tier each require a freshly checked authenticator code. Being signed in is not enough.

Security posture

The worst outcome is not a stolen card number. It is a forged allow.

Signing

Boring cryptography

Ed25519-signed JWTs. No verifiable credentials, no DIDs. Public keys at /.well-known/jwks.json, rotated with overlap.

Availability

Fail closed

A 5xx is a deny. So is a timeout. So is an exhausted retry. An outage removes purchases, never protections.

Data

Data minimization

Verify responses carry IDs, a display name, and a tier. Never PII, never payment credentials, never the principal's other grants.

Control

Kill switches

Revoke-all per agent. Freeze per account, idempotent and exempt from rate limiting by design.

Disclosure

A posture you can read

A published security policy: response commitments, safe harbor for researchers, known limitations disclosed up front.

Film · On revoke — kill switches · never fail open · 0:15

Agents will act. Make it provable.

Four commands, under ten minutes from a fresh clone. The demo issues a grant, allows a purchase, denies an over-cap one, walks a step-up, confirms, revokes, then fetches a ledger proof, verifies the chain independently, tampers with one byte, and watches verification fail.

Film · The receipt — alter one byte, the chain breaks · 0:14