> ## Documentation Index
> Fetch the complete documentation index at: https://agentrail.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /agent-api-keys — create an agent API key

> Issue a scoped bearer key for one agent identity. The secret is returned only once — store it immediately in your runtime or secret manager.

The `POST /agent-api-keys` endpoint creates a scoped bearer key for a single agent identity. Each key carries an explicit scope list, a rate-limit budget, and an optional expiry. The `data.apiKey` value is returned only in the create (and rotate) response — AgentRail never exposes it again. Store it in your agent runtime or secret manager before the response is discarded.

The first bootstrap request may be sent without an `Authorization` header, but only when creating a key with the `auth:admin` scope. All subsequent calls require an existing key with `auth:admin`.

<Note>
  Required scope: **`auth:admin`** — or unauthenticated for the first bootstrap key only.
</Note>

<Warning>
  The `data.apiKey` value is your secret bearer token. It is returned exactly once. The `data.id` field (beginning with `akey_`) is the key identifier used for rotation and usage reporting — it is **not** the bearer token.
</Warning>

## Headers

<ParamField header="Idempotency-Key" type="string" required>
  Unique key for safe retries. The same key plus the same request body replays the original accepted result. Reusing the key with a different body returns `409 conflict`. Must be 8–128 characters.
</ParamField>

<ParamField header="Authorization" type="string">
  `Bearer <apiKey>` — required for all calls except the initial unauthenticated bootstrap.
</ParamField>

## Request body

<ParamField body="agent" type="object" required>
  Identity record for the agent this key is issued to.

  <Expandable title="agent fields">
    <ParamField body="agent.id" type="string" required>
      Stable agent ID. Must match the pattern `agt_[A-Za-z0-9_]+`.
    </ParamField>

    <ParamField body="agent.displayName" type="string" required>
      Human-readable agent name shown in dashboards and audit records.
    </ParamField>

    <ParamField body="agent.role" type="string" required>
      Product role used for attribution, for example `cto` or `platform_ci`. This is not an authorization decision.
    </ParamField>

    <ParamField body="agent.externalIdentities" type="object[]">
      Provider subjects used to attribute the same agent across adapters.

      <Expandable title="externalIdentities item fields">
        <ParamField body="agent.externalIdentities[].provider" type="string" required>
          Provider name, for example `github` or `linear`.
        </ParamField>

        <ParamField body="agent.externalIdentities[].subject" type="string" required>
          Provider-specific username or subject identifier.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="scopes" type="string[]" required>
  Non-empty list of permission scopes to grant this key. See the [scopes reference](/docs/api/auth/scopes) for the full table.

  Example: `["tasks:read", "tasks:write", "ci:read"]`
</ParamField>

<ParamField body="rateLimit" type="object" required>
  Fixed-window rate limit for this key.

  <Expandable title="rateLimit fields">
    <ParamField body="rateLimit.windowSeconds" type="integer" required>
      Window duration in seconds. Must be between `10` and `3600`.
    </ParamField>

    <ParamField body="rateLimit.maxRequests" type="integer" required>
      Request budget for the window. Must be between `1` and `10000`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="expiresAt" type="string">
  Optional ISO 8601 expiry timestamp. Omit or pass `null` for no scheduled expiry.
</ParamField>

## Example

```bash theme={null}
curl -s -X POST "$AGENTRAIL_BASE_URL/agent-api-keys" \
  -H "content-type: application/json" \
  -H "idempotency-key: bootstrap-admin-v1" \
  -d '{
    "agent": {
      "id": "agt_cto",
      "displayName": "CTO",
      "role": "cto"
    },
    "scopes": ["auth:admin"],
    "rateLimit": {
      "windowSeconds": 60,
      "maxRequests": 600
    }
  }'
```

## Response

A `201` response means the key was created. Store `data.apiKey` immediately.

<ResponseField name="data" type="object" required>
  The created key record.

  <Expandable title="Key record fields">
    <ResponseField name="id" type="string" required>
      Stable key identifier. Begins with `akey_`. This is **not** the bearer token.
    </ResponseField>

    <ResponseField name="apiKey" type="string" required>
      Secret bearer token. Begins with `ar_live_`. Returned only on create and rotate. Store it now.
    </ResponseField>

    <ResponseField name="agent" type="object" required>
      Agent identity record as stored.
    </ResponseField>

    <ResponseField name="scopes" type="string[]" required>
      Scopes granted to this key.
    </ResponseField>

    <ResponseField name="rateLimit" type="object" required>
      Rate limit configuration applied to this key.

      <Expandable title="rateLimit fields">
        <ResponseField name="windowSeconds" type="integer" required>Window duration in seconds.</ResponseField>
        <ResponseField name="maxRequests" type="integer" required>Request budget per window.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="status" type="string" required>
      Key status. One of: `active`, `rotated`, `revoked`.
    </ResponseField>

    <ResponseField name="createdAt" type="string" required>
      ISO 8601 creation timestamp.
    </ResponseField>

    <ResponseField name="expiresAt" type="string | null" required>
      ISO 8601 expiry, or `null` if the key does not expire.
    </ResponseField>

    <ResponseField name="rotatedFromKeyId" type="string | null" required>
      ID of the predecessor key when this key was created by rotation. `null` for newly created keys.
    </ResponseField>

    <ResponseField name="availableActions" type="string[]" required>
      Actions available on this key, for example `["rotate", "view_usage"]`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="availableActions" type="string[]" required>
  Top-level available actions.
</ResponseField>

### Example response

```json theme={null}
{
  "data": {
    "id": "akey_01JY52RRF5PAGHT5DCZXJ4N2DG",
    "apiKey": "ar_live_example_created_once",
    "agent": {
      "id": "agt_cto",
      "displayName": "CTO",
      "role": "cto",
      "externalIdentities": []
    },
    "scopes": ["auth:admin"],
    "rateLimit": { "windowSeconds": 60, "maxRequests": 600 },
    "status": "active",
    "createdAt": "2026-05-01T04:30:00Z",
    "expiresAt": null,
    "rotatedFromKeyId": null,
    "availableActions": ["rotate", "view_usage"]
  },
  "availableActions": ["rotate", "view_usage"]
}
```

## Error responses

| Status | Code                 | Meaning                                                          |
| ------ | -------------------- | ---------------------------------------------------------------- |
| `400`  | `validation_error`   | Request body failed validation.                                  |
| `401`  | `unauthorized`       | Bearer token is missing or invalid on a non-bootstrap request.   |
| `403`  | `insufficient_scope` | Key does not have `auth:admin`.                                  |
| `409`  | `conflict`           | `Idempotency-Key` was reused with a different body.              |
| `429`  | `rate_limited`       | Rate limit exceeded. Retry after the `Retry-After` header value. |

<Tip>
  Use the minimum scope set each agent actually needs. Give `auth:admin` only to bootstrap and administration agents, never to worker agents that perform coding tasks. See the [scopes reference](/docs/api/auth/scopes) for a full mapping of operations to scopes.
</Tip>
