All docs
AgentID docs
Run from the app you want to configure:
$ npx @agentmail/agentid-cli initButton name and mark: brand page.
Set up with a coding agent
Give your coding agent this prompt.
Add AgentID sign-in to this project. Read https://www.agentid.com/llms-full.txt for the integration reference, then run `npx @agentmail/agentid-cli init` from the project root and follow its prompts.Add AgentID to your app
AgentID is a standard OpenID Connect provider. If your stack already validates login tokens, this is configuration, not code.
Auth providers#
Pick your service:
Clerk
Clerk’s built-in AgentID connection, step by step with screenshots.
- 01Add AgentID in Clerk
- 02Turn on custom credentials
- 03Create an AgentID application
- 04Paste both keys into Clerk
- 05Add owner scopes (optional)
- 06Let agents finish sign-up
- 07Test the sign-in
- 08Add an initiate login URI
Supabase
Custom provider in Supabase Auth, step by step with screenshots.
- 01Open Custom Providers
- 02Copy your callback URL
- 03Create an AgentID application
- 04Copy both keys
- 05Fill in the provider form
- 06Create and enable it
- 07Keep the owner claims
- 08Sign in
- 09Add an initiate login URI
Auth0
The official AgentID connection from Auth0 Marketplace, step by step.
- 01Find your Auth0 callback URL
- 02Create an AgentID application
- 03Copy both keys
- 04Open AgentID in Auth0 Marketplace
- 05Add the credentials and permissions
- 06Create the connection
- 07Optional: enable PKCE
- 08Turn on AgentID sign-in
- 09Test the sign-in
- 10Add an initiate login URI
Better Auth
A helper package for Better Auth’s Generic OAuth plugin, step by step.
- 01Create an AgentID application
- 02Copy both keys
- 03Point it at AgentID
- 04Add the sign-in button
- 05Add an initiate login URI
- 06Sign in, and let the agent’s browser continue
- 07Optional: ask who owns the agent
Auth.js v4
One provider entry for an existing NextAuth app, step by step.
- 01Create an AgentID application
- 02Save both keys
- 03Add the OAuth provider
- 04Offer AgentID sign-in
- 05Add an initiate login URI
- 06Check the integration
WorkOSNot yet
Not available yet. WorkOS AuthKit can’t add custom sign-in providers, so AgentID needs WorkOS to support it first.
Why it doesn’t work yet, and get notified →Custom OIDC
Any other stack: the values a connector asks for, the defaults to change, and what to store.
- 01Start from discovery
- 02The fields a connector asks for
- 03Branding the button
- 04The initiate login URI
- 05Defaults that will not work
- 06The sign-in is not instant
- 07Mapping claims onto your user
- 08Checking it works
Verifying a token#
Tokens are ES256. Validate against the JWKS and check iss and aud; caching keys by kid is safe.
https://auth.agentid.com/v0/jwks.jsonnpx @agentmail/agentid-cli doctorReference
Endpoints, scopes, claims and lifetimes. Every number here is enforced.
Endpoints#
All on the issuer origin.
- /.well-known/openid-configuration
- Discovery document. Point your OIDC client here and it derives the rest.
- /v0/jwks.json
- Public signing keys, with stable key ids and an overlap window across rotations. The pre-/v0 path still resolves here.
- /v0/authorize
- Starts an Authorization Code sign-in. Open clients require PKCE (S256). Registered clients may omit it; when one sends a challenge, the verifier is enforced.
- /v0/token
- Exchanges the code for an id_token and access_token, enforcing the verifier whenever authorization used PKCE and echoing a supplied nonce in the id_token.
- /v0/userinfo
- Returns identity and granted owner claims for a Bearer access token, re-checked against live state on every call. Token envelope fields and scope are not included.
- /v0/register
- Dynamic client registration (RFC 7591), advertised as registration_endpoint. The console and the CLI call it for you when you create an application.
Scopes#
Space-delimited. Start with openid email profile; no scope falls back to openid email.
- openid
- The agent’s verified subject. Always present, and every sign-in must carry it.
- The agent’s inbox address, plus email_verified.
- profile
- The agent’s display name as name, its address-derived handle as preferred_username, and an opaque identifier for its owner as owner_sub.
Owner scopes disclose the organization’s human owner. Open clients cannot request them.
- owner_profile
- The name of the human who owns the agent, as the owner_name claim.
- owner_email
- That human’s email address, as the owner_email claim, plus owner_email_verified.
Owner scopes need the App: Share Owner permission on the agent’s AgentMail API key. Without it, the agent can’t finish the sign-in alone, and the organization owner has to approve it from their AgentMail account. You pick scopes in your auth provider, which sends them to /authorize, not when you create the application.
Token claims#
Both client types always receive iss, sub, aud, iat, exp, jti and actor_type. Ungranted claims are omitted, not nulled — test for presence.
{ "iss": "https://auth.agentid.com", "aud": "https://yourapp.com", "sub": "lM9vT2aR7sK4qN8wE1xC6bY0uF3hJ5pD9gL2zV7oA4Q", "actor_type": "agent", "email": "support@acme.agentmail.to", "email_verified": true, "iat": 1767225000, "exp": 1767225600, "jti": "9f1c2a44-3e77-4c19-9a2e-6b0d5f8e1c33" }
{ "iss": "https://auth.agentid.com", "aud": "b7d41e0a-2c65-4f8b-9d31-0a5e7c2f4b18", "sub": "lM9vT2aR7sK4qN8wE1xC6bY0uF3hJ5pD9gL2zV7oA4Q", "actor_type": "agent", "scope": "openid email profile owner_profile owner_email", "email": "support@acme.agentmail.to", "email_verified": true, "name": "Acme Support", "preferred_username": "support_acme-agentmail-to", "owner_sub": "oW7xN2pQ4mT8vL1kR6sC9dF3gH5jB0aE2uY4zI6nP8A", "owner_name": "Maya Chen", "owner_email": "maya@acme.com", "owner_email_verified": true, "iat": 1767225000, "exp": 1767225600, "jti": "9f1c2a44-3e77-4c19-9a2e-6b0d5f8e1c33" }
GET https://auth.agentid.com/v0/userinfo Authorization: Bearer <access_token> { "sub": "lM9vT2aR7sK4qN8wE1xC6bY0uF3hJ5pD9gL2zV7oA4Q", "actor_type": "agent", "email": "support@acme.agentmail.to", "email_verified": true, "name": "Acme Support", "preferred_username": "support_acme-agentmail-to", "owner_sub": "oW7xN2pQ4mT8vL1kR6sC9dF3gH5jB0aE2uY4zI6nP8A", "owner_name": "Maya Chen", "owner_email": "maya@acme.com", "owner_email_verified": true }
Registered clients receive owner_name and owner_email when their matching scopes are granted. What your auth library does with them is a separate question: most build the user from the standard OIDC claims and drop the rest, so the claim can be in the token and still missing from your session. Each provider guide names its own fix.
A registered client also gets scope, and its aud is the opaque client_id registration issued rather than a URL.
subAlways- Opaque, 43-character subject identifying an inbox. Stable across clients for the lifetime of that inbox; deleting and recreating the same address produces a new subject.
emailemail scope- The inbox the agent signed in as, verified live when the token is minted. Follows the email scope.
email_verifiedemail scope- Always true, and present exactly when email is. It is never false: granting email already required a live inbox inside the signing credential’s scope.
nameprofile scope- The agent’s display name. Follows the profile scope, and is omitted when the inbox has none set.
preferred_usernameprofile scope- The agent’s handle, derived from its address: support@acme.agentmail.to becomes support_acme-agentmail-to. Follows the profile scope and is always present under it.
owner_subprofile scope- Opaque, 43-character owner identifier under profile. Agents with the same owner share this value across clients. Derived from the pod when it has its own owner, otherwise the organization; it cannot be matched to raw AgentMail API IDs.
actor_typeAlways- Always the literal "agent", under no scope. Every subject this issuer mints is an agent inbox; read this, not a comparison on iss, to apply agent policy downstream.
issAlways- The AgentID issuer URL. Must match the issuer in discovery.
audAlways- Your exact client_id: a URL for open clients, or the issued identifier for registered clients.
iatAlways- Token issue time, in Unix seconds.
expAlways- Token expiration time, in Unix seconds: ten minutes after iat.
jtiAlways- Unique identifier for this ID token.
nonceWhen sent- Echoed verbatim from your authorization request, when you sent one, so you can detect replay.
scopeRegistered client- What this sign-in actually granted, space-delimited. Read it rather than assuming you got what you asked for.
owner_nameowner_profile scope- The name of the human who owns the agent. Follows the owner_profile scope.
owner_emailowner_email scope- That human’s email. Follows the owner_email scope.
owner_email_verifiedowner_email scope- Always true, and present exactly when owner_email is. It is never false: AgentID releases an owner email only after it has been verified.
Lifetimes#
Discovery does not advertise these.
- Sign-in transaction
5 min- 5 minutes from the redirect to /authorize. The waiting page counts it down, and the agent has to approve inside it.
- Authorization code
60 s- 60 seconds, single use. Exchange it as soon as your callback receives it.
- id_token and access_token
10 min- 10 minutes. There are no refresh tokens, so a longer session is one you mint yourself.
- Sign-in key
30 days- 30 days from activation. The credential the agent’s browser holds for the inbox, recorded on AgentMail as an inbox-scoped public key and revoked there. Independent of the bearer key that created it: deleting that key revokes nothing.
- Remembered approval
180 days- 180 days from the last sign-in, per inbox and client. Consent, not a credential: inside it the agent does nothing and the browser continues on its own; a changed redirect URI or scope set asks again. It outlives a revoked key, and a live approval does not mean a key is still usable.
Bars are on a log scale: the key and the approval are months, the other three minutes.
Keep state and code_verifier for at least ten minutes; an early expiry fails the callback as a mismatch.
Signing in with AgentID
What an agent does to complete a sign-in. Request and response shapes are in AgentID sign in on the AgentMail docs.
The waiting page#
An existing AgentID session continues on its own; otherwise the waiting page shows one command to create one. It also shows the auth_token, and the agent has 5 minutes to approve.
Approve#
POST /v0/inboxes/{inbox_id}/authorize
{ "auth_token": "<from the waiting page>", "accept_disclosure": true }Three things end separately.#
- The browser session
- Forgetting a session at auth.agentid.com/sessions removes the sign-in material from that browser alone, and the page lists only the sessions saved in the browser that opens it.
- The key
- Revoking the key,
DELETE /v0/api-keys/{api_key_id}on the AgentMail API, stops new sign-ins with it everywhere. - The app’s session
- Ends when you end it. A revoked key does not sign anyone out of your app.