Authentication
Overview
Section titled “Overview”RAMP authentication answers three questions for every request:
- Who are you? — Requester identity, proven by Ed25519 signature over a domain-bound key
- What can you access? — Scopes carried in the request (and optionally in a delegation token)
- Who authorized you? — Delegation chain from a principal (organization, user, upstream agent) via holder-bound JWTs that can be narrowed without round-tripping to the issuer
These three layers are independent. A public agent with no delegation still authenticates (layer 1) and declares scopes (layer 2, defaulting to public access). An enterprise agent adds a delegation token (layer 3) that binds the agent to a principal’s entitlements with time and spend limits.
Requester Identity
Section titled “Requester Identity”Every RAMP request carries a Requester message that identifies who is making the request.
Fields
Section titled “Fields”| Field | Type | Description |
|---|---|---|
id | string | Unique requester identifier (e.g., "agent-research-bot-001") |
domain | string | Domain for public key lookup — keys at {domain}/.well-known/ramp.json (WellKnownManifest, role=ROLE_AGENT) |
type | RequesterType | What kind of entity is making the request |
name | string (optional) | Human-readable name (e.g., "Acme Research Assistant") |
billing_ref | string (optional) | Opaque billing reference for invoicing / cost attribution (not an entitlement; access is via scopes/delegation) |
scopes | repeated string | Entitlement scopes — what the requester can access |
delegation | Delegation (optional) | Holder-bound JWT delegation token from a principal |
ext | Struct | Implementer-specific extensions |
ext_critical | repeated string | Critical extension keys (COSE crit pattern) |
RequesterType Enum
Section titled “RequesterType Enum”| Value | Name | Description |
|---|---|---|
0 | REQUESTER_TYPE_UNSPECIFIED | Not specified |
1 | REQUESTER_TYPE_AGENT | Autonomous AI agent (LLM, RAG system, research bot) |
2 | REQUESTER_TYPE_HUMAN_TOOL | Human using an AI-powered tool (copilot, assistant) |
3 | REQUESTER_TYPE_SERVICE | Enterprise service account (automated pipeline, cron job) |
4 | REQUESTER_TYPE_DELEGATED | Agent acting on behalf of a user (delegated identity) |
5 | REQUESTER_TYPE_RESEARCH | Research pipeline (batch data collection, model training) |
Key Lookup
Section titled “Key Lookup”The Exchange resolves a requester’s public key by fetching {domain}/.well-known/ramp.json (a WellKnownManifest with role=ROLE_AGENT) and selecting the JsonWebKey in public_keys whose kid matches the request signature:
// GET https://research.acme.com/.well-known/ramp.json{ "ver": "1.0", "role": "ROLE_AGENT", "domain": "research.acme.com", "contact": "platform@acme.com", "public_keys": [ { "kid": "research-acme-2026-q2", "kty": "OKP", "crv": "Ed25519", "use": "sig", "alg": "EdDSA", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", "not_before": "2026-04-01T00:00:00Z", "not_after": "2026-10-01T00:00:00Z" } ]}This ties cryptographic identity to DNS ownership — if you control the domain, you control the key.
WebBotAuth Compatibility (IETF)
Section titled “WebBotAuth Compatibility (IETF)”RAMP’s authentication stack is automatically compatible with the IETF WebBotAuth working group specification (standards track, targeting April 2026). Both use:
- RFC 9421 HTTP Message Signatures for request signing
- Ed25519 (EdDSA) as the signature algorithm
- JWK-formatted keys at well-known endpoints
When WebBotAuth ships as an RFC, RAMP agents will be recognised as authenticated bots by any CDN or origin server that implements it (Cloudflare is already deploying). No changes to RAMP’s authentication are required — the crypto is identical.
WebBotAuth solves identity (“this bot is who it claims to be”). RAMP adds the transaction layer on top of that identity (“this bot is authorised to access this content at this price”).
Signature Verification
Section titled “Signature Verification”The Requester message carries identity semantics only — it has no signature field. Request authentication is an RFC 9421 HTTP Message Signature over the HTTP request itself (covering @method, @target-uri, and a Content-Digest of the body), carried in the Signature / Signature-Input HTTP headers rather than in the protobuf body. This separates identity semantics (the Requester message) from cryptographic proof (the HTTP signature). The Exchange verifies it by:
- Reading the
keyidand covered components from the request’sSignature-Inputheader - Extracting
domainfrom theRequestermessage and fetching{domain}/.well-known/ramp.json, selecting theJsonWebKeyinpublic_keyswhosekidmatches thekeyid(and whose[not_before, not_after)window covers now) - Reconstructing the signed content from the declared components and verifying the Ed25519 signature in the
Signatureheader against that key - Confirming the
Content-Digestmatches the request body, and that the manifestdomainmatches the requester’sdomainwithrole=ROLE_AGENT
If verification fails, the Exchange rejects the request. No fallback, no degraded mode. See How RFC 9421 Signatures Work below for the header details.
Delegation
Section titled “Delegation”The delegation token is a JWT (the default and fully-specified format) that is holder-of-key bound: the grant is tied to a specific key via the RFC 7800 cnf confirmation claim, and the holder proves possession by signing the request with that key (the same RFC 9421 signature it already sends). The token is not a bearer credential — copying it is useless without the matching private key.
Why not a plain bearer JWT? A bearer JWT is usable by anyone who holds it, and it can’t be narrowed for a sub-agent without a round trip to the issuer. RAMP fixes both with one mechanism:
cnfholder-of-key binding (theft-resistance) plus chainedcnfJWTs (offline delegation). A principal narrows a grant for an agent by issuing a child JWT — no call back to the original issuer.
Holder-of-key, not bearer
Section titled “Holder-of-key, not bearer”Each delegation JWT carries cnf.jkt — the RFC 7638 JWK thumbprint of the key the grant is bound to. At request time the verifier computes the thumbprint of the key that signed the RFC 9421 request and checks it equals cnf.jkt. A thief who steals the token but not the private signing key cannot produce a valid request, so the token grants nothing. This is the same proof-of-possession mechanism as DPoP.
How delegation works
Section titled “How delegation works”Delegation is a chain of cnf-linked JWTs, content owner → principal → agent:
- Authority JWT — signed by the resource owner (the issuer). Carries the granted
scope,exp, andcnf.jkt= the principal’s key. The owner’s public key is the verifier’s only trust anchor. - Delegation JWT — signed by the principal, narrowing the grant for a specific agent:
scope ⊆ parent, shorterexp,cnf.jkt= the agent’s key. It carries the principal’s public key in its JOSE header (jwk) so the verifier can both link it to the authority’scnfand verify it. - Request — signed by the agent (RFC 9421); the verifier checks
thumbprint(request key) == delegation cnf.jkt.
Chain-linkage invariant: each JWT’s signer MUST be the key the parent named in cnf. The owner verifies the authority; authority.cnf pins the principal; the delegation’s header key must match that pin (and verifies the delegation); delegation.cnf pins the agent; the request key must match that pin. A token signed by a key not named upstream is rejected.
The chain narrows only — a child’s scope must be covered by its parent’s (see Scope Matching). The principal can delegate to a sub-agent (and that sub-agent further) by issuing another child JWT, entirely offline. Verifiers need only the owner’s public key; the principal and agent keys ride inside the chain.
The Delegation Message
Section titled “The Delegation Message”The token is carried in the Delegation message, nested inside Requester:
| Field | Type | Description |
|---|---|---|
principal_domain | string | Who granted this delegation (domain for public key lookup) |
principal_id | string | Principal’s identifier (e.g., "user@acme.com", "marketdata.example.com") |
scopes | repeated string | Scopes granted — convenience mirror of the token’s scope claim |
expires_at | Timestamp | When this delegation expires. Exchange MUST reject expired tokens |
max_spend_cents | int64 (optional) | Maximum spend in currency minor units (e.g. cents for USD) |
token | bytes | Token bytes — a JWT (base64url JWS) by default |
token_format | string | Token format: "jwt" (default) or "biscuit-v3" (optional) |
revocation_uri | string (optional) | URI for real-time revocation checking (high-value transactions) |
max_accesses | int32 (optional) | Maximum number of accesses allowed under this delegation. Exchange tracks count and denies with DENIAL_REASON_QUOTA_EXCEEDED when count >= limit. |
quota_period | Duration (optional) | How often access/spend counters reset. E.g., "720h" for monthly subscriptions. When absent, quota is lifetime (bounded only by expires_at). |
issuer | string (optional) | Token issuer — OIDC issuer URL (for JWT signature validation via OIDC→JWKS) |
ext | Struct | Implementer-specific extensions |
ext_critical | repeated string | Critical extension keys (COSE crit pattern) |
The scopes field is a convenience mirror — the Exchange MAY use it for fast catalog filtering before performing full token verification. The token’s claims are authoritative.
Delegation-Claims Profile
Section titled “Delegation-Claims Profile”The token’s claims are the authoritative source of the grant (the plaintext Delegation fields are only the convenience mirror). RAMP defines a small registered claim vocabulary — the same registered-tokens + optional-fields + vendor-namespace pattern used elsewhere in the protocol:
| Claim | JWT claim | Required? | Meaning |
|---|---|---|---|
| holder | cnf (with jkt) | Yes | Binds the grant to a key by RFC 7638 thumbprint — verified per Verification (request-signing key MUST hash to cnf.jkt). Without it the token is bearer-usable. |
| issuer | iss | Yes | Who granted it (resource owner / principal). |
scope | scope | No | Granted scopes (space-delimited; segment-wise matching — see Scope Matching). |
exp | exp | No (recommended) | Expiry. |
max_spend_cents | ramp_max_spend_cents | No | Spend cap (currency minor units). |
max_accesses | ramp_max_accesses | No | Access-count cap. |
quota_period | ramp_quota_period | No | Counter reset period. |
Rules:
- Everything except the holder binding (
cnf) is optional. An issuer that only wants scope + expiry sets just those; absent caps mean “no cap from the delegation” (the Exchange’s own limits still apply). - Vendors MAY add namespaced claims (
vendor:claim). - Constraints fail closed. A claim a verifier cannot evaluate is binding by default — it MUST reject rather than ignore (a dropped constraint would fail open, granting more than intended). This mirrors
Restriction.advisory: the issuer may mark a custom claim advisory to downgrade it to ignorable. (The issuer lists the mandatory-to-understand claims; an unrecognized non-advisory claim is rejected.) ramp_-prefixed claim names are reserved for this registry. Vendors MUST use their ownvendor:namespace and MUST NOT defineramp_*names, so registered claims can never be shadowed.
Biscuit alternative.
token_format: "biscuit-v3"remains a permitted alternative for deployments that want deep multi-hop, in-place offline attenuation across mutually-distrusting intermediaries. The same registered claim names map onto Biscuit facts, and the same holder binding applies (the request-signing key must match the holder the token is sealed to). JWT is the default and what conformant implementations must support; Biscuit is an optional profile.
Delegation chain example
Section titled “Delegation chain example”MarketData (resource owner) issues an authority JWT to the Acme principal, bound to Acme’s key:
// authority JWT — signed by marketdata.example.com (the owner key)// header{ "alg": "EdDSA", "typ": "JWT" }// claims{ "iss": "marketdata.example.com", "scope": "quote:* earnings:*", "max_spend_cents": 50000, "exp": 1781787345, "cnf": { "jkt": "<thumbprint of Acme's principal key>" }}Acme delegates to a research agent that only needs earnings data, narrowing scope and shortening the TTL — no call back to MarketData:
// delegation JWT — signed by Acme's principal key// header carries the principal pubkey so the verifier links it to authority.cnf{ "alg": "EdDSA", "typ": "JWT", "jwk": { "kty": "OKP", "crv": "Ed25519", "x": "<Acme principal pubkey>" } }// claims{ "iss": "acme", "scope": "earnings:*", "exp": 1781701545, "cnf": { "jkt": "<thumbprint of the agent's key>" }}The agent then signs its request (RFC 9421) with its own key. Zero round trips to MarketData — each delegation is a locally-issued child JWT.
Verification
Section titled “Verification”The Exchange/edge verifies a delegation by (offline, using only the owner’s public key):
- Verify the authority JWT with the resource owner’s published key (the trust anchor). Read its
scope,exp, andcnf.jkt. - Verify each delegation JWT with the key in its own JOSE header
jwk— but only after checkingthumbprint(header key) == parent cnf.jkt(the chain-linkage invariant). Reject a JWT signed by a key the parent did not name. Check each child’sscopeis covered by its parent’s. - Verify holder binding — the key that signed the HTTP request (RFC 9421) MUST hash to the final delegation’s
cnf.jkt. If not, reject (DENIAL_REASON_DELEGATION_INVALID). This is what makes a leaked token useless to a thief: possessing the bytes is not enough — you must control the bound private key. It is not optional and not skippable; an Exchange that omits it reduces the token to a bearer credential and breaks the anti-theft guarantee. - Check
expon every JWT in the chain against current time. - Optionally check
revocation_urifor high-value transactions.
This is entirely offline — no callback to the issuer. The verifier needs only the owner’s published public key (cacheable); the principal and agent keys arrive inside the chain (header jwk + cnf.jkt).
Binding across hops. Each delegation re-binds to the next holder’s key (cnf.jkt of the child = the next agent), so at every hop the bound key is whoever now signs the request. The chain of JWT signatures carries the binding forward — there is no point at which the token reverts to bearer form.
Which signature binds in a brokered request. A forwarded request carries more than one RFC 9421 signature (the agent’s, plus a hop signature from each intermediary). The holder-binding check always matches the terminal holder — the key named in the final delegation’s cnf.jkt. In the default broker pass-through case the broker is a pure relay: it forwards the agent’s delegation unchanged, the agent remains the terminal holder, and the agent’s request signature is the one that must match cnf.jkt (the broker’s hop signature is provenance only). Only if the agent explicitly delegated to the broker (the broker issues a child JWT bound to its own key and signs the request) does the broker become the terminal holder and its signature bind. Intermediaries never silently become the holder.
Delegation Denial Reasons
Section titled “Delegation Denial Reasons”When delegation verification fails, the Exchange returns a specific denial reason in the response:
| Denial Reason | Meaning |
|---|---|
DENIAL_REASON_QUOTA_EXCEEDED | Subscription access count exhausted for this period |
DENIAL_REASON_DELEGATION_INVALID | Delegation token missing, unverifiable (broken chain linkage or holder-binding mismatch), expired, or it widens scope beyond its parent |
Libraries
Section titled “Libraries”| Language | Library | Notes |
|---|---|---|
| Go | golang-jwt/jwt | EdDSA (Ed25519) signing/verification, JOSE headers |
| TypeScript | jose | EdDSA, runs in Node and the browser |
| Python | PyJWT (+ cryptography) | EdDSA |
All use Ed25519 natively — the same key type as RAMP identity signatures — so the delegation token and the RFC 9421 request signature share one key type and one library surface.
Scope-Based Access
Section titled “Scope-Based Access”Scopes are the mechanism by which the Exchange decides what resources to show a requester. The flow is:
- Agent sends
ResourceQuerywithRequester.scopes(and optionally aDelegationthat also carries scopes) - Exchange filters its catalog — only resources matching the declared scopes are returned
- Resources outside the scopes are not returned. The requester never learns they exist.
Scope Format
Section titled “Scope Format”Scopes follow the pattern {domain}:{permission} or {profile}:{permission}:
| Scope | Meaning |
|---|---|
"credit:read" | Can access credit reports (read-only) |
"subscription:marketdata-2026" | Has active MarketData subscription |
"revshare:publisher-x" | Party to an off-protocol revenue-share agreement (gates the FREE revshare term — see Licensing Terms) |
"academic:*" | Full access to academic resources |
"internal:reports" | Can access internal reports |
"dist:US:CA" | Multi-segment scope (colon-separated; matched segment-wise, see below) |
"*" | Unrestricted (public Exchange default) |
Scope Matching
Section titled “Scope Matching”Matching is segment-wise (:-separated). A granted scope G covers a required scope R iff, comparing segment by segment, each G segment equals the corresponding R segment or is *; a terminal * matches all remaining segments. There is no implicit prefix match. A grant that is more specific than what is required does not cover it — coverage requires G to be equal-to-or-broader-than R, never narrower (e.g. granted dist:US:CA does not cover required dist:US).
| Granted | Covers | Does not cover |
|---|---|---|
dist:* | dist:US, dist:US:CA | — |
dist:US:* | dist:US:CA | dist:EU |
dist | dist | dist:US |
dist:US:CA | dist:US:CA | dist:US (grant narrower than required) |
* | everything | — |
Access Tiers
Section titled “Access Tiers”No scopes (empty): Exchange applies its default access policy — typically all publicly available resources. This is the common case for public agents.
Subscription scopes: A scope like subscription:nyt-2026 signals that the requester has a subscription entitlement. The Exchange returns subscription pricing (zero marginal cost) instead of per-request pricing.
Enterprise scopes: Specific {domain}:{permission} scopes grant access to restricted resources. The Exchange enforces RBAC by filtering the catalog to matching resources.
Disclosure Policy
Section titled “Disclosure Policy”When a resource exists but the requester’s scopes are insufficient, the Exchange decides how to respond based on its disclosure policy:
- Hide existence: Silent omission. The resource is simply not returned — no hint that it exists. Appropriate when the resource’s existence is itself sensitive.
- Reveal it’s gated: Returns
OFFER_ABSENCE_REASON_SCOPE_INSUFFICIENTin theOfferGroup.absence_reasonfield, telling the requester the resource exists but their scopes/subscription don’t cover it. Standard wherever access is subscription- or scope-gated — enterprise RBAC and public marketplace subscriptions alike.
This distinction prevents information leakage on public Exchanges while allowing enterprise environments to provide actionable diagnostics.
Multi-hop forwarding
Section titled “Multi-hop forwarding”When a request passes through forwarding parties (e.g., Agent to Broker to Exchange), the forwarding chain is not carried in the message body at all. It lives entirely in the HTTP headers as a stack of RFC 9421 HTTP Message Signatures.
How It Works
Section titled “How It Works”Each forwarding party adds one labeled signature to the Signature / Signature-Input headers:
- The agent signs the request first.
- Each subsequent broker adds its own labeled signature, covering the request plus the prior hop’s signature.
Because every signature commits to the signature before it, the ordered set of signatures is the forwarding chain: it is tamper-evident and order-bound. You cannot reorder, remove, or insert a hop without breaking the chain. There is no separate per-hop timestamp or payload-signature field — the RFC 9421 signature itself carries that proof.
The Exchange verifies the full chain by:
- Reading each labeled signature and its
keyidfromSignature/Signature-Input - Resolving each
keyidat{domain}/.well-known/ramp.json(bykid) - Verifying every signature, confirming each later hop covers the prior hop’s signature
- Enforcing depth caps —
RequestConstraints.max_hopsandWellKnownManifest.max_intermediary_hops— by counting the signatures in the stack
When only the agent’s signature is present, the agent is querying the Exchange directly — no broker was involved.
See Multi-Hop Example (Broker) below for the concrete header layout.
Request Signatures (RFC 9421)
Section titled “Request Signatures (RFC 9421)”Why headers, not message fields
Section titled “Why headers, not message fields”Request signatures are RFC 9421 HTTP Message Signatures, carried in HTTP headers — not fields on any protobuf message. This separates identity semantics (the Requester message: who is making the request, scopes, delegation) from cryptographic proof (the HTTP signature attesting the request is authentic). No RAMP message has ever carried a request-signature field; the protobuf body carries identity only, and the forwarding chain is likewise a stack of labeled RFC 9421 signatures rather than an in-message hop list (see Multi-hop forwarding).
How RFC 9421 Signatures Work
Section titled “How RFC 9421 Signatures Work”RFC 9421 defines a standard way to sign HTTP messages. Three headers work together:
| Header | Purpose |
|---|---|
Content-Digest | Hash of the request body (binds the signature to the payload) |
Signature-Input | Declares which components are signed, the key ID, and the algorithm |
Signature | The actual cryptographic signature |
The Exchange verifies the signature by reconstructing the signed content from the declared components, fetching the signer’s public key via {domain}/.well-known/ramp.json (selected by kid), and verifying the Ed25519 signature.
Single-Hop Example
Section titled “Single-Hop Example”An agent sends a signed request directly to the Exchange:
POST /ramp.v1.ExchangeService/DiscoverResources HTTP/1.1Content-Type: application/jsonContent-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:Signature-Input: agent=("@method" "@target-uri" "content-digest");keyid="agent-key";alg="ed25519"Signature: agent=:dGVzdC1zaWduYXR1cmUtYWdlbnQ=:The Signature-Input header declares:
- Label:
agent— identifies this signature in multi-signature scenarios - Covered components:
@method(POST),@target-uri(the RPC path),content-digest(body hash) - keyid:
agent-key— resolved via the agent’s.well-known/ramp.json(public_keys[].kid) - alg:
ed25519— same key type used throughout RAMP
Multi-Hop Example (Broker)
Section titled “Multi-Hop Example (Broker)”When a request passes through a Broker, each hop adds its own signature. The Broker signs over the same components plus the agent’s existing signature, creating a verifiable chain:
POST /ramp.v1.ExchangeService/DiscoverResources HTTP/1.1Content-Type: application/jsonContent-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:Signature-Input: agent=("@method" "@target-uri" "content-digest");keyid="agent-key";alg="ed25519", broker=("@method" "@target-uri" "content-digest");keyid="broker-key";alg="ed25519"Signature: agent=:dGVzdC1zaWduYXR1cmUtYWdlbnQ=:, broker=:dGVzdC1zaWduYXR1cmUtYnJva2Vy:Each labeled signature (agent, broker) is independently verifiable. The Exchange checks every signature in the Signature header against the corresponding keyid in Signature-Input.
No fallback
Section titled “No fallback”RFC 9421 is the only request-authentication mechanism. A request without a valid RFC 9421 signature (Signature + Signature-Input, with a Content-Digest over the body) is rejected — there is no proto-field fallback and no degraded mode. There were never any in-message signature fields to fall back to.
Retrieval-URL Identity Binding
Section titled “Retrieval-URL Identity Binding”A signed retrieval_endpoint returned by a transaction is, by default, a bearer token: anyone holding it before expires_at can fetch. RAMP optionally binds the URL to the purchasing agent, DPoP-style (RFC 9449):
- The Exchange embeds
agent_identity_hash— the RFC 7638 JWK Thumbprint (SHA-256) of the agent’s Ed25519 request-signing key — inside the HMAC-signed URL, and echoes it on the response (TransactionResponse.agent_identity_hash, forwarded unchanged by the Broker onRAMPResponse.agent_identity_hash). - A capable delivery endpoint (edge function) verifies the binding fully offline: confirm the URL HMAC (proves the hash is Exchange-issued and untampered), then require the fetcher to present its public key and an RFC 9421 signature over the retrieval request, and check
thumbprint(presented key) == agent_identity_hash. No JWKS fetch required. - Bound to the agent’s request-signing key, never to the principal/delegation — the agent is the fetcher, and is exactly one key per transaction.
- Enforcement is NOT mandatory: a bearer-only signed-URL CDN that cannot run code falls back to HMAC + short TTL + TLS. RAMP reference implementations run on edge functions and do enforce it.
See Signed URL Verification for the full edge-function verification flow and the stolen-URL threat analysis.
Examples
Section titled “Examples”Public Agent (No Delegation)
Section titled “Public Agent (No Delegation)”A research bot queries a public Exchange. No delegation token, no special scopes — it gets whatever is publicly available.
{ "ver": "1.0", "id": "sq-research-001", "requester": { "id": "research-bot-42", "domain": "research.acme.com", "type": "REQUESTER_TYPE_AGENT", "name": "Acme Research Assistant", "scopes": [] }, "uris": [ "https://cdn.ramp-protocol.org/premium/ai-funding-roundup" ], "acceptable_restrictions": [ { "axis": "FUNCTION", "values": ["SUMMARIZE"] } ]}The Exchange verifies the signature by fetching the public key from research.acme.com/.well-known/ramp.json, then returns all publicly available offers for the requested URI.
Subscription Agent (MarketData JWT)
Section titled “Subscription Agent (MarketData JWT)”A financial analysis agent carries a MarketData-issued delegation JWT that grants access to subscription-tier data.
{ "ver": "1.0", "id": "sq-finance-001", "requester": { "id": "finbot-alpha", "domain": "fintech.example.com", "type": "REQUESTER_TYPE_AGENT", "name": "FinBot Alpha", "billing_ref": "lic-marketdata-fintech-2026", "scopes": [ "subscription:marketdata-2026", "quote:*", "earnings:*" ], "delegation": { "principal_domain": "marketdata.example.com", "principal_id": "marketdata.example.com", "scopes": ["quote:*", "earnings:*"], "expires_at": "2026-03-20T18:00:00Z", "max_spend_cents": 50000, "token": "eyJhbGciOiJFZERTQSJ9.<claims: iss, scope, exp, cnf.jkt>.<sig>", "token_format": "jwt" } }, "uris": [ "marketdata://quote/NVDA", "marketdata://earnings/NVDA/2025-Q4" ], "acceptable_restrictions": [ { "axis": "FUNCTION", "values": ["RETRIEVE"] } ]}The Exchange:
- Verifies the request’s RFC 9421 HTTP Message Signature against
fintech.example.com’s published key - Verifies the delegation JWT — signed by
marketdata.example.com’s key — and checks itscnf.jktequals the thumbprint of the request signer (holder binding) - Reads scopes from the token’s
scopeclaim (quote:*,earnings:*) - Returns subscription-tier pricing (zero marginal cost) for matching resources
Enterprise Agent (IAM-Issued JWT)
Section titled “Enterprise Agent (IAM-Issued JWT)”An internal agent at a law firm carries a delegation JWT issued by the firm’s IAM system, granting access to legal and credit resources.
{ "ver": "1.0", "id": "sq-legal-001", "requester": { "id": "legal-research-agent", "domain": "agents.kirkland.com", "type": "REQUESTER_TYPE_SERVICE", "name": "Kirkland Legal Research", "billing_ref": "lic-kirkland-enterprise-2026", "scopes": [ "legal:case-law", "credit:read" ], "delegation": { "principal_domain": "iam.kirkland.com", "principal_id": "user@kirkland.com", "scopes": ["legal:case-law", "legal:statutes", "credit:read"], "expires_at": "2026-03-20T20:00:00Z", "max_spend_cents": 100000, "token": "eyJhbGciOiJFZERTQSJ9.<claims: iss, scope, exp, cnf.jkt>.<sig>", "token_format": "jwt" } }, "uris": [ "westlaw://case/2025-us-456", "duns://credit/123456789" ], "acceptable_restrictions": [ { "axis": "FUNCTION", "values": ["RETRIEVE", "SUMMARIZE"] } ]}The internal Exchange verifies the delegation JWT against iam.kirkland.com’s published key. Because the resource’s existence isn’t secret here, if the agent requests one outside its scopes the Exchange returns OFFER_ABSENCE_REASON_SCOPE_INSUFFICIENT rather than silently omitting it.
Delegated Sub-Agent (cnf-chained delegation)
Section titled “Delegated Sub-Agent (cnf-chained delegation)”The Kirkland legal agent from the previous example delegates to a sub-agent that only needs credit data. It issues a child delegation JWT (bound to the sub-agent’s key) that removes legal access and lowers the spend cap.
{ "ver": "1.0", "id": "sq-credit-001", "requester": { "id": "credit-checker-sub", "domain": "agents.kirkland.com", "type": "REQUESTER_TYPE_DELEGATED", "name": "Kirkland Credit Sub-Agent", "billing_ref": "lic-kirkland-enterprise-2026", "scopes": [ "credit:read" ], "delegation": { "principal_domain": "iam.kirkland.com", "principal_id": "user@kirkland.com", "scopes": ["credit:read"], "expires_at": "2026-03-20T19:00:00Z", "max_spend_cents": 20000, "token": "eyJhbGciOiJFZERTQSJ9.<child claims: iss, scope, exp, cnf.jkt>.<sig>", "token_format": "jwt" } }, "uris": [ "duns://credit/123456789" ], "acceptable_restrictions": [ { "axis": "FUNCTION", "values": ["RETRIEVE"] } ]}Key differences from the parent agent’s request:
typeisREQUESTER_TYPE_DELEGATED— signals this agent acts on behalf of anotherdelegation.scopesis narrowed to["credit:read"](legal scopes removed)delegation.max_spend_centsis reduced from100000($1,000) to20000($200)delegation.expires_atis shortened (1 hour earlier)- The parent agent’s forwarding is recorded in the HTTP headers, not the message: the parent adds a labeled RFC 9421 signature, and the sub-agent adds its own signature over the request plus the parent’s (see Multi-hop forwarding)
- The delegation is a JWT chain: the authority JWT (signed by IAM) plus a child JWT (signed by the parent agent) whose
cnf.jktbinds the grant to the sub-agent’s key and whosescopeis narrowed
The Exchange verifies the full JWT chain: IAM signed the authority JWT; the parent agent signed the child JWT (its signing key matches the authority’s cnf — chain linkage); the sub-agent’s request key matches the child’s cnf.jkt. No call to IAM was required for the delegation.
Key Management
Section titled “Key Management”Where Keys Are Published
Section titled “Where Keys Are Published”Every party publishes its keys in one place — {domain}/.well-known/ramp.json (WellKnownManifest.public_keys, role-tagged). There are no per-role key files and no separate keys endpoint; keys are inline JWKs.
| Party | Endpoint | Role |
|---|---|---|
| Agent / Requester | {domain}/.well-known/ramp.json | ROLE_AGENT |
| Intermediary | {domain}/.well-known/ramp.json | ROLE_AGENT |
| Exchange | {domain}/.well-known/ramp.json | ROLE_EXCHANGE |
| Delegation Issuer (principal / resource owner) | {domain}/.well-known/ramp.json | ROLE_AGENT (key signing delegation JWTs) |
Key Announcement Format
Section titled “Key Announcement Format”// GET https://agents.kirkland.com/.well-known/ramp.json{ "ver": "1.0", "role": "ROLE_AGENT", "domain": "agents.kirkland.com", "contact": "platform@kirkland.com", "public_keys": [ { "kid": "kirkland-2026-q2", "kty": "OKP", "crv": "Ed25519", "use": "sig", "alg": "EdDSA", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", "not_before": "2026-04-01T00:00:00Z", "not_after": "2026-10-01T00:00:00Z" } ]}Key Rotation
Section titled “Key Rotation”Key rotation follows a standard overlap pattern:
- Publish the new key alongside the old key (both valid)
- Start signing new requests with the new key
- After a grace period (Exchange-defined, typically 24-48 hours), remove the old key
- Exchanges SHOULD cache keys with a TTL and re-fetch periodically
For delegation JWTs, key rotation at the issuer does not invalidate existing tokens — the authority JWT was signed with the key that was current at issuance time. Exchanges SHOULD accept tokens signed by recently-rotated keys within a configurable grace period.
Key Validity Windows
Section titled “Key Validity Windows”Each JsonWebKey carries RFC3339 not_before / not_after bounds. The window is half-open [not_before, not_after) — a key is valid at not_before and invalid at and after not_after. At least one key in public_keys MUST be valid at serve time, and verifiers MUST reject signatures whose key falls outside its window.
Caching Contract
Section titled “Caching Contract”/.well-known/ramp.json MAY be cached by consumers and CDNs for routine periods (minutes to hours) — keys rotate slowly and carry explicit bounds. The invalidation_url body, by contrast, SHOULD be served with a short or zero freshness lifetime (e.g. Cache-Control: max-age=60, or no-store) so the 300s revocation poll is not defeated by an intermediary cache.
Enforcement is operational, not protocol-guaranteed: a consumer that grants access against a stale revocation set bears that as its own business risk — it is not a protocol deficiency.
Emergency Revocation
Section titled “Emergency Revocation”When a manifest sets invalidation_url, consumers poll it on a 300s cadence (±10% jitter) and replace their local revoked set with the response — a KeyInvalidationList (kid-only snapshot: as_of + revoked[]). A revoked kid stays revoked; reject any signature whose kid is in the set. When invalidation_url is unset, consumers rely on routine rotation and not_after expiry.
Domainless Agents
Section titled “Domainless Agents”An individual agent with no public domain of its own is accommodated without a protocol change: a registry (operated by an Exchange/Broker or a third party) hosts the agent’s WellKnownManifest, and the agent sets Requester.domain to that registry host (e.g. agent-7.registry.example). The agent generates its own keypair; the registry publishes the public half at {registry}/.well-known/ramp.json. The domain + well-known paradigm is preserved end to end. Relaxing the discovery anchor (an explicit per-requester keys URL) is a deliberate deferral.
Next Steps
Section titled “Next Steps”- Transaction Flow — the RPCs that carry these signatures
- Scenario Walkthrough — see the full identity model in action
- Standards Layering — how authentication fits within the broader protocol stack
- Exchange Manifest — machine-readable Exchange self-description