Skip to content

Add AgentID to your app

AgentID is a standard OpenID Connect provider. If your stack already validates login tokens, everything below is configuration rather than code.

On this page

Putting the button on your login page? The name and the downloadable mark are on the brand page.

Auth providers#

Most apps do not implement OIDC themselves. AgentID goes in as a custom provider in each of the services below, and each has its own guide.

Clerk
Custom OAuth provider, walked end to end with screenshots. Needs a registered client and a secret.
Supabase
Custom OIDC provider with auto-discovery, walked end to end with screenshots. Needs a registered client and a secret.
Auth0
Custom social connection, included on every plan, walked end to end. Needs a registered client and secret; its required PKCE setup uses the Management API.
Better Auth
Generic OAuth plugin, walked end to end. Needs a registered client and a secret; the auth method is a config line the plugin does not default to.

Anywhere else — a generic OIDC connector, a library, or a client you wrote — takes the same provider through one guide: custom and generic OIDC. The contract those are configured against starts at manual setup.

Manual setup#

If you implement OIDC yourself, point your existing client at the issuer. Your client_id can be a URL you control, so there is nothing to register to get started.

issuer
https://auth.agentid.com

Send the agent to /authorize the way you would any other provider. PKCE is required, and code_challenge_method must be S256.

authorization request
GET https://auth.agentid.com/v0/authorize
  ?response_type=code
  &client_id=https://yourapp.com
  &redirect_uri=https://yourapp.com/callback
  &scope=openid%20email
  &state=<opaque>
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256

The agent approves the request out of band. Your callback then exchanges the code exactly as it would anywhere else.

token exchange
POST https://auth.agentid.com/v0/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<code>
&redirect_uri=https://yourapp.com/callback
&client_id=https://yourapp.com
&code_verifier=<verifier>

What a connector or an OIDC library needs on top of these two requests — which of its defaults this issuer refuses, and what to map onto your user record — is on custom and generic OIDC.

Endpoints#

All served from the issuer origin. Derive them from discovery rather than hard-coding them, so rotations and additions reach you for free.

/.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. Read it from jwks_uri rather than hardcoding it; the pre-/v0 path still resolves here.
/v0/authorize
Starts an Authorization Code sign-in. Open clients require PKCE (S256); registered clients require either S256 PKCE or an OIDC nonce.
/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
Claims for a Bearer access token, re-derived from the live inbox on every call. The only surface that serves the owner claims.
/v0/register
Dynamic client registration (RFC 7591). Only needed for the registered tier.

Scopes#

Space-delimited, as a single string. Choose the scopes your app actually needs in your auth provider’s configuration; registered clients may request any supported scope at sign-in. Start with openid email profile. A request without a scope falls back to openid and 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 the name claim. Registered clients only.

On top of that sits one decision, which the two scopes below answer together: may this app learn who the human behind the agent is? They disclose the human who owns the AgentMail organization, not anything about the agent’s own inbox, and they are the only scopes an open client cannot reach at all.

owner_profile
The name of the human who owns the organization behind the agent, as the owner_name claim.
owner_email
That human’s email address, as the owner_email claim.

The agent’s signing credential remains the final gate for owner information. If it does not carry the matching permission, a sign-in requesting an owner scope fails instead of returning a silently thinner identity. Scope is selected at /authorize, not during client registration. Open clients cannot request owner scopes.

Token claims#

Every id_token carries the set below. Scope-gated claims are omitted rather than nulled when the scope was not granted, so test for presence, not for an empty value.

sub
Stable subject derived from the inbox. The same agent gets the same sub every time.
email
The inbox the agent signed in as, verified live when the token is minted. Follows the email scope.
email_verified
Always true when present. Granting email already required a live inbox inside the signing credential’s scope.
iss / aud / exp / iat / jti
Standard OIDC claims. aud is your exact client_id; jti names the single sign-in.
nonce
Echoed verbatim from your authorization request, when you sent one, so you can detect replay.
id_token, open client (decoded)
{
  "iss": "https://auth.agentid.com",
  "aud": "https://yourapp.com",
  "sub": "a91f2c8e...43",
  "email": "support@acme.agentmail.to",
  "email_verified": true,
  "iat": 1767225000,
  "exp": 1767225600,
  "jti": "9f1c2a44-3e77-4c19-9a2e-6b0d5f8e1c33"
}

A registered client gets three more, and its aud is the opaque client_id registration issued rather than a URL.

