diff --git a/fern/changelog/2026-07-20-agentid-public-key-auth.mdx b/fern/changelog/2026-07-20-agentid-public-key-auth.mdx
new file mode 100644
index 0000000..440fb59
--- /dev/null
+++ b/fern/changelog/2026-07-20-agentid-public-key-auth.mdx
@@ -0,0 +1,42 @@
+---
+tags: ["api-keys", "agentid", "new-feature", "sdk"]
+---
+
+## Summary
+
+Build AgentID sign-in flows with a scoped P-256 credential while keeping private
+key material in your own keystore. The API reference now defines a dedicated
+public-key lifecycle for generated SDKs, and the new guide provides strict Python
+and TypeScript approval helpers.
+
+### What's new?
+
+**New endpoints:**
+
+- `POST /v0/api-keys/public-keys` - Register only a public P-256 JWK and receive the server-owned `api_key_id` used as `kid`
+- `GET /v0/api-keys/public-keys` - List public-key credentials without mixing in bearer credentials
+- `PATCH /v0/api-keys/public-keys/{api_key_id}` - Rename a credential without mutating security-relevant fields
+- `DELETE /v0/api-keys/public-keys/{api_key_id}` - Revoke one public-key credential
+- `POST /v0/api-keys/public-keys/agentid-sign-in/revoke-all` - Idempotently invalidate every current AgentID sign-in key in an organization
+
+**New AgentID endpoint** (served by the AgentID issuer, not part of the AgentMail REST API or generated SDKs — call it directly as shown in the guide):
+
+- `POST https://auth.agentid.com/authorize/approve` - Submit one strict ES256 approval assertion without a bearer credential
+
+**New features:**
+
+- **Scoped credentials**: Register organization-, pod-, or inbox-scoped keys with inherited scope and expiry defaults.
+- **Generated SDK contract**: Generate P-256 keys, register only public coordinates, pin the approval header and claims, and keep private keys below model context once corresponding SDK releases are published.
+
+### Use cases
+
+Build agents that:
+
+- Approve AgentID sign-in while the private key stays in a keystore or HSM
+- Delegate sign-in authority to one organization, pod, or inbox
+- Rotate credentials with a create-new, deploy-new, delete-old sequence
+- Fence every active AgentID sign-in key with an idempotent emergency operation
+
+
+ Follow the [AgentID Public-Key Authentication guide](https://docs.agentmail.to/agentid-public-key-authentication) for complete Python and TypeScript helpers, lifecycle rules, and the accepted browser-session intent limitation.
+
diff --git a/fern/definition/api-keys.yml b/fern/definition/api-keys.yml
index 2cab110..66f3c86 100644
--- a/fern/definition/api-keys.yml
+++ b/fern/definition/api-keys.yml
@@ -20,6 +20,148 @@ types:
type: datetime
docs: Time at which api key was created.
+ PublicJwkCoordinate:
+ type: string
+ validation:
+ minLength: 43
+ maxLength: 43
+ pattern: "^[A-Za-z0-9_-]{43}$"
+ docs: A 32-byte P-256 coordinate encoded as unpadded base64url.
+
+ PublicJwk:
+ docs: |
+ A public P-256 JWK. The object accepts exactly `kty`, `crv`, `x`, and `y`.
+ Private key material such as `d`, embedded key IDs, and all other members
+ are rejected. The server also rejects coordinates that are not a point on
+ P-256.
+ properties:
+ kty: literal<"EC">
+ crv: literal<"P-256">
+ x: PublicJwkCoordinate
+ y: PublicJwkCoordinate
+
+ OrganizationPublicKeyScope:
+ docs: Organization-wide authority.
+ properties: {}
+
+ PodPublicKeyScope:
+ docs: Authority over one live pod and its inboxes.
+ properties:
+ id:
+ type: uuid
+ docs: ID of the pod.
+
+ InboxPublicKeyScope:
+ docs: Authority over one live inbox incarnation.
+ properties:
+ id:
+ type: string
+ validation:
+ format: email
+ maxLength: 254
+ docs: ID of the inbox.
+
+ PublicKeyScope:
+ docs: The immutable scope in which a public-key credential can approve AgentID sign-in.
+ union:
+ organization: OrganizationPublicKeyScope
+ pod: PodPublicKeyScope
+ inbox: InboxPublicKeyScope
+
+ PublicKeyMaterial:
+ docs: Registered public key material and its server-computed RFC 7638 thumbprint.
+ properties:
+ jwk: PublicJwk
+ fingerprint:
+ type: string
+ validation:
+ minLength: 43
+ maxLength: 43
+ pattern: "^[A-Za-z0-9_-]{43}$"
+ docs: RFC 7638 SHA-256 JWK thumbprint encoded as unpadded base64url.
+
+ PublicKeyCredential:
+ docs: |
+ An AgentID sign-in credential. `type` and `api_key_id` are server-owned;
+ use `api_key_id` as the JWS `kid`. This response never contains a bearer
+ secret or private key.
+ properties:
+ api_key_id:
+ type: uuid
+ docs: Server-generated credential ID. Store this value as the signing key's `kid`.
+ type:
+ type: literal<"public_key">
+ docs: Server-owned credential discriminator. Callers cannot select or update it.
+ name:
+ type: Name
+ docs: Human-readable credential name.
+ public_key: PublicKeyMaterial
+ scope: PublicKeyScope
+ expires_at:
+ type: optional
+ docs: Immutable absolute expiry. Omitted when the credential does not expire.
+ revoked_at:
+ type: optional
+ docs: Present when organization-wide revoke-all invalidated this credential generation.
+ created_at: datetime
+ updated_at: datetime
+
+ CreatePublicKeyRequest:
+ docs: |
+ Register only a public P-256 JWK. Credential type, `api_key_id`, sign-in
+ eligibility, permissions, and generation are server-owned and are not
+ request properties.
+ properties:
+ public_key: PublicJwk
+ name:
+ type: optional
+ validation:
+ minLength: 1
+ maxLength: 256
+ docs: Defaults to `AgentID key {first eight fingerprint characters}`.
+ scope:
+ type: optional
+ docs: |
+ Omit to inherit the registering bearer key's exact scope. An explicit
+ scope must be the caller's scope or a live descendant.
+ expires_at:
+ type: optional
+ docs: |
+ Future absolute expiry. Omit to inherit the registering bearer key's
+ expiry. A child credential cannot outlive its creator.
+
+ UpdatePublicKeyNameRequest:
+ docs: |
+ Rename a public-key credential. Key material, ID, type, scope, sign-in
+ eligibility, permissions, generation, and expiry are immutable.
+ properties:
+ name:
+ type: string
+ validation:
+ minLength: 1
+ maxLength: 256
+
+ ListPublicKeysResponse:
+ properties:
+ count: global.Count
+ next_page_token: optional
+ public_keys:
+ type: list
+ docs: Public-key credentials only, ordered by creation time descending by default.
+
+ RevokeAllAgentIdSignInKeysResponse:
+ docs: Permanent idempotency receipt for an organization-wide AgentID sign-in key revocation.
+ properties:
+ previous_generation:
+ type: integer
+ validation:
+ min: 0
+ current_generation:
+ type: integer
+ validation:
+ min: 1
+ revoked_at: datetime
+
ApiKeyPermissions:
docs: Granular permissions for the API key. When ommitted all permissions are granted. Otherwise, only permissions set to true are granted.
properties:
@@ -116,6 +258,9 @@ types:
api_key_create:
type: optional
docs: Create API keys.
+ api_key_update:
+ type: optional
+ docs: Update API keys.
api_key_delete:
type: optional
docs: Delete API keys.
@@ -226,3 +371,95 @@ service:
api_key_id: ApiKeyId
errors:
- global.NotFoundError
+
+ listPublicKeys:
+ method: GET
+ path: /public-keys
+ display-name: List Public-Key Credentials
+ docs: |
+ List only public-key credentials visible to the bearer caller's scope.
+ Bearer credentials are never returned, even though both credential types
+ share storage and pagination indexes. Requires `api_key_read`.
+ request:
+ name: ListPublicKeysRequest
+ query-parameters:
+ limit: optional
+ page_token: optional
+ ascending: optional
+ response: ListPublicKeysResponse
+
+ createPublicKey:
+ method: POST
+ path: /public-keys
+ display-name: Register Public-Key Credential
+ docs: |
+ Register a public P-256 JWK using an existing AgentMail bearer API key
+ with `api_key_create`. Re-registering the same JWK creates a new
+ credential ID; it does not replace or recover an earlier credential.
+ The private key must never be sent to AgentMail.
+ request: CreatePublicKeyRequest
+ response: PublicKeyCredential
+ errors:
+ - global.ValidationError
+ - global.ConflictError
+
+ updatePublicKeyName:
+ method: PATCH
+ path: /public-keys/{api_key_id}
+ display-name: Rename Public-Key Credential
+ docs: |
+ Rename the credential. All security-relevant fields are immutable.
+ Requires `api_key_update`.
+ path-parameters:
+ api_key_id:
+ type: uuid
+ docs: Public-key credential ID returned by registration.
+ request: UpdatePublicKeyNameRequest
+ response: PublicKeyCredential
+ errors:
+ - global.ValidationError
+ - global.NotFoundError
+
+ revokePublicKey:
+ method: DELETE
+ path: /public-keys/{api_key_id}
+ display-name: Revoke Public-Key Credential
+ docs: |
+ Permanently revoke one public-key credential. This hard-deletes the
+ credential; repeating the request returns not found. Requires
+ `api_key_delete`.
+ path-parameters:
+ api_key_id:
+ type: uuid
+ docs: Public-key credential ID returned by registration.
+ response:
+ status-code: 204
+ errors:
+ - global.NotFoundError
+
+ revokeAllAgentIdSignInKeys:
+ method: POST
+ path: /public-keys/agentid-sign-in/revoke-all
+ display-name: Revoke All AgentID Sign-In Keys
+ docs: |
+ Invalidate every current public-key credential in the caller's
+ organization by advancing its AgentID key generation. The caller must be
+ organization-scoped and either have `api_key_delete` or, for a verified
+ self-serve agent organization, use an unrestricted unmanaged bearer
+ credential. No request body is accepted.
+
+ `Idempotency-Key` is required and must be a UUID. Reusing the same UUID
+ returns the original permanent receipt without advancing the generation
+ again. A new UUID performs a new generation advance.
+ request:
+ name: RevokeAllAgentIdSignInKeysRequest
+ headers:
+ Idempotency-Key:
+ type: string
+ validation:
+ format: uuid
+ docs: Required UUID identifying this revoke-all operation permanently.
+ response: RevokeAllAgentIdSignInKeysResponse
+ errors:
+ - global.ValidationError
+ - global.ConflictError
diff --git a/fern/docs.yml b/fern/docs.yml
index c236557..477add6 100644
--- a/fern/docs.yml
+++ b/fern/docs.yml
@@ -314,6 +314,9 @@ navigation:
# path: pages/integrations/crewai.mdx
- section: Guides
contents:
+ - page: AgentID Public-Key Authentication
+ icon: fa-solid fa-key
+ path: pages/guides/agentid-public-key-authentication.mdx
- page: Sending & Receiving Email
icon: fa-solid fa-right-left
path: pages/guides/sending-receiving-email.mdx
diff --git a/fern/pages/guides/agentid-public-key-authentication.mdx b/fern/pages/guides/agentid-public-key-authentication.mdx
new file mode 100644
index 0000000..562058d
--- /dev/null
+++ b/fern/pages/guides/agentid-public-key-authentication.mdx
@@ -0,0 +1,413 @@
+---
+title: AgentID Public-Key Authentication
+subtitle: Register a scoped P-256 key and sign one AgentID approval without exposing the private key.
+slug: agentid-public-key-authentication
+description: Generate and store a P-256 key, register its public JWK, and submit a strict signed AgentID approval.
+---
+
+AgentID public-key credentials let an agent prove possession of a P-256 private
+key while approving an AgentID sign-in. Registration uses an existing AgentMail
+bearer API key. Approval uses only a compact signature and never sends that
+bearer key or the private key to AgentID.
+
+
+ Generate and use the private key in a keystore, HSM, KMS, or a small trusted
+ signing process. Give the model an opaque signing capability, not the private
+ JWK, PEM, environment variable, tool output, log entry, prompt, trace, or
+ conversation history.
+
+
+## Protocol at a glance
+
+1. Generate a P-256 key pair in trusted code and persist the private key in your
+ keystore.
+2. Export only `{kty: "EC", crv: "P-256", x, y}` and register it at
+ `POST /v0/api-keys/public-keys` with an existing AgentMail bearer API key.
+3. Store the returned `api_key_id` beside the private-key handle. It is the JWS
+ `kid`; do not compute or choose it yourself.
+4. For one pending authorization transaction, sign exactly `{jti, inbox_id}`
+ with ES256 and protected `typ: agentid-approval+jwt`.
+5. POST exactly `{assertion, inbox_id}` to
+ `https://auth.agentid.com/authorize/approve` without bearer authorization.
+ Success is `204 No Content`.
+
+Public-key credentials are only AgentID sign-in credentials. They cannot replace
+an AgentMail bearer API key for normal REST API calls.
+
+## Generate and register a key
+
+The registration endpoint rejects private `d`, unknown JWK members, non-P-256
+curves, malformed coordinates, and coordinates that are not on P-256. The
+server computes the RFC 7638 SHA-256 fingerprint and returns it; compare or log
+the public fingerprint when you need an audit handle, never the private key.
+
+
+ The generated SDK methods in this guide require an AgentMail Python and
+ TypeScript SDK release that includes the public-key credential endpoints. If
+ your installed client does not expose these methods yet, use the REST API or
+ upgrade after that release is published.
+
+
+After that SDK release, install the Python example dependencies with
+`pip install agentmail cryptography PyJWT httpx`, or the TypeScript dependencies
+with `npm install agentmail jose`.
+
+
+
+```python title="Python"
+from __future__ import annotations
+
+import base64
+from datetime import datetime
+from typing import Any, Dict, Literal, Optional, TypedDict, Union
+
+from agentmail import AgentMail
+from cryptography.hazmat.primitives.asymmetric import ec
+
+
+class OrganizationScope(TypedDict):
+ type: Literal["organization"]
+
+
+class PodScope(TypedDict):
+ type: Literal["pod"]
+ id: str
+
+
+class InboxScope(TypedDict):
+ type: Literal["inbox"]
+ id: str
+
+
+Scope = Union[OrganizationScope, PodScope, InboxScope]
+
+
+def b64url_coordinate(value: int) -> str:
+ return base64.urlsafe_b64encode(value.to_bytes(32, "big")).rstrip(b"=").decode()
+
+
+def public_jwk(private_key: ec.EllipticCurvePrivateKey) -> Dict[str, str]:
+ numbers = private_key.public_key().public_numbers()
+ return {
+ "kty": "EC",
+ "crv": "P-256",
+ "x": b64url_coordinate(numbers.x),
+ "y": b64url_coordinate(numbers.y),
+ }
+
+
+def register_agentid_key(
+ client: AgentMail,
+ private_key: ec.EllipticCurvePrivateKey,
+ *,
+ scope: Optional[Scope] = None,
+ name: Optional[str] = None,
+ expires_at: Optional[datetime] = None,
+):
+ request: Dict[str, Any] = {"public_key": public_jwk(private_key)}
+ if scope is not None:
+ request["scope"] = scope
+ if name is not None:
+ request["name"] = name
+ if expires_at is not None:
+ request["expires_at"] = expires_at
+ return client.api_keys.create_public_key(**request)
+
+
+# Generate inside your keystore in production. This in-process object is only a
+# minimal example; persist it before registration so a crash cannot orphan the kid.
+private_key = ec.generate_private_key(ec.SECP256R1())
+client = AgentMail(api_key="YOUR_EXISTING_AGENTMAIL_API_KEY")
+credential = register_agentid_key(
+ client,
+ private_key,
+ name="production signer",
+ scope={"type": "inbox", "id": "agent@example.com"},
+)
+
+# Store this mapping in trusted application state.
+key_record = {
+ "keystore_handle": "opaque-keystore-handle",
+ "kid": str(credential.api_key_id),
+}
+```
+
+```typescript title="TypeScript"
+import { generateKeyPairSync, type KeyObject } from "node:crypto";
+import { AgentMailClient } from "agentmail";
+
+type Scope =
+ | { type: "organization" }
+ | { type: "pod"; id: string }
+ | { type: "inbox"; id: string };
+
+function publicJwk(publicKey: KeyObject) {
+ const jwk = publicKey.export({ format: "jwk" });
+ if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.x || !jwk.y) {
+ throw new Error("expected a P-256 public key");
+ }
+ // Select the four public members explicitly. Do not spread an exported JWK.
+ return { kty: "EC" as const, crv: "P-256" as const, x: jwk.x, y: jwk.y };
+}
+
+async function registerAgentIdKey(
+ client: AgentMailClient,
+ publicKey: KeyObject,
+ options: { scope?: Scope; name?: string; expiresAt?: Date } = {},
+) {
+ return client.apiKeys.createPublicKey({
+ publicKey: publicJwk(publicKey),
+ scope: options.scope,
+ name: options.name,
+ expiresAt: options.expiresAt,
+ });
+}
+
+// Generate inside your keystore in production. Persist the private-key handle
+// before registration so a crash cannot orphan the returned kid.
+const { privateKey, publicKey } = generateKeyPairSync("ec", {
+ namedCurve: "prime256v1",
+});
+const client = new AgentMailClient({ apiKey: "YOUR_EXISTING_AGENTMAIL_API_KEY" });
+const credential = await registerAgentIdKey(client, publicKey, {
+ name: "production signer",
+ scope: { type: "inbox", id: "agent@example.com" },
+});
+
+const keyRecord = {
+ keystoreHandle: "opaque-keystore-handle",
+ kid: credential.apiKeyId,
+};
+```
+
+
+
+The examples keep a process-local private key only to show the types. In a
+production helper, make the signer accept an opaque keystore handle and return a
+signature; do not make private key bytes an application-level return value.
+
+## Scope and expiry
+
+Omitting `scope` inherits the registering bearer key's exact live scope. An
+explicit scope may be the caller's scope or a live descendant, never an ancestor
+or sibling.
+
+```json title="Organization scope"
+{ "type": "organization" }
+```
+
+```json title="Pod scope"
+{ "type": "pod", "id": "33333333-3333-4333-8333-333333333333" }
+```
+
+```json title="Inbox scope"
+{ "type": "inbox", "id": "agent@example.com" }
+```
+
+For `expires_at`, omission inherits the registering bearer credential's expiry.
+If that bearer does not expire, the public-key credential does not expire. An
+explicit expiry must be in the future and cannot be later than the creator's
+expiry. Scope, key material, AgentID eligibility, and expiry are immutable after
+registration; only `name` can be patched.
+
+## Sign and submit one approval
+
+The protected header and payload are intentionally smaller than a general JWT:
+
+```json title="Protected header"
+{
+ "alg": "ES256",
+ "typ": "agentid-approval+jwt",
+ "kid": "api_key_id returned by registration"
+}
+```
+
+```json title="Signed payload"
+{ "jti": "transaction challenge", "inbox_id": "agent@example.com" }
+```
+
+Do not add `aud`, `iat`, `exp`, `nonce`, `scope`, or any other claim. Do not add
+`jwk`, `jku`, `x5u`, `x5c`, or `crit` to the protected header. The transaction's
+server-side expiry is authoritative. The assertion must be a three-segment
+compact JWS no larger than 2 KiB; `jti` is 1–128 characters and `inbox_id` is
+1–254 characters and must identify an email inbox.
+
+
+
+```python title="Python"
+import httpx
+import jwt
+from cryptography.hazmat.primitives.asymmetric import ec
+
+AGENTID_APPROVE_URL = "https://auth.agentid.com/authorize/approve"
+
+
+def approve_agentid_transaction(
+ *,
+ private_key: ec.EllipticCurvePrivateKey,
+ api_key_id: str,
+ jti: str,
+ inbox_id: str,
+) -> None:
+ assertion = jwt.encode(
+ {"jti": jti, "inbox_id": inbox_id},
+ private_key,
+ algorithm="ES256",
+ headers={"alg": "ES256", "typ": "agentid-approval+jwt", "kid": api_key_id},
+ )
+
+ response = httpx.post(
+ AGENTID_APPROVE_URL,
+ json={"assertion": assertion, "inbox_id": inbox_id},
+ # Deliberately no Authorization header and no browser cookies.
+ headers={"Content-Type": "application/json"},
+ timeout=10,
+ )
+ response.raise_for_status()
+ if response.status_code != 204:
+ raise RuntimeError(f"unexpected approval status {response.status_code}")
+```
+
+```typescript title="TypeScript"
+import type { KeyObject } from "node:crypto";
+import { CompactSign } from "jose";
+
+const AGENTID_APPROVE_URL = "https://auth.agentid.com/authorize/approve";
+
+async function approveAgentIdTransaction(input: {
+ privateKey: KeyObject;
+ apiKeyId: string;
+ jti: string;
+ inboxId: string;
+}): Promise {
+ const payload = new TextEncoder().encode(
+ JSON.stringify({ jti: input.jti, inbox_id: input.inboxId }),
+ );
+ const assertion = await new CompactSign(payload)
+ .setProtectedHeader({
+ alg: "ES256",
+ typ: "agentid-approval+jwt",
+ kid: input.apiKeyId,
+ })
+ .sign(input.privateKey);
+
+ const response = await fetch(AGENTID_APPROVE_URL, {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ // Deliberately no Authorization header, credentials option, or browser cookie.
+ body: JSON.stringify({ assertion, inbox_id: input.inboxId }),
+ });
+ if (response.status !== 204) {
+ throw new Error(`AgentID approval failed with ${response.status}`);
+ }
+}
+```
+
+
+
+The unsigned `inbox_id` in the JSON body is an ergonomic duplicate and must be
+byte-for-byte equal to the signed claim. The server resolves `kid` only against a
+stored public-key credential, verifies the signature, validates the transaction,
+and rechecks the key, organization, scope, inbox, generation, and expiry before
+committing one approval. Concurrent or repeated submissions have one winner.
+
+## List, rename, revoke, and rotate
+
+The generated clients for this contract expose dedicated lifecycle methods.
+Legacy `api_keys.list`, `api_keys.create`, and `api_keys.delete` remain
+bearer-only and have no public-key request member.
+
+
+
+```python title="Python"
+# Public-key list results never include bearer credentials.
+page = client.api_keys.list_public_keys(limit=20)
+
+# Name is the only mutable property.
+renamed = client.api_keys.update_public_key_name(
+ credential.api_key_id,
+ name="production signer 2026-08",
+)
+
+# Rotate by creating the replacement first, deploying its new kid, then deleting old.
+replacement = register_agentid_key(client, replacement_private_key, name="replacement")
+deploy_kid_and_keystore_handle(replacement.api_key_id, replacement_private_key_handle)
+client.api_keys.revoke_public_key(credential.api_key_id)
+```
+
+```typescript title="TypeScript"
+// Public-key list results never include bearer credentials.
+const page = await client.apiKeys.listPublicKeys({ limit: 20 });
+
+// Name is the only mutable property.
+const renamed = await client.apiKeys.updatePublicKeyName(credential.apiKeyId, {
+ name: "production signer 2026-08",
+});
+
+// Rotate by creating the replacement first, deploying its new kid, then deleting old.
+const replacement = await registerAgentIdKey(client, replacementPublicKey, {
+ name: "replacement",
+});
+await deployKidAndKeystoreHandle(replacement.apiKeyId, replacementPrivateKeyHandle);
+await client.apiKeys.revokePublicKey(credential.apiKeyId);
+```
+
+
+
+Registration never updates in place. Even registering identical JWK coordinates
+again returns a new `api_key_id`; store and use that new value as `kid`. Rotation
+is therefore create new, deploy new, then delete old. Never reuse an old `kid`
+for new key material.
+
+For an emergency organization-wide fence, call
+`POST /v0/api-keys/public-keys/agentid-sign-in/revoke-all` with an
+organization-scoped bearer credential and a required UUID `Idempotency-Key`.
+The caller normally needs `api_key_delete`. A verified self-serve agent
+organization may instead use an unrestricted unmanaged bearer credential for
+this emergency operation. The request has no body. Repeating the same UUID
+returns the original `{previous_generation, current_generation, revoked_at}`
+receipt and does not advance the generation twice. A new UUID advances it again.
+Existing rows remain visible with `revoked_at` for audit; individually revoked
+keys are deleted.
+
+
+
+```python title="Python"
+import uuid
+
+receipt = client.api_keys.revoke_all_agent_id_sign_in_keys(
+ idempotency_key=str(uuid.uuid4()),
+)
+print(receipt.previous_generation, receipt.current_generation)
+```
+
+```typescript title="TypeScript"
+import { randomUUID } from "node:crypto";
+
+const receipt = await client.apiKeys.revokeAllAgentIdSignInKeys({
+ idempotencyKey: randomUUID(),
+});
+console.log(receipt.previousGeneration, receipt.currentGeneration);
+```
+
+
+
+## Intent and browser-session limitation
+
+
+ The approval assertion proves that the key holder approved the server-created
+ transaction identified by `jti` for one inbox. It does not prove that the key
+ holder initiated the transaction, controls the browser session, inspected the
+ relying party, or intended the relying party's action.
+
+
+An attacker can start a valid authorization transaction in the attacker's own
+browser, induce an agent to sign that transaction's `jti`, and then continue in
+the same attacker browser session. AgentID's per-transaction cookie binding
+prevents a different browser from continuing the flow, but it does not remove
+this accepted transaction-intent/session-swap residual.
+
+If your product requires intent assurance, bind the displayed relying party and
+transaction to an authenticated, trusted out-of-band instruction before calling
+the signing helper. Do not claim that signature validity alone verifies user
+intent.