Available

Accept agents at your own API

Verify an AI agent's access token in your service, check that it was issued for your API, and confirm the one action it is bound to.

When an AI agent calls your API, it presents an access token. This guide shows you how to verify that token in your service, read who is calling, and check the action the agent is bound to. Your service does all of this without calling AuthFI.

Before you begin

You need:

  • A registered agent. See Give an AI agent an identity.
  • Administrator access to the AuthFI console.
  • A JWT library that supports EdDSA. The examples use jose for Node.js and PyJWT for Python.

Give the agent a token for your API

In the console, open Integrations > Applications and register your API as an application. Then grant the agent access to the application registered for your API.

The application's client ID is how a token names your API. It is in the header of the application's page, and under "Client ID" in the Overview section.

The agent then asks for an access token for your API, with the client ID as resource:

curl https://acme.authfi.io/apis/regional/v1/ai/auth/token \
  -d grant_type=client_credentials \
  -d client_id=agt_7f3a9c21 \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  -d client_assertion="$ASSERTION" \
  -d resource="$API_CLIENT_ID"

$ASSERTION is a fresh client assertion, the short statement the agent signs with its own key. Build it as in Give an AI agent an identity. $API_CLIENT_ID is the client ID of your API's application. agt_7f3a9c21 is the agent ID, shown as "Client id" on the agent's Connect tab under Identity > AI Agents. The same tab shows the token endpoint and a worked example of this request.

{
  "access_token": "eyJhbGciOiJFZERTQSIs…",
  "token_type": "Bearer",
  "expires_in": 900,
  "agent_id": "agt_7f3a9c21",
  "type": "autonomous",
  "capabilities": ["transfer:accounts"]
}

An agent with no grant on your API gets invalid_target. It cannot obtain a token that your API would accept.

Verify the access token

In your service, check every access token before you act on it.

CheckExpect
SignatureVerifies with the key named by kid, from https://acme.authfi.io/.well-known/jwks.json
algEdDSA
typat+jwt
isshttps://acme.authfi.io
audYour API's client ID
expIn the future
import { createRemoteJWKSet, jwtVerify } from "jose";

const keys = createRemoteJWKSet(new URL("https://acme.authfi.io/.well-known/jwks.json"));

export async function verifyAgent(token) {
  const { payload } = await jwtVerify(token, keys, {
    issuer: "https://acme.authfi.io",
    audience: process.env.API_CLIENT_ID,
    algorithms: ["EdDSA"],
    typ: "at+jwt",
  });
  return payload;
}

Both libraries cache the key set and fetch it again when they meet a kid they do not know. That is what key rotation needs.

Read who is calling

The verified claims tell you which agent is calling and who is accountable for it.

ClaimTells you
client_idThe agent, always. agt_…
subThe agent, or the person it is acting for
act.subPresent when the agent acts for a person: the agent, as agent:agt_…
agent_typedelegated or autonomous
owner_user_idThe owner of a delegated agent
approval_group_idThe approval group of an autonomous agent
capabilitiesThe names of what the agent was granted
roles, groupsThe agent's own roles and groups

An agent calls either as itself or for a person. To tell the two cases apart, look for act.

Tokensubact.subMeans
Agent on its ownagt_…absentThe agent is acting as itself
Agent for a personthe personagent:agt_…The agent is acting for that person

A token with act is issued after the person approves the agent's request.

Decide what the caller may do from capabilities, and record client_id with everything it does.

Check the action the agent is bound to

For a sensitive action, require a transaction token as well as the access token. A transaction token names one action and its details, and lasts 60 seconds.

CheckExpect
Signature, iss, aud, expAs for the access token
typtxn_token+jwt
subThe same agent as the access token
azd.actionThe action being asked for
azd.resourceThe thing it is being done to
azd.contextThe same details as the request in front of you
{
  "typ": "txn_token+jwt",
  "sub": "agt_7f3a9c21",
  "txn": "0b7d6a52-…",
  "azd": {
    "action": "transfer:accounts",
    "resource": "account:4821",
    "context": { "amount": 1200, "currency": "USD", "account_type": "business" }
  }
}

Compare azd with what the request asks for. A token issued for 1,200 must not carry a request for 12,000. Reject the request if anything differs.

Check that it works

Pass the access token from the first step to the function from the second step, and print the result.

console.log(await verifyAgent(process.env.ACCESS_TOKEN));

The claims are printed. client_id is the agent ID, iss is https://acme.authfi.io, and aud names your API's client ID.

Then pass a token that was issued for another service. Verification fails, because aud does not name your API.

When an agent is revoked

An agent's access token lasts 15 minutes. A revoked agent loses access to your API within that time, with nothing for you to do. To reject it sooner, receive revocation events.

Troubleshooting

What you seeCauseFix
invalid_target from the token endpointThe agent has no grant on your APIGrant the agent access to the application registered for your API
invalid_client from the token endpointThe agent is unknown or revoked, or its assertion did not verifyCheck the key ID, the audience and the clock. Use a new jti each time.
Verification fails on the audienceThe token was issued for another serviceHave the agent send your API's client ID as resource
Verification fails on a kid you do not knowThe project's signing key was rotatedFetch the key set again, then cache it

Next steps