Skip to main content
Every agent in the registry signs with keys it holds. The registry keeps the history of those keys: which are proven, which were replaced and until when they still count, and which were reported compromised. The normative text is spec/agent-keys.md. All routes on this page take the developer API key, and only work on the developer’s own agents: another developer’s agent answers 404.

Key identity

A key is identified by its JWK Thumbprint (RFC 7638, SHA-256, base64url). Only the required members count (crv, kty, x, y for EC; e, kty, n for RSA; crv, kty, x for Ed25519), so adding kid, alg or use to a JWK never changes its thumbprint. Send public keys only: a JWK with a private member such as d is refused. One key belongs to one agent. A key already in another agent’s history is refused with 409 AGENT_KEY_CONFLICT by POST /v1/agents/{agentId}/keys, and by POST and PATCH /v1/agents when AGENT_KEY_HISTORY_MIRROR_ENABLED is true.

Adding a key

The key starts pending. It cannot be used until its possession is proven. An agent may hold at most 10 keys that are pending, active or within a rotation overlap. A key registered as publicJwk on POST or PATCH /v1/agents enters the history too when the operator has set AGENT_KEY_HISTORY_MIRROR_ENABLED=true (off by default). With it off, add the registered key through POST /v1/agents/{agentId}/keys to bring it into the history. A key in the history becomes proven when the agent presents a DPoP proof with it at the OAuth endpoints, or through the challenge below.

Payments rails need P-256

Declare the rails the agent uses:
The rails are ap2, verifiable_intent, acp and ucp. An agent that declares a payments rail (ap2 or verifiable_intent) may hold only ES256 keys on P-256: any other key is refused with KEY_ALGORITHM_NOT_ALLOWED, and the rail cannot be declared while the agent still holds another key type, either in its history (pending, active or within a rotation overlap) or as its registered publicJwk. PATCH /v1/agents refuses a non-P-256 publicJwk for such an agent only when AGENT_KEY_HISTORY_MIRROR_ENABLED is true. Without a payments rail, Ed25519 keys are accepted as well.

Proving possession

Ask for a challenge for the pending key:
Sign it with the key, inside the agent, where the private key lives:
and send the result:
The key becomes active. A challenge proves possession once: a replayed or expired proof, a proof signed by another key, or a proof for another registry is refused with key_unproven (or audience_mismatch). Asking for a new challenge cancels the previous one.

Rotating a key

Add the new key and prove it first, then rotate the old one:
In the key history the old key stays usable for the overlap (7 days by default, set by AGENT_KEY_ROTATION_OVERLAP_SECONDS, at most 30 days per request) and is not usable from validTo on. A replacement that is still pending is refused with key_unproven.

The registered key is not changed by a rotation

The auth service’s own token endpoints (the OAuth PAR, code exchange, refresh and token exchange, POST /v1/authorize and POST /v1/token, delegation) and the agent’s DID document do not read the key history yet. They bind grants to the agent’s registered key, publicJwk, and to nothing else (FINDINGS G-85). A rotation does not change publicJwk, so until you change it:
  • the rotated key keeps working at those endpoints, after its validTo too, for as long as it stays the registered key;
  • the replacement is not accepted there.
When the agent has switched to signing with the replacement, make it the registered key:
The history is unchanged by this: the replacement stays active and the rotated key keeps its validTo. At the token endpoints the change is immediate, with no overlap: from then on they accept only the replacement, and keyPossessionVerified is false until the agent presents a DPoP proof with it at the OAuth endpoints, as after any change of publicJwk.

Reporting a compromised key

The key ends immediately and can never be registered again, by any agent, including after the agent that held it is deleted: compromised keys are kept in a record that outlives the agent. Registering one through POST /v1/agents/{agentId}/keys is refused with 409 key_not_active, and so is registering it through POST or PATCH /v1/agents when AGENT_KEY_HISTORY_MIRROR_ENABLED=true. A delegation to an agent whose registered key was reported compromised is refused with 409 key_not_active, including one that was already in flight. Every grant bound to it (cnf.jkt) is revoked, with every grant delegated from those, and each revocation is written to the audit chain. The response says how many grants were revoked. If the key was the agent’s registered publicJwk, the newest proven replacement takes its place (agentKey: "promoted"); with no replacement the registered key is cleared and the agent is suspended (agentSuspended: true) until a new key is registered. Calling it again is safe.

Reading the history

Each key carries its status (pending, active, rotated, compromised), validFrom, validTo, possessionProvedAt, rotatedFrom and whether it is usable now; a key that is not usable says why in denial (key_unproven or key_not_active).
Last modified on September 28, 2026