> Markdown version of https://authpi.com/docs/quickstarts/agents/ — fetch the complete AuthPI docs index at https://authpi.com/llms.txt to discover all available pages.

# Give an AI agent an identity in 10 minutes

Create an agent identity, add a secret verifier, mint a five-minute OAuth token via client_credentials, and validate it in your API with jose.

## Overview

By the end of this tutorial an AI agent will have its own identity in AuthPI — not a borrowed user account, not a shared API key. The flow: create the agent → give it a secret credential → exchange the credential for a short-lived token via the standard `client_credentials` grant → validate the token server-side.

### Prerequisites

- An AuthPI account and an **Issuer** (`i_...`) created in the [console](https://console.authpi.com)
- A Core API key with write access to the issuer

Export everything once so the commands below are copy-paste:

```bash
export AUTHPI_KEY_ID="key_..."        # API key ID
export AUTHPI_KEY_SECRET="..."        # API key secret
export ACCOUNT_ID="acc_..."           # your account
export ISSUER_ID="i_..."              # your issuer
```

API keys authenticate with HTTP Basic auth — the key ID is the username, the secret is the password (curl's `-u` builds the header for you).

## Step 1 — Create the agent

```bash
curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "name": "invoice-reader", "scopes": ["invoices:read"] }'
```

```json
{
  "data": {
    "id": "agt_9f2c1a7e4b3d48f6a0c5e8d2b7a91c3f",
    "name": "invoice-reader",
    "status": "active",
    "scopes": ["invoices:read"],
    "created_at": 1781222400000
  }
}
```

`scopes` is the agent's full allowed set — tokens can request a subset of these, never more. Save the ID:

```bash
export AGENT_ID="agt_9f2c1a7e4b3d48f6a0c5e8d2b7a91c3f"
```

## Step 2 — Add a secret verifier

The agent needs a credential to authenticate with. A `secret` verifier generates one server-side:

```bash
curl -X POST "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents/$AGENT_ID/verifiers" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "type": "secret", "name": "pipeline-credential" }'
```

```json
{
  "data": {
    "id": "v_5e8d2b7a91c3f49f2c1a7e4b3d48f6a0",
    "type": "secret",
    "status": "active",
    "name": "pipeline-credential",
    "secret": "mJ4kP9xQ2wL7nR5tY8vB3cD6fG1hS0aZ4eU7iO2pXq",
    "created_at": 1781222400000
  }
}
```

**Important:** the `secret` field is returned only once, at creation. AuthPI stores a SHA-256 hash and cannot show it again — capture it before moving on.

## Step 3 — Mint a token via client_credentials

The agent exchanges its ID and secret at your issuer's token endpoint — the standard OAuth 2.0 `client_credentials` grant (RFC 6749 §4.4), with the `agt_` ID as the `client_id`:

```bash
export AGENT_SECRET="mJ4kP9xQ2wL7nR5tY8vB3cD6fG1hS0aZ4eU7iO2pXq"  # from step 2

curl -X POST "https://idp.authpi.com/$ISSUER_ID/token" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=$AGENT_ID" \
  --data-urlencode "client_secret=$AGENT_SECRET" \
  --data-urlencode "scope=invoices:read" \
  --data-urlencode "resource=https://api.example.com"
```

```json
{
  "access_token": "eyJhbGciOiJFZERTQSIsImtpZCI6Ii4uLiJ9.eyJpc3MiOi4uLn0...",
  "token_type": "Bearer",
  "expires_in": 300,
  "scope": "invoices:read"
}
```

Agent tokens live **5 minutes** (`expires_in: 300`) by design — agents re-mint per work cycle, so scope changes and suspensions propagate within one TTL window. No refresh token or ID token is issued. `resource` sets the token's `aud` claim (it defaults to the agent's own ID if omitted); pin it to your API so the token can't be replayed elsewhere.

## Step 4 — Call a protected endpoint

The token is a standard Bearer JWT — the agent sends it to *your* API like any OAuth client. Inside, `sub` is the `agt_` ID and a `dat` (data attestation) claim of `{"type": "agent"}` marks it as a non-human identity:

```bash
export AGENT_TOKEN="eyJhbGciOiJFZERTQSIs..."   # the access_token from step 3

curl https://api.example.com/invoices \
  -H "Authorization: Bearer $AGENT_TOKEN"
```

## Step 5 — Validate the token in your API

Verify the signature against your issuer's JWKS and check the claims. With [jose](https://github.com/panva/jose):

```typescript
import { createRemoteJWKSet, jwtVerify } from "jose";

const ISSUER = `https://idp.authpi.com/${process.env.ISSUER_ID}`; // e.g. .../i_8fK2mQp4xR7tWz
const JWKS = createRemoteJWKSet(new URL(`${ISSUER}/jwks.json`));

export async function verifyAgentToken(bearer: string) {
  // jwtVerify checks signature, iss, aud, and exp
  const { payload } = await jwtVerify(bearer, JWKS, {
    issuer: ISSUER,
    audience: "https://api.example.com", // the `resource` the token was minted for
  });
  // Agent guard: dat.type is the semantic check, the sub prefix is defense in depth
  const dat = payload.dat as { type?: string } | undefined;
  if (dat?.type !== "agent" || !String(payload.sub).startsWith("agt_")) {
    throw new Error("Not an agent token");
  }
  return { agentId: payload.sub as string, scopes: String(payload.scope ?? "").split(" ").filter(Boolean) };
}
```

A request without a token, with an expired token (remember: 5 minutes), or with a human user's token (`dat.type === "identity"`) all fail this check.

## Step 6 — Clean up

Deleting the agent removes its verifiers, KV cache entries, and listing index entry in one call. The response is `204 No Content`; tokens already minted stay valid until they expire — at most 5 minutes.

```bash
curl -X DELETE "https://api.authpi.com/v1/accounts/$ACCOUNT_ID/issuers/$ISSUER_ID/agents/$AGENT_ID" \
  -u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET"
```

## Next steps

- [Authenticate x402 agents](/docs/guides/x402-agent-auth/) — add wallet verifiers and resolve paying wallets to agent identities
- [Agents](/docs/concepts/agents/) — the full concept model: lifecycle, verifiers, scopes
- [Agents — Core API reference](/docs/reference/core-api/agents/) — every endpoint and field
- [Token claims](/docs/reference/token-claims/) — everything inside an AuthPI access token