An AI agent proves who it is the same way a person does with Sign in with Google: with a signed token from an identity provider your app already knows how to check. This post walks the sign-in from the first redirect to the token, lists everything that token says about the agent and its owner, and explains what your app should do once the agent is inside.
An AI agent proves its identity to your app with a signed token from a standard OpenID Connect sign-in, which your app checks against the issuer's published keys without having to trust the agent. With AgentID, the sign-in for AI agents from AgentMail, that token names the agent, marks it as an agent, and for registered apps names the human who owns it.
Most agents today prove nothing of the kind. They arrive with a borrowed password, a pasted key or a user agent string, and each of those says what the agent claims rather than what someone else will vouch for. A sign-in flips that around. An issuer your app already recognizes does the checking, and your app only has to verify one signature and read a handful of fields. The sections below follow a single sign-in from start to finish and then cover what the token can and can't tell you.
What happens between the redirect and the token?
Five steps happen between the redirect and the token, and your app writes code for two of them. The sequence is the ordinary OIDC authorization code flow; the complete guide to OIDC for AI agents covers the protocol background, and this section stays on what the agent and the app each do.
- Your app redirects the agent's browser to
https://auth.agentid.com/v0/authorizewith a standard authorization code request. - The agent's browser continues or waits. If it already holds an AgentID session, it continues on its own. If not, the waiting page shows one command the agent runs to create one.
- The agent approves inside a sign-in transaction that lasts 5 minutes. The proof is a signature over a single server-generated transaction that is consumed exactly once, made with a key that stays in the agent's browser.
- AgentID redirects back to your callback with an authorization code that is valid for 60 seconds and works once.
- Your server exchanges the code at
/v0/token, as usual, and receives an ES256-signed id_token and an access token. Both are valid for 10 minutes.
No password and no API key crosses this flow, and your app never receives an AgentMail key it could store and reuse. The agent's approval is remembered for 180 days per inbox and app, so later sign-ins to the same app complete without the agent doing anything unless your redirect URI or scopes change.
What does the token contain?
The token contains the agent's identity in every case and the owner's identity only when a registered app asked for it. Seven claims are always present; the rest depend on the scopes your app requested and whether it is registered.
| Claim | When present | Meaning |
|---|---|---|
iss | Always | https://auth.agentid.com |
sub | Always | Opaque 43-character subject for the agent's inbox, stable across apps |
aud | Always | Your exact client ID: a URL for open clients, an issued ID for registered ones |
iat, exp | Always | Issued and expiry times, 10 minutes apart |
jti | Always | Unique ID for this token |
actor_type | Always | The literal "agent" |
nonce | When your app sent one | Echoed back for replay detection |
email | email scope | The agent's inbox, verified live when the token is minted |
email_verified | With email | Always true |
name | profile scope | The agent's display name, omitted if none is set |
preferred_username | profile scope | A handle built from the address |
owner_sub | profile scope | Opaque 43-character owner ID, shared by agents with the same owner |
scope | Registered clients | What was actually granted |
owner_name | owner_profile, registered | The owner's name |
owner_email | owner_email, registered | The owner's email address |
Here is one, decoded. It belongs to a registered app that asked for every scope:
{
"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",
"iat": 1767225000,
"exp": 1767225600,
"jti": "9f1c2a44-3e77-4c19-9a2e-6b0d5f8e1c33"
}The aud here is an issued ID, not a URL, because owner claims only ever go to registered apps. An open client's token has its own URL as aud and never carries owner_name or owner_email.
What should your app do when a claim is missing?
Your app should treat a missing claim as not granted and test for presence rather than for null. AgentID omits claims it didn't grant instead of sending them empty, so name is simply absent for an agent with no display name, and owner_sub is absent when your app didn't request profile. With no scope at all, the issuer falls back to openid email.
The owner claims have one guarantee worth relying on. A registered app that requested owner_email never receives a successful sign-in without it. If the agent's key isn't allowed to share its owner, the agent can't finish the sign-in alone, and the organization owner has to approve it from their AgentMail account.
How do you verify the token is genuine?
You verify the token the way you verify any OIDC id_token. There are five checks, and none of them involves the agent:
- The ES256 signature matches a key in
https://auth.agentid.com/v0/jwks.json. issis exactlyhttps://auth.agentid.com.audis exactly your client ID.exphasn't passed.noncematches the one you sent, if you sent one.
The signing key lives in AWS KMS and can't be exported, and the published keys keep stable IDs with an overlap window, so key rotation doesn't break tokens already in flight. Once the checks pass, key the account on sub, not on the email. Deleting an inbox and recreating the same address produces a new sub, which is how you tell a new agent from an old one that happens to share an address. The endpoint and claim reference is in the AgentID docs, and the key handling is described on the security page.
How do you know which agent is calling your API after sign-in?
You know because your app ties its own session to the sub it just verified. AgentID's part ends at sign-in: the flow is authorization code only, its tokens expire after 10 minutes, and there are no refresh tokens, so they are not built to be a long-lived API credential. Your app issues the session or API key it would issue any user, with a lifetime you choose, and records the sub and owner_sub against it.
Every request under that session is then attributable to one agent and, through owner_sub, to one owner. That is what makes rate limits per human and per-owner signup caps possible. When you want fresh confirmation, call /v0/userinfo within the 10-minute window, which re-checks live state, or run a new sign-in, which completes on its own inside the remembered approval.
What does the token not prove?
The token doesn't prove the agent is safe. It proves which agent this is and, for registered apps, which human is accountable for it, and it carries no trust score or endorsement of any kind. Your abuse controls still run; they just have a stable key to run on.
It also grants nothing beyond identity. Signing in gives your app no access to the agent's mailbox, so you can't read or send its mail. You can write to it, though, because the address is a working inbox, and agents on the other side of this flow are covered on the AgentID page on AgentMail.
AgentID gives your agent a verified identity and its own email address. Free for apps to add.


