Skip to navigation

AgentID Public-Key Authentication

Register scoped public keys and manage credentials for your agents.

Register a public P-256 key when your application manages its own signing key. AgentMail stores the public key and returns an api_key_id; the private key stays in your application or keystore.

For provider sign-in, start with AgentID Sign-In. Connect and authorize create sign-in keys for you. This guide covers registering and managing keys you generate yourself.

Before you start

Use a bearer API key with api_key_create to register keys. Reading, updating, and deleting keys require api_key_read, api_key_update, and api_key_delete, respectively.

Install the TypeScript SDK with npm install agentmail@latest, or the CLI with npm install -g agentmail-cli@latest. Set AGENTMAIL_API_KEY in your environment.

The examples use TypeScript SDK 0.5.25 and CLI 1.4.0. Python SDK 0.5.10 still exposes the previous public-key endpoints. The Python example uses the current HTTP route with httpx; install its dependencies with pip install httpx cryptography.

Generate and store private keys in trusted code or a keystore. Keep private key material out of model context, tool output, and logs. Register only the four public JWK fields: kty, crv, x, and y.

Generate and register a key

The following examples generate a P-256 key and register it for one inbox. They keep the private key in memory to demonstrate key generation; persist it securely before registration in production.

import base64
import os
import httpx
from cryptography.hazmat.primitives.asymmetric import ec
private_key = ec.generate_private_key(ec.SECP256R1())
numbers = private_key.public_key().public_numbers()
def coordinate(value: int) -> str:
return base64.urlsafe_b64encode(value.to_bytes(32, "big")).rstrip(b"=").decode()
public_key = {
"kty": "EC", "crv": "P-256",
"x": coordinate(numbers.x), "y": coordinate(numbers.y),
}
response = httpx.post(
"https://api.agentmail.to/v0/inboxes/agent%40example.com/api-keys",
headers={"Authorization": f"Bearer {os.environ['AGENTMAIL_API_KEY']}"},
json={"public_key": public_key, "client_id": "production-signer-v1",
"name": "production signer", "permissions": {"provider_connect": True}},
timeout=30,
)
response.raise_for_status()
print(response.json()["api_key_id"])

To register an existing public JWK with the CLI, put only those four public fields in public-jwk.json:

CLI
agentmail inboxes api-keys create \
--inbox-id agent@example.com \
--name "production signer" \
--public-key "$(cat public-jwk.json)" \
--permissions '{"provider_connect":true}'

The response has type: "public_key", an api_key_id, and the public key’s server-computed fingerprint. It does not contain a bearer secret. Registered keys have no status; sign-in keys created by connect or authorize do.

The API rejects private JWK fields such as d, extra JWK members, other curves, and invalid P-256 coordinates.

Choose the scope

The route determines the key’s scope:

ScopeRegistration pathTypeScript methodCLI command
Caller’s scopePOST /v0/api-keysclient.apiKeys.create(request)agentmail api-keys create
PodPOST /v0/pods/{pod_id}/api-keysclient.pods.apiKeys.create(podId, request)agentmail pods api-keys create --pod-id <pod_id>
InboxPOST /v0/inboxes/{inbox_id}/api-keysclient.inboxes.apiKeys.create(inboxId, request)agentmail inboxes api-keys create --inbox-id <inbox_id>

Include public_key in the request (TypeScript: publicKey; CLI: --public-key) to register a public key. A request without it creates a bearer key. The target scope must be accessible to the creating key; pod_id and inbox_id in the response identify the resulting scope.

Permissions, expiry, and client IDs

  • permissions defaults to the creating key’s permissions. Explicit grants cannot exceed what that key holds.
  • expires_at defaults to the creating key’s expiry. An explicit expiry must be in the future and cannot exceed the creator’s expiry. If the creator has no expiry and none is supplied, the registered key does not expire.
  • Registered public keys are independent of their creator afterward. Changing or deleting the bearer key does not change or revoke them.
  • Update name and permissions with PATCH /v0/api-keys/{api_key_id}. Key material, type, scope, and expiry are immutable.
  • An optional client_id is a caller-chosen alias, unique within the organization. It must be URL-safe and contain no slash or @. You can use it instead of api_key_id to get, update, or delete a public key.
  • Registration with the same client_id and matching key returns the existing credential; a conflicting registration returns 409. Persist and reuse the key pair when retrying registration, rather than generating a new key for the same alias.

List, update, and revoke

Use the same API Keys endpoints for bearer keys, registered public keys, and sign-in keys:

CLI
# list public keys
agentmail api-keys list --type public_key
# inspect and rename a key
agentmail api-keys get --api-key-id 4d795cc3-ae87-4f84-85e3-4ad4ca656f44
agentmail api-keys update \
--api-key-id 4d795cc3-ae87-4f84-85e3-4ad4ca656f44 \
--name "production signer v2"
# revoke the key when it is no longer needed
agentmail api-keys delete --api-key-id 4d795cc3-ae87-4f84-85e3-4ad4ca656f44
OperationHTTP requestTypeScript method
List public keysGET /v0/api-keys?type=public_keyclient.apiKeys.list({ type: "public_key" })
Get a keyGET /v0/api-keys/{api_key_id}client.apiKeys.get(apiKeyId)
Update a keyPATCH /v0/api-keys/{api_key_id}client.apiKeys.update(apiKeyId, request)
Delete a keyDELETE /v0/api-keys/{api_key_id}client.apiKeys.delete(apiKeyId)

Follow next_page_token until it is absent, including when a page is empty. Pass it as page_token in HTTP, pageToken in TypeScript, or --page-token in the CLI.

To rotate a key, generate and persist a replacement, register it with a new client_id, deploy and verify it, then delete the old key. To revoke multiple keys, list the intended credentials and delete each one individually.

See the API Keys reference for complete request and response fields.