# Create an API key

`POST /o/{org}/api-keys`

Mints a key that acts as the caller (an org admin) on the session
routes. The `secret` is in this response only; it is never stored or
shown again. Without `expires_at` the key never expires.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `org` | path | string | yes | The org's slug or id. A caller with no role in the org gets `404`. |

## Request body

- `name` (string, required)
- `scopes` (array of string, optional): Defaults to both scopes.
- `max_concurrent` (integer, optional, default `4`): Runs this key may have in progress at once.
- `expires_at` (string | null, optional): In the future and at most one year away.

## Example: example

```bash
curl -X POST "https://api.prix.dev/o/$RUSH_ORG/api-keys" \
  -H "Authorization: Bearer $RUSH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ci-runner",
    "expires_at": "2027-01-01T00:00:00Z"
  }'
```

```typescript
const org = process.env.RUSH_ORG;

const res = await fetch(`https://api.prix.dev/o/${org}/api-keys`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RUSH_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "ci-runner",
    expires_at: "2027-01-01T00:00:00Z",
  }),
});

console.log(res.status, await res.json());
```

```python
import os

import requests

org = os.environ["RUSH_ORG"]

res = requests.post(
    f"https://api.prix.dev/o/{org}/api-keys",
    headers={"Authorization": f"Bearer {os.environ['RUSH_TOKEN']}"},
    json={
        "name": "ci-runner",
        "expires_at": "2027-01-01T00:00:00Z",
    },
)

print(res.status_code, res.json())
```

## Responses

### 201

The key, with its secret.

- `id` (string, required): `key_…`; use it in the key routes.
- `name` (string, required)
- `prefix` (string, required): The secret's first characters, to tell keys apart.
- `scopes` (array of string, required)
- `max_concurrent` (integer, required)
- `webhook_url` (string | null, required)
- `expires_at` (string | null, required)
- `created_by` (object, required)
  - `id` (string, required)
  - `email` (string | null, required)
- `last_used_at` (string | null, required)
- `created_at` (string, required)
- `secret` (string, required): The key's secret. Only in the create and rotate responses; never shown again.
- `webhook_secret` (string, optional): The webhook signing secret. Only in the update response that minted it.

### 400

An invalid field (`VALIDATION_ERROR`).

### 401

No bearer, an unknown, revoked or expired one, or an API key on a route keys cannot call.

### 404

No such resource for the caller, including one that exists in an org the caller cannot see.

## Example response (201)

```json
{
  "id": "key_Zq3T9vB1mN4pX8cR2wYe",
  "name": "ci-runner",
  "prefix": "rk_live_Q7f2",
  "scopes": [
    "runs:read",
    "runs:write"
  ],
  "max_concurrent": 4,
  "webhook_url": null,
  "expires_at": "2027-01-01T00:00:00.000Z",
  "created_by": {
    "id": "5b8e2c1d-3f4a-4b6c-9d7e-8f9a0b1c2d3e",
    "email": "dana@acme.dev"
  },
  "last_used_at": null,
  "created_at": "2026-10-06T15:00:00.000Z",
  "secret": "rk_live_Q7f2…"
}
```