org
The agent’s organization id. Allowlist on this to accept sign-ins from only certain organizations.
scope
What this sign-in actually granted, space-delimited. Read it rather than assuming you got what you asked for.
name
The agent’s display name. Follows the profile scope, and is omitted when the inbox has none set.
id_token, registered client (decoded)
{
  "iss": "https://auth.agentid.com",
  "aud": "b7d41e0a-2c65-4f8b-9d31-0a5e7c2f4b18",
  "sub": "a91f2c8e...43",
  "org": "2b7c9f10-84a3-4e52-b6d7-1f9c3e5a70d4",
  "scope": "openid email profile owner_email",
  "email": "support@acme.agentmail.to",
  "email_verified": true,
  "name": "Acme Support",
  "iat": 1767225000,
  "exp": 1767225600,
  "jti": "9f1c2a44-3e77-4c19-9a2e-6b0d5f8e1c33"
}

The owner claims are not in that token, and no id_token can carry them. They are held on the grant and served from /userinfo, so a client holding owner_email reads it there with the access token from the same exchange. Decoding the id_token and finding no owner_email is the expected result, not a broken integration.

owner_name
The name of the human who owns the agent. Follows the owner_profile scope.
owner_email
That human’s email. Follows the owner_email scope.
GET /v0/userinfo, registered client
GET https://auth.agentid.com/v0/userinfo
Authorization: Bearer <access_token>

{
  "sub": "a91f2c8e...43",
  "org": "2b7c9f10-84a3-4e52-b6d7-1f9c3e5a70d4",
  "email": "support@acme.agentmail.to",
  "email_verified": true,
  "name": "Acme Support",
  "owner_name": "Maya Chen",
  "owner_email": "maya@acme.com"
}

Verifying a token#

Tokens are signed with ES256. Validate against the published JWKS using whichever JOSE library you already have, and check iss and aud as you would for any provider. Keys rotate with stable key ids and an overlap window, so caching JWKS by kid is safe.

jwks
https://auth.agentid.com/v0/jwks.json

Client tiers#

Open is the default and needs no onboarding: your client_id is an https URL you control, your redirect URI sits on that same origin, and you can request openid and email. There is no secret and so nothing to configure for token endpoint authentication; code_challenge is therefore required for this tier. The origin is the whole trust anchor, which is also why an open client cannot run on a dev machine: http and loopback hosts are a registered-client allowance, so localhost development starts at registration.

Registering makes you a confidential client. The call is gated — you authenticate it with your own AgentMail API key — and it grants:

Owner information when requested
owner_profile and owner_email are selected in your auth provider, not during console registration. Open clients cannot reach them.
A client secret
Confidential token exchange, via client_secret_basic or client_secret_post.
Up to 10 redirect URIs
An allowlist you control, rather than the single URL that identifies an open client.
Per-client sign-in history
Every sign-in against your client, with the agent’s subject, the scopes granted, and when.
Editable registration
Name, redirect URIs, display metadata, and auth method can all be changed after the fact.

Your sign-in history obeys the same scopes as everything else: the agent’s email appears in it only if your client holds email, and its display name only if you hold profile. The owner name and email are checked twice more, against what your client still declares and what the agent’s credential still permits, so dropping a scope stops it surfacing even in history you collected while you held it. Registering does not widen what you can see about an agent beyond what it granted you.

Watch out. Requesting owner information does not guarantee it arrives. The agent’s signing credential has to carry the matching permission, and the organization behind it needs exactly one designated owner holding the value. When a sign-in asks for it and the credential does not permit it, that sign-in fails for the agent with a 403 rather than handing you a token quietly missing the claim, so a client that looks misconfigured is usually waiting on a permission the agent has not been granted.

Registering a client#

Optional. Register when you need an elevated scope or a confidential client with a secret. Registration is one call (RFC 7591), authenticated with your own AgentMail API key as the initial access token.

POST /v0/register
POST https://auth.agentid.com/v0/register
Authorization: Bearer <your AgentMail API key>
Content-Type: application/json

{
  "client_name": "My Tool",
  "redirect_uris": ["https://yourapp.com/callback"],
  "token_endpoint_auth_method": "client_secret_basic"
}

Up to 10 redirect URIs. The response carries the client_secret once and it cannot be retrieved again, so store it when you receive it.

Watch out. Registration-time scope metadata is ignored. Configure the scopes sent to /authorize in your OIDC integration, and request an owner scope only when the app needs the human owner’s identity.

Building the agent side? Approving a sign-in is a separate contract, addressed to the agent: approving a sign-in.