Skip to content
All docs

AgentID docs

Run from the app you want to configure:

$ npx @agentmail/agentid-cli init
AgentID CLI guide

Button name and mark: brand page.

Set up with a coding agent

Give your coding agent this prompt.

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.

    1. 01Add AgentID in Clerk
    2. 02Turn on custom credentials
    3. 03Create an AgentID application
    4. 04Paste both keys into Clerk
    5. 05Add owner scopes (optional)
    6. 06Let agents finish sign-up
    7. 07Test the sign-in
    8. 08Add an initiate login URI
    Add AgentID to Clerk →
  • Supabase

    Custom provider in Supabase Auth, step by step with screenshots.

    1. 01Open Custom Providers
    2. 02Copy your callback URL
    3. 03Create an AgentID application
    4. 04Copy both keys
    5. 05Fill in the provider form
    6. 06Create and enable it
    7. 07Keep the owner claims
    8. 08Sign in
    9. 09Add an initiate login URI
    Add AgentID to Supabase →
  • Auth0

    The official AgentID connection from Auth0 Marketplace, step by step.

    1. 01Find your Auth0 callback URL
    2. 02Create an AgentID application
    3. 03Copy both keys
    4. 04Open AgentID in Auth0 Marketplace
    5. 05Add the credentials and permissions
    6. 06Create the connection
    7. 07Optional: enable PKCE
    8. 08Turn on AgentID sign-in
    9. 09Test the sign-in
    10. 10Add an initiate login URI
    Add AgentID to Auth0 →
  • Better Auth

    A helper package for Better Auth’s Generic OAuth plugin, step by step.

    1. 01Create an AgentID application
    2. 02Copy both keys
    3. 03Point it at AgentID
    4. 04Add the sign-in button
    5. 05Add an initiate login URI
    6. 06Sign in, and let the agent’s browser continue
    7. 07Optional: ask who owns the agent
    Add AgentID to Better Auth →
  • Auth.js v4

    One provider entry for an existing NextAuth app, step by step.

    1. 01Create an AgentID application
    2. 02Save both keys
    3. 03Add the OAuth provider
    4. 04Offer AgentID sign-in
    5. 05Add an initiate login URI
    6. 06Check the integration
    Add AgentID to Auth.js v4 →
  • 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.

    1. 01Start from discovery
    2. 02The fields a connector asks for
    3. 03Branding the button
    4. 04The initiate login URI
    5. 05Defaults that will not work
    6. 06The sign-in is not instant
    7. 07Mapping claims onto your user
    8. 08Checking it works
    Add AgentID to any OIDC stack →

Verifying a token#

Tokens are ES256. Validate against the JWKS and check iss and aud; caching keys by kid is safe.

jwks
https://auth.agentid.com/v0/jwks.json
terminal
npx @agentmail/agentid-cli doctor

Reference

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.
email
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.

id_token, open client
{
  "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"
}
id_token, registered client
{
  "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 /v0/userinfo, registered client
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#

AgentMail API
POST /v0/inboxes/{inbox_id}/authorize

{ "auth_token": "<from the waiting page>", "accept_disclosure": true }

Wait for active#

AgentMail API
GET /v0/api-keys/{api_key_id}

{ "status": "active" }

Poll until it reads active. The key then lasts 30 days, and the app’s approval is remembered for 180 days: both are under lifetimes.

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.