# API keys API

> Create, list, rotate, and revoke organization API keys. These routes need a browser session.

- [List API keys](#list-api-keys): `GET /v1/api-keys`
- [Create an API key](#create-an-api-key): `POST /v1/api-keys`
- [Revoke an API key](#revoke-an-api-key): `DELETE /v1/api-keys/{api_key_id}`
- [Rotate an API key](#rotate-an-api-key): `POST /v1/api-keys/{api_key_id}/rotate`

## List API keys

`GET /v1/api-keys`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie.
- **Who:** Any member (owners and admins see all).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | `integer` | No |  |
| `cursor` | query | `string (uuid)` | No |  |
| `X-Organization-ID` | header | `string (uuid)` | No | Organization context. Required with a browser session. Optional with an API key, which fixes the organization; when sent it must match. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `200` | Owners and admins see every organization key; other members see their own | [`ApiKeyPage`](/docs/api/schemas#apikeypage) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | Bearer authentication (key management requires a session) | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl "$VMYARD_API_URL/v1/api-keys" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "X-Organization-ID: $VMYARD_ORGANIZATION_ID"
```

```json title="Response 200"
{
  "items": [
    {
      "created_at": "2026-10-12T05:51:01Z",
      "created_by": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "expires_at": "2026-10-12T05:51:01Z",
      "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "last_four": "x9Q2",
      "last_used_at": "2026-10-12T05:51:01Z",
      "name": "ci-runner",
      "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
      "prefix": "vmy_AbCdEfGh",
      "preset": "read_only",
      "revoked_at": "2026-10-12T05:51:01Z"
    }
  ],
  "next_cursor": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f"
}
```

## Create an API key

`POST /v1/api-keys`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** Any member.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Organization-ID` | header | `string (uuid)` | No | Organization context. Required with a browser session. Optional with an API key, which fixes the organization; when sent it must match. |

**Request body** (`application/json`, [`CreateApiKeyRequest`](/docs/api/schemas#createapikeyrequest))

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | 1–64 characters. |
| `preset` | [`ApiKeyPresetDto`](/docs/api/schemas#apikeypresetdto) | Yes |  |
| `expires_in_days` | `integer` | No | 1–365. default `90`. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `201` | The secret is returned only in this response | [`ApiKeySecretDto`](/docs/api/schemas#apikeysecretdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure or bearer authentication (key management requires a session) | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `422` | Invalid request (`invalid_request`); `field_errors` names the fields. | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/api-keys" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "X-Organization-ID: $VMYARD_ORGANIZATION_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "ci-runner",
  "preset": "use_existing",
  "expires_in_days": 30
}'
```

```json title="Response 201"
{
  "api_key": {
    "created_at": "2026-10-12T05:51:01Z",
    "created_by": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "expires_at": "2026-10-12T05:51:01Z",
    "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "last_four": "x9Q2",
    "last_used_at": "2026-10-12T05:51:01Z",
    "name": "ci-runner",
    "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "prefix": "vmy_AbCdEfGh",
    "preset": "read_only",
    "revoked_at": "2026-10-12T05:51:01Z"
  },
  "secret": "vmy_…"
}
```

## Revoke an API key

`DELETE /v1/api-keys/{api_key_id}`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** The key's creator, Owner, or Admin.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `api_key_id` | path | `string (uuid)` | Yes |  |
| `X-Organization-ID` | header | `string (uuid)` | No | Organization context. Required with a browser session. Optional with an API key, which fixes the organization; when sent it must match. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `204` | Revoked; repeating the request is a no-op | — |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure, bearer authentication, or neither the key's creator nor an owner/admin | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Not found in this organization (`resource_not_found`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X DELETE "$VMYARD_API_URL/v1/api-keys/$API_KEY_ID" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "X-Organization-ID: $VMYARD_ORGANIZATION_ID"
```

## Rotate an API key

`POST /v1/api-keys/{api_key_id}/rotate`

- **API key:** not accepted (`403 session_required`).
- **Browser session:** the `vmyard_session` cookie and the `x-csrf-token` header.
- **Who:** The key's creator, Owner, or Admin.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `api_key_id` | path | `string (uuid)` | Yes |  |
| `X-Organization-ID` | header | `string (uuid)` | No | Organization context. Required with a browser session. Optional with an API key, which fixes the organization; when sent it must match. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| `201` | Replacement key with the same name, preset, and lifetime; the old key is revoked | [`ApiKeySecretDto`](/docs/api/schemas#apikeysecretdto) |
| `401` | Not signed in, or the API key is malformed, expired, or revoked (`unauthenticated`, `invalid_api_key`). | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `403` | CSRF failure, bearer authentication, or neither the key's creator nor an owner/admin | [`ProblemDetails`](/docs/api/schemas#problemdetails) |
| `404` | Unknown or already revoked key | [`ProblemDetails`](/docs/api/schemas#problemdetails) |

```sh title="Request"
curl -X POST "$VMYARD_API_URL/v1/api-keys/$API_KEY_ID/rotate" \
  -b "vmyard_session=$VMYARD_SESSION" \
  -H "x-csrf-token: $VMYARD_CSRF" \
  -H "X-Organization-ID: $VMYARD_ORGANIZATION_ID"
```

```json title="Response 201"
{
  "api_key": {
    "created_at": "2026-10-12T05:51:01Z",
    "created_by": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "expires_at": "2026-10-12T05:51:01Z",
    "id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "last_four": "x9Q2",
    "last_used_at": "2026-10-12T05:51:01Z",
    "name": "ci-runner",
    "organization_id": "6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
    "prefix": "vmy_AbCdEfGh",
    "preset": "read_only",
    "revoked_at": "2026-10-12T05:51:01Z"
  },
  "secret": "vmy_…"
}
```
