# Create API Key

- **Method:** `POST`
- **Path:** `/api/v1/api-keys`
- **Operation ID:** `create_api_key`
- **Tags:** identity

Mint one machine key for your own tenant. The secret is in this response and in nothing else.

What it is for: a person signs in to the dashboard and needs a credential for their CI, their laptop
or their coding agent. The key carries *scopes* and no role, so a CI key minted `runs:write,runs:read`
starts and reads runs and can neither mint another key nor upload a provider credential.

Only `sha256` of the secret is stored, so there is no route that shows it again: losing it means
minting another and revoking this one, which is the same motion as a rotation and is meant to be
cheap. The tenant is the one your own credential belongs to and cannot be named in the request.

## Effective servers

- `https://api.saram.sh`

  Saram

## Authentication

- **HTTPBearer**: HTTP bearer

  a Saram API key (`sk_saram_<env>_...`) or a person's AuthKit access token, as `Authorization: Bearer <credential>`. Both are told apart by shape and produce the same principal.

## Request body

**Required:** `true`

**Content type:** `application/json`

schema: `ApiKeyWrite`, no additional properties

`POST /api/v1/api-keys`: what a person asks for when they mint one of their tenant's machine keys.

- **`name` (required)**: `string`

  Which of this tenant's credentials this is: lowercase letters, digits, underscore and dash, up to 64 characters - ci, dana-laptop. Not unique, because a rotation is two live keys under one name until the caller has moved.
- **`expires_in_days`**: `integer | null`, minimum: `1`

  Refuse this key at the gate after this many days. Omitted, it does not expire on its own.
- **`scopes`**: `(array of string) | null`

  A subset of runs:write, runs:read, files:write, credentials:write, keys:admin. Omitted, the key gets runs:write and runs:read, which is what a CI key needs and nothing more.

**Example:**

```json
{
  "name": "",
  "scopes": [
    ""
  ],
  "expires_in_days": 1
}
```

## Responses

### 201 Successful Response

**Content type:** `application/json`

schema: `ApiKeyMinted`, no additional properties

What `POST /api/v1/api-keys` answers: the row, and the one and only sight of the secret.

`secret` is not stored anywhere - `api_keys` holds `sha256` of it - so a caller who does not keep this
response mints another and revokes this one. That is the same motion as a rotation, and it is meant to be.

- **`created_at` (required)**: `string`, format: `date-time`
- **`created_by` (required)**: `string`

  usr\_... for a person who minted it from the dashboard, operator for the CLI.
- **`id` (required)**: `string`
- **`name` (required)**: `string`
- **`scopes` (required)**: `array of string`
- **`secret` (required)**: `string`

  sk\_saram\_\_<40 characters>. Shown once, here, and never again by any route: only its hash is stored. Treat it as you would a password - it carries this tenant's authority until it is revoked.
- **`expires_at`**: `string | null`, format: `date-time`
- **`last_used_at`**: `string | null`, format: `date-time`

  When this key last got through the gate, to a few minutes: it answers whether anything is still using the key before you revoke it, which does not need the last second.
- **`revoked_at`**: `string | null`, format: `date-time`

  Set on the record DELETE answers with. The row stays; the key stops working.

**Example:**

```json
{
  "id": "",
  "name": "",
  "scopes": [
    ""
  ],
  "created_by": "",
  "created_at": "",
  "last_used_at": "",
  "expires_at": "",
  "revoked_at": "",
  "secret": ""
}
```

### 403 this credential may not mint keys: an API key without the keys:admin scope, or a person whose role in this tenant is not owner or admin

### 422 a name the table cannot hold, or a scope that is not one of ours

## Schemas

- `ApiKeyMinted` — shown above.
- `ApiKeyWrite` — shown above.
