---
title: "Authentication"
description: "Organizations, API keys, and the difference between an org key and an agent key."
canonical_url: "https://www.agent-identity.dev/docs/getting-started/authentication"
markdown_url: "https://www.agent-identity.dev/docs/getting-started/authentication.md"
last_updated: "2018-10-20"
x_farming_labs_generated_preamble: true
agent:
  tokenBudget: 800
  task: "Obtain the right kind of API key for an operation and authenticate a request with it."
  outcome: "Requests carry `Authorization: Bearer aid_live_…`; administrative calls use an org key and agent calls use a key created with an identity_id."
  appliesTo:
    package:
      - "@aid/api"
      - "@agentidentity/sdk"
  prerequisites:
    - "An organization, created by POST /v1/organizations or POST /v1/auth/signup."
    - "For an agent key, an existing agent to scope it to."
  files:
    - "apps/api/src/auth.ts"
    - "packages/auth/src/index.ts"
  sideEffects:
    - "A created key is returned in full exactly once and stored hashed; no endpoint will show it again."
  verification:
    - description: "Confirm which actor a key resolves to."
      expect: "GET /v1/me returns the org, and an identity_id when it is an agent key."
  rollback:
    - "POST /v1/api-keys/{id}/revoke invalidates a key immediately. Issue a replacement first if the agent is running."
  failureModes:
    - symptom: "401 auth.unauthorized on every request."
      resolution: "The key is missing, revoked, or from another organization. Confirm with GET /v1/me and reissue if needed."
    - symptom: "403 on suspending an agent or managing A2A access rules with a working key."
      resolution: "Those need an org key; an agent key cannot perform them. Use the org key."
---

# Authentication
URL: /docs/getting-started/authentication
LLM index: /llms.txt
Description: Organizations, API keys, and the difference between an org key and an agent key.
Related: /docs/concepts/identities, /docs/getting-started/quickstart, /docs/a2a/access

<!-- farming-labs:agent-contract:start -->
## Agent Contract

Task: Obtain the right kind of API key for an operation and authenticate a request with it.
Outcome: Requests carry `Authorization: Bearer aid_live_…`; administrative calls use an org key and agent calls use a key created with an identity_id.

### Applies To

- Package: `@aid/api`, `@agentidentity/sdk`

### Prerequisites

- An organization, created by POST /v1/organizations or POST /v1/auth/signup.
- For an agent key, an existing agent to scope it to.

### Files

- `apps/api/src/auth.ts`
- `packages/auth/src/index.ts`

### Side Effects

- A created key is returned in full exactly once and stored hashed; no endpoint will show it again.

### Verification

- Confirm which actor a key resolves to.
  - Expected: GET /v1/me returns the org, and an identity_id when it is an agent key.

### Rollback

- POST /v1/api-keys/{id}/revoke invalidates a key immediately. Issue a replacement first if the agent is running.

### Failure Modes

- 401 auth.unauthorized on every request. — Recovery: The key is missing, revoked, or from another organization. Confirm with GET /v1/me and reissue if needed.
- 403 on suspending an agent or managing A2A access rules with a working key. — Recovery: Those need an org key; an agent key cannot perform them. Use the org key.
<!-- farming-labs:agent-contract:end -->

# Authentication

Every authenticated request carries a bearer token:

```http
Authorization: Bearer aid_live_…
```

Keys are stored hashed. The full value is returned exactly once, at creation, and no
endpoint will show it again.

## Two kinds of key

The distinction matters more than it looks, because it decides what a request is allowed
to do:

**Org key** — created by `POST /v1/organizations` or `POST /v1/auth/signup`, with no
`identity_id`. It acts as the organization. Administrative operations — suspending an
agent, deciding who can reach an agent over A2A, adding domains, creating provider accounts
— require one.

**Agent key** — created by `POST /v1/api-keys` with an `identity_id`. It acts *as that
one agent*. This is what you give the running agent process. Sending an A2A task, and
moving a task's state, require the agent's own key rather than an org key. An agent key can only
be made for an agent of your own organization: asking for one for anyone else's is `404`, and a key
that somehow names another organization's agent is treated as no key at all.

`GET /v1/usage` (`client.usage()`) reports what the organization has used against its limits: email
sent today against the daily limit, when it resets, whether the owner is verified, and agents against
the cap. Any key of the organization may read it.

The API resolves one actor from the key and derives the organization from it. There is no
way to act across organizations, and no request parameter that changes which organization
you are in. The only cross-org reads in the whole API are the public agent card and the
A2A directory, both of which are deliberately public.

## Getting the first key

```ts title="signup.ts"
import { createOrganization, verifyOwner } from "@agentidentity/sdk";

const org = await createOrganization("https://api.agent-identity.dev", {
  name: "Acme",
  slug: "acme",
  ownerEmail: "you@acme.com",
});
await verifyOwner("https://api.agent-identity.dev", org.adminApiKey, "481923");
```

