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

# Authentication

> API key authentication, tenant model, key lifecycle, and error handling.

# Authentication

How to authenticate with the Agent Harness API, manage API keys, and understand the tenant/attribution model.

***

## Credential format

Protected endpoints require an API key in Bearer format:

| Header          | Value                      |
| --------------- | -------------------------- |
| `Authorization` | `Bearer <key_id>:<secret>` |

The raw key has the form `key_id:secret`. Use it exactly as issued; do not Base64-encode or otherwise transform it.

```
Authorization: Bearer key_abc123xyz:your_secret_here
```

***

## Tenant model

Every API key is bound to exactly one **customer** (`customer_id`). The customer is the sole server-validated authorization boundary. All resources (runs, events, memory, usage) are isolated by `customer_id`.

### Attribution fields

When creating a run, you may optionally include:

| Field          | Purpose                                                             | Authorization? |
| -------------- | ------------------------------------------------------------------- | :------------: |
| `workspace_id` | Customer-provided organizational label (team, project, environment) |       No       |
| `subject_id`   | Customer-provided user/actor label                                  |       No       |

These are **opaque attribution fields** — the API stores them as-is and uses them for:

* **Billing attribution**: Usage events carry these dimensions for your own cost allocation.
* **Filtering**: Usage queries (`GET /v1/usage`) accept these as optional filter parameters.
* **Telemetry**: They appear in structured logs for your own operational visibility.

They are **not** validated against any server-side membership data. A request with a valid API key can access all of that customer's resources regardless of `workspace_id` or `subject_id` values.

***

## API key lifecycle

API keys support the following lifecycle operations. These endpoints are part of the v0 product surface for customer self-service onboarding.

### POST /v1/api-keys — Create API key

Create a new API key for the authenticated customer.

| Item   | Value          |
| ------ | -------------- |
| Method | POST           |
| Path   | `/v1/api-keys` |
| Auth   | Required       |

**Response (201):**

```json  theme={null}
{
  "id": "ak_xxx",
  "key": "key_abc123xyz:secret_value_shown_once",
  "customer_id": "cust_xxx",
  "status": "active",
  "created_at": "2026-03-25T14:30:00.000Z",
  "request_id": "uuid"
}
```

> **Important**: The `key` field (containing the secret) is returned **only once** at creation time. Store it securely — it cannot be retrieved again.

### DELETE /v1/api-keys/{id} — Revoke API key

Immediately revoke a key. Subsequent requests using the revoked key receive `401 AUTH_API_KEY_REVOKED`.

| Item   | Value               |
| ------ | ------------------- |
| Method | DELETE              |
| Path   | `/v1/api-keys/{id}` |
| Auth   | Required            |

**Response:** 200 (revoked), 401, 403, 404

### POST /v1/api-keys/{id}/rotate — Rotate API key

Issue a replacement key. The old key remains valid during a grace period (server-configured), then is automatically revoked.

| Item   | Value                      |
| ------ | -------------------------- |
| Method | POST                       |
| Path   | `/v1/api-keys/{id}/rotate` |
| Auth   | Required                   |

**Response (200):**

```json  theme={null}
{
  "id": "ak_new_xxx",
  "key": "key_new123:new_secret_shown_once",
  "replaces": "ak_old_xxx",
  "grace_period_ends_at": "2026-03-25T15:30:00.000Z",
  "request_id": "uuid"
}
```

During the grace period, both old and new keys are valid. After the grace period, the old key is revoked.

### Lifecycle audit

Every key lifecycle operation (create, revoke, rotate) is recorded in an append-only audit log. Audit records include: actor, action, timestamp, and `customer_id`.

***

## Protected vs public endpoints

| Path pattern    | Auth required |
| --------------- | :-----------: |
| `/v1/*`         |      Yes      |
| `/health/*`     |       No      |
| `/openapi.json` |       No      |

***

## Required headers (protected requests)

| Header            |       Required       | Example                              |
| ----------------- | :------------------: | ------------------------------------ |
| `Authorization`   |          Yes         | `Bearer <key_id>:<secret>`           |
| `Content-Type`    | Yes (POST/PUT/PATCH) | `application/json`                   |
| `idempotency-key` |  Yes (run creation)  | Unique string per create (e.g. UUID) |

***

## Auth failure response envelope

All auth failures return JSON with a typed envelope:

```json  theme={null}
{
  "error": "unauthorized",
  "reason_code": "AUTH_API_KEY_MISSING",
  "request_id": "uuid"
}
```

Use `reason_code` for programmatic handling.

***

## Auth reason codes

| Reason code                           | HTTP | Meaning                                        |
| ------------------------------------- | :--: | ---------------------------------------------- |
| `AUTH_API_KEY_MISSING`                |  401 | No Authorization header or Bearer token        |
| `AUTH_AUTHORIZATION_HEADER_MALFORMED` |  401 | Header present but not `Bearer <key>:<secret>` |
| `AUTH_API_KEY_INVALID`                |  401 | Key not found or verification failed           |
| `AUTH_API_KEY_REVOKED`                |  401 | Key has been revoked                           |
| `AUTH_API_KEY_EXPIRED`                |  401 | Key past expiry                                |
| `AUTH_API_KEY_NOT_ACTIVE`             |  401 | Key not in active state                        |
| `AUTH_CONTEXT_MISSING`                |  401 | Auth context missing after middleware          |
| `AUTHZ_UNTRUSTED_CALLER_METADATA`     |  403 | Invalid or conflicting caller metadata         |
| `AUTHZ_SCOPE_MISMATCH`                |  403 | Request scope does not match resource scope    |
| `AUTHZ_DENY_BY_DEFAULT`               |  403 | Access denied (deny-by-default)                |

***

## Response correlation

Every response includes:

* **JSON body**: `request_id` field
* **HTTP header**: `x-request-id`

Use these to correlate API errors with server logs when contacting support.

***

## Security best practices

* Store keys in environment variables or a secrets manager. Never commit to source control.
* Rotate keys immediately if exposure is suspected.
* Use separate keys for development and production environments.
* Monitor `401`/`403` error rates for anomalous access patterns.

***

## Troubleshooting

| Symptom                    | Action                                                                                                         |
| -------------------------- | -------------------------------------------------------------------------------------------------------------- |
| 401 on protected routes    | Verify `Authorization: Bearer <key_id>:<secret>`. Ensure no extra encoding, whitespace, or line breaks.        |
| 401 `AUTH_API_KEY_REVOKED` | Request a new key; the previous key is permanently invalid.                                                    |
| 401 `AUTH_API_KEY_EXPIRED` | Rotate the key; the expiry date has passed.                                                                    |
| 403 on resource access     | Confirm the key belongs to the customer that owns the target resource. Cross-customer access is always denied. |
| 403 `AUTHZ_SCOPE_MISMATCH` | The run or resource belongs to a different `customer_id` than the authenticated key.                           |


Built with [Mintlify](https://mintlify.com).