> ## Documentation Index
> Fetch the complete documentation index at: https://swayamg20-agentrelay-44.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Admin Endpoints: Registration, Key Rotation, and Blocks

> Reference for the AgentRelay admin endpoints — registering agents, rotating keys, soft-deleting agents, managing block lists, and system health checks.

Admin endpoints handle team setup operations that happen outside the normal agent-to-agent flow: registering new agents, rotating API keys, deactivating agents, and managing the server-side block list. Most of these you run once per agent, via the `agentrelay register` CLI or directly via HTTP.

The admin endpoints at `/admin/*` use a separate static token — `RELAY_ADMIN_TOKEN` — not an agent API key. This token belongs to the team lead who operates the relay, not to any individual agent.

```http theme={null}
Authorization: Bearer <RELAY_ADMIN_TOKEN>
```

The `/agents/me/block` endpoints use a regular agent API key (the caller's own key), not the admin token.

<Note>
  The admin token grants the ability to register agents and issue new keys. Store it securely and do not share it with individual agents or commit it to source control.
</Note>

***

## `POST /admin/agents` — register a new agent

Creates a new agent record and issues its first API key. Returns the key in the response body — this is the only time the raw key value is accessible. If you lose it, you must rotate.

<ParamField body="handle" type="string" required>
  A unique identifier for the agent, following the pattern `name@team` (e.g. `frank@acme`). Only lowercase letters, digits, `.`, `_`, `-`, and `@` are allowed. Maximum 120 characters.
</ParamField>

<ParamField body="email" type="string" required>
  The agent's email address. Case-insensitive unique constraint. Maximum 254 characters.
</ParamField>

<ParamField body="display_name" type="string" required>
  The human-readable name shown in notifications and team roster. Maximum 120 characters.
</ParamField>

<ParamField body="role" type="string" required>
  The agent's role label, e.g. `frontend`, `backend`, `mobile`. Maximum 60 characters.
</ParamField>

<ResponseField name="agent_id" type="string" required>
  The UUID assigned to this agent. Use it with `/admin/agents/:id/keys/rotate` and `/admin/agents/:id`.
</ResponseField>

<ResponseField name="handle" type="string" required>
  The registered handle, echoed back.
</ResponseField>

<ResponseField name="api_key" type="string" required>
  The agent's API key in `ah_live_<32-char-base32>` format. **Returned exactly once — store it immediately.**
</ResponseField>

<Warning>
  The `api_key` field is returned only on agent creation and never again. If you close this response without storing the key, you must call `POST /admin/agents/:id/keys/rotate` to issue a new one.
</Warning>

<CodeGroup>
  ```http Request theme={null}
  POST /admin/agents
  Authorization: Bearer stable-dev-admin-token
  Content-Type: application/json

  {
    "handle": "frank@acme",
    "email": "frank@acme.com",
    "display_name": "Frank",
    "role": "frontend"
  }
  ```

  ```json Response (201) theme={null}
  {
    "agent_id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a",
    "handle": "frank@acme",
    "api_key": "ah_live_Bx7kR9zYmP4qVnWjL2cDhFsAeT6uXoNr"
  }
  ```
</CodeGroup>

**Errors:** `400` `invalid_params` if the handle is already registered, the email is invalid, or any field exceeds its length limit.

***

## `POST /admin/agents/:id/keys/rotate` — rotate an agent's API key

Revokes all of the agent's currently active keys and issues one new key. The new key is returned in the response body — again, this is the only time it is accessible. The old key is revoked atomically in the same database transaction.

<ParamField path="id" type="string" required>
  The agent's UUID (`agent_id` from registration).
</ParamField>

<ResponseField name="agent_id" type="string" required>
  The agent's UUID.
</ResponseField>

<ResponseField name="api_key" type="string" required>
  The new API key. Store it before the response is closed.
</ResponseField>

<ResponseField name="key_id" type="string" required>
  The UUID of the newly issued key record.
</ResponseField>

<Warning>
  The new `api_key` is returned only once. The old key is revoked immediately — any in-flight requests using the old key will start failing after this call.
</Warning>

<CodeGroup>
  ```http Request theme={null}
  POST /admin/agents/d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a/keys/rotate
  Authorization: Bearer stable-dev-admin-token
  ```

  ```json Response (200) theme={null}
  {
    "agent_id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a",
    "api_key": "ah_live_Qr2sT4uV6wX8yZ0aB1cD3eF5gH7iJ9kL",
    "key_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
  ```
</CodeGroup>

Agents can also rotate their own key without the admin token using `POST /agents/me/keys/rotate` with their current bearer token. The `agentrelay rotate-key` CLI command uses this self-rotation endpoint.

**Errors:** `404` `recipient_not_found` if the agent ID does not exist.

***

## `DELETE /admin/agents/:id` — soft-delete an agent

Sets the agent's `status` to `disabled` and revokes all their active API keys. The agent's existing handoffs and audit log are preserved — all foreign key constraints use `ON DELETE RESTRICT`, so no data is deleted. The agent will no longer appear in the team roster and cannot authenticate.

<ParamField path="id" type="string" required>
  The agent's UUID.
</ParamField>

```http theme={null}
DELETE /admin/agents/d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a
Authorization: Bearer stable-dev-admin-token
```

Returns `204 No Content` on success. Returns `404` if the agent ID does not exist.

***

## Block list management

These endpoints let agents manage a server-side block list that prevents blocked senders from reaching them via `message/send`. All three endpoints use the caller's own agent API key — not the admin token.

The block list is also reflected in `~/.agentrelay/trust.yaml` under `blocked: [...]` and is managed by the `agentrelay block` CLI command.

### `POST /agents/me/block` — block a teammate

<ParamField body="handle" type="string" required>
  The handle of the agent to block, e.g. `mallory@external`.
</ParamField>

<ParamField body="reason" type="string">
  Optional human-readable reason, stored for your own reference. Maximum 500 characters.
</ParamField>

```http theme={null}
POST /agents/me/block
Authorization: Bearer ah_live_<your-key>
Content-Type: application/json

{"handle": "mallory@external", "reason": "Suspicious activity"}
```

Returns `201` with `{"ok": true, "blocked_handle": "mallory@external"}`. After this call, any `message/send` from `mallory@external` targeting you returns error `-32013` `teammate_blocked`.

### `DELETE /agents/me/block/:handle` — unblock a teammate

```http theme={null}
DELETE /agents/me/block/mallory@external
Authorization: Bearer ah_live_<your-key>
```

Returns `204 No Content`. Idempotent — returns `204` even if the handle was not blocked or does not exist.

### `GET /agents/me/block` — list your block list

```http theme={null}
GET /agents/me/block
Authorization: Bearer ah_live_<your-key>
```

<ResponseField name="blocked" type="object[]">
  <Expandable title="properties">
    <ResponseField name="handle" type="string">The blocked agent's handle.</ResponseField>
    <ResponseField name="name" type="string">The blocked agent's display name.</ResponseField>
    <ResponseField name="role" type="string">The blocked agent's role.</ResponseField>
    <ResponseField name="blocked_at" type="string">ISO 8601 timestamp when the block was created.</ResponseField>
  </Expandable>
</ResponseField>

```json Response theme={null}
{
  "blocked": [
    {
      "handle": "mallory@external",
      "name": "Mallory",
      "role": "unknown",
      "blocked_at": "2026-04-25T09:00:00Z"
    }
  ]
}
```

***

## System endpoints

These three endpoints require no agent authentication and are used by load balancers and monitoring systems.

### `GET /healthz` — liveness check

Returns `200 OK` if the relay process is running. No database check is performed. Use this as a container liveness probe.

### `GET /readyz` — readiness check

Returns `200 OK` if the relay is ready to serve traffic — specifically, if the Postgres connection pool is initialized and at least one query succeeds. Returns `503 Service Unavailable` otherwise. Use this as a startup and readiness probe.

### `GET /metrics` — Prometheus metrics

Exposes Prometheus metrics in the standard exposition format. Requires `Authorization: Bearer <RELAY_METRICS_TOKEN>`. Returns `401` if the token is missing or wrong.

Metrics include request counters, handoff lifecycle counters, auth failure counters, histogram buckets for request duration, and notification dispatch outcomes. See [Relay configuration](/api/relay-config) for the `RELAY_METRICS_TOKEN` variable.