Or the same thing over HTTP:

```bash title="terminal"
curl -sS -X POST https://api.agent-identity.dev/v1/organizations \
  -H 'content-type: application/json' \
  -d '{"name":"Acme","slug":"acme","owner_email":"you@acme.com"}'
```

Returns `admin_api_key`, once. Naming an owner emails them a six-digit code, which you
return to `POST /v1/auth/verify` to lift the limits on an unverified organization.

Alternatively `POST /v1/auth/signup` creates the organization *and* a user login. The address
has to prove it is real first, so it is two steps:

```ts title="signup.ts"
import { startSignUp, verifySignUp } from "@agentidentity/sdk";

await startSignUp(baseUrl, { orgName: "Acme", orgSlug: "acme", email: "you@acme.com", password });
// a 6-digit code is emailed; nothing exists yet
const session = await verifySignUp(baseUrl, { email: "you@acme.com", code: "481923" });
session.apiKey; // the account, the organization and this key now exist
```

`POST /v1/auth/signup` answers `202 verification_required` and creates nothing;
`POST /v1/auth/signup/verify` with the code answers `201` with the `api_key`, and the address
is the organization's verified owner. Codes last 15 minutes, five wrong guesses burn one,
`POST /v1/auth/signup/resend` sends another (5 an hour per address), and one network address can
start 30 signups an hour. Existing accounts are unaffected and sign in as before. (`signUp()`
throws where a code is required, since there is no session yet; use the two calls above.) Use signup when a human will also sign into
the dashboard; use `POST /v1/organizations` for machine-only setups.
`POST /v1/auth/claim` attaches a human login to an organization that was bootstrapped
without one.

## Issuing an agent key

```ts title="agent-key.ts"
const { key } = await client.apiKeys.create({ name: "support-agent runtime", identityId });
```

```bash title="terminal"
curl -sS -X POST https://api.agent-identity.dev/v1/api-keys \
  -H "authorization: Bearer $AID_KEY" \
  -H 'content-type: application/json' \
  -d '{"name":"support-agent runtime","identity_id":"6f21…"}'
```

```json
{ "id": "a41c…", "key": "aid_live_…", "key_prefix": "aid_live_a41c" }
```

Store `key` immediately. `key_prefix` is what the list endpoint and the dashboard show
afterwards, so it is worth recording alongside the name to keep keys identifiable.

`GET /v1/api-keys` lists keys without their secrets.
`POST /v1/api-keys/:id/revoke` invalidates one immediately.
`GET /v1/me` returns the actor behind the current key — the fastest way to answer
"which key is this, and is it an org key or an agent key?".

## Errors

A missing, malformed, revoked, or foreign key all produce the same response:

```json
{
  "error": {
    "code": "auth.unauthorized",
    "message": "Missing or invalid API key",
    "request_id": "req-…",
    "retryable": false
  }
}
```

`401` means the request had no usable actor. `403` means the actor was recognised but is
not allowed to do this — an agent key attempting an org-admin operation, or an agent
reaching for another identity's resources. Treat them differently: the first is a
credentials problem, the second is a wrong-key-for-the-job problem.

Unauthenticated endpoints are rate limited per IP (30 per minute); authenticated ones are
limited per organization (600 per minute). Exceeding either returns `429`.

Choosing the key for a request:

- Org key (no `identity_id`): `/v1/identities/*` administration, `/v1/api-keys`,
  `/v1/webhooks`, `/v1/domains`, `/v1/provider-accounts`, `/v1/contact-rules`,
  `/v1/a2a/contact-rules` writes, `/v1/a2a/invitations/*`, and `publicly_discoverable` /
  `filter_mode` in `PUT /v1/identities/:id/a2a/settings`.
- Agent key (bound to `identity_id`): `POST /v1/a2a/tasks`, `POST /v1/a2a/tasks/:id/reply`,
  `/cancel` and `/messages`. Replying is authorized against the *target* identity's own key,
  and cancel and messages against the *sender's*, so an org key cannot complete a task on an
  agent's behalf. An agent key can also read and change its own A2A settings (not who can
  reach it).
- Either: mail send/list/threads, `/v1/events*`, `/v1/vault/*` (subject to the identity's
  own keypair for rotate and redeem), `/v1/contacts`, `/v1/tunnels`.

`GET /v1/me` is the cheap disambiguator when a key's provenance is unknown: its
`actor_type` is `user` for an org key and `agent` for an agent-bound key.

Do not retry a `401` with the same key — `retryable` is false and the outcome will not
change. Retry `429` after a delay; retry `5xx` with backoff.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
