# API keys (/api-v2/api-keys)



API keys are shown in full exactly once, at creation. Only a SHA-256 hash is stored for auth, plus a masked form and the key's scopes and budget for display. Revoking removes the hash so the key stops working immediately. Each key may carry its own spending limit, enforced inline with inference. Key ids are prefixed `key_`; the raw key starts with `zk_live_`. See [authentication](/api-reference/authentication).

All requests require a bearer API key. Creating a key requires a verified email on the project owner.

## Create a key [#create-a-key]

`POST /v2/api-keys`

<ParamField body="name" type="string">
  A human label for the key. Must not be empty.
</ParamField>

<ParamField body="scopes" type="array" default="[&#x22;inference&#x22;]">
  Scopes to grant. Recognized: `inference`, `read`, `admin`. Defaults to `["inference"]`.
</ParamField>

```bash
curl https://api.zumik.ai/v2/api-keys \
  -H "Authorization: Bearer $ZUMIK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "prod", "scopes": ["inference"] }'
```

```json
{
  "id": "key_01jy7nlm56p8q9r0s1t2u3v4wx",
  "object": "api_key",
  "project_id": "prj_01jy7n0a4c8m2t6v9q3wrxk7bd",
  "name": "prod",
  "masked": "zk_live_4ab2…cdef",
  "scopes": ["inference"],
  "status": "active",
  "created_at": "2026-06-15T16:28:14Z",
  "spent_micros": 0,
  "key": "zk_live_4ab2Qn8xVc1pLm7sRtZk9wUe3yHd0fGcdef"
}
```

<ResponseField name="id" type="string">
  Opaque key id, prefixed `key_`.
</ResponseField>

<ResponseField name="object" type="string">
  Always `api_key`.
</ResponseField>

<ResponseField name="project_id" type="string">
  The owning project.
</ResponseField>

<ResponseField name="name" type="string">
  The key's label.
</ResponseField>

<ResponseField name="masked" type="string">
  A masked form safe to display, e.g. `zk_live_4ab2…cdef`.
</ResponseField>

<ResponseField name="scopes" type="array">
  The granted scopes.
</ResponseField>

<ResponseField name="status" type="string">
  `active` or `revoked`.
</ResponseField>

<ResponseField name="created_at" type="string">
  RFC 3339 creation timestamp.
</ResponseField>

<ResponseField name="last_used_at" type="string">
  When the key was last used, omitted until first use.
</ResponseField>

<ResponseField name="budget_micros" type="integer">
  The per-key spending limit in micro-USD, omitted when none is set.
</ResponseField>

<ResponseField name="spent_micros" type="integer">
  Spend recorded against the key in micro-USD.
</ResponseField>

<ResponseField name="key" type="string">
  The full raw key, beginning `zk_live_`. Returned only on creation, never again. Store it securely.
</ResponseField>

<Warning>
  The `key` field is the only time you can read the full key. If you lose it, revoke the key and create a new one.
</Warning>

## List keys [#list-keys]

`GET /v2/api-keys`

Returns the project's keys, newest first. The raw key and its hash are never included.

```bash
curl https://api.zumik.ai/v2/api-keys \
  -H "Authorization: Bearer $ZUMIK_API_KEY"
```

```json
{
  "object": "list",
  "data": [
    {
      "id": "key_01jy7nlm56p8q9r0s1t2u3v4wx",
      "object": "api_key",
      "project_id": "prj_01jy7n0a4c8m2t6v9q3wrxk7bd",
      "name": "prod",
      "masked": "zk_live_4ab2…cdef",
      "scopes": ["inference"],
      "status": "active",
      "created_at": "2026-06-15T16:28:14Z",
      "spent_micros": 0
    }
  ]
}
```

## Revoke a key [#revoke-a-key]

`DELETE /v2/api-keys/{key_id}`

Removes the key's hash so it stops working immediately.

<ParamField path="key_id" type="string">
  The `key_...` id to revoke.
</ParamField>

```bash
curl -X DELETE https://api.zumik.ai/v2/api-keys/key_01jy7nlm56p8q9r0s1t2u3v4wx \
  -H "Authorization: Bearer $ZUMIK_API_KEY"
```

```json
{
  "id": "key_01jy7nlm56p8q9r0s1t2u3v4wx",
  "object": "api_key.revoked",
  "revoked": true
}
```

## Set a per-key budget [#set-a-per-key-budget]

`POST /v2/api-keys/{key_id}/budget`

Sets or clears the key's own spending limit, independent of the project cap. Useful when a team member should not be able to drain a shared budget.

<ParamField path="key_id" type="string">
  The `key_...` id to update.
</ParamField>

<ParamField body="limit_usd" type="number">
  The limit in whole USD. Must be non-negative. `null` clears the per-key limit.
</ParamField>

```bash
curl https://api.zumik.ai/v2/api-keys/key_01jy7nlm56p8q9r0s1t2u3v4wx/budget \
  -H "Authorization: Bearer $ZUMIK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "limit_usd": 50 }'
```

Returns the updated key record with `budget_micros` set.

## Errors [#errors]

| Status | Code                    | When                                       |
| ------ | ----------------------- | ------------------------------------------ |
| 400    | `invalid_request_error` | Empty `name`, or a negative `limit_usd`.   |
| 401    | `invalid_api_key`       | Missing or invalid API key.                |
| 403    | `insufficient_scope`    | The project owner's email is not verified. |
| 404    | `invalid_request_error` | The key does not exist in this project.    |

See the full table on [errors](/api-reference/errors).
