# /v1/keys — key administration

Admin only. Send the deployment's `INGEST_TOKEN` as `x-admin-token`. The plaintext key is returned **exactly once**, at creation; only its SHA-256 hash is stored.

> There is a console for all of this at **[/admin](/admin)** — sign in with Discord (approved accounts only) and create, name, disable and delete keys in a browser, with each key's calls and spend for the day beside it. Naming a key's project there is what creates that project on the cost tracker. The endpoints below are the same operations for scripts, and still use the `INGEST_TOKEN`.

## POST /v1/keys — mint a key

```bash
curl -X POST "https://tikdata.io/v1/keys" \
  -H "x-admin-token: $INGEST_TOKEN" \
  -H "content-type: application/json" \
  -d '{"label":"market bubble site","project":"market-bubble"}'
```

### Body

| field | required | notes |
|---|---|---|
| `label` | yes | human name, shown in listings |
| `project` | yes | attribution bucket; every call this key makes is counted under it |
| `scopes` | no | array of upstream path prefixes, e.g. `["/api/v1/tiktok/"]`; restricts `/v1/raw` |

### Response — 201

```json
{
  "key": "thr_live_a2d5c7e1f3b9d4c6a8e0f2b4d6c8a0e2f4b6d8c0a2e4f6b8d0c2e4f6",
  "warning": "Store this now — it is hashed on the server and cannot be shown again.",
  "record": {
    "id": "rk_1630e4b68fd5",
    "label": "market bubble site",
    "project": "market-bubble",
    "enabled": true,
    "scopes": null,
    "prefix": "thr_live_a2d5c7",
    "createdAt": "2026-09-07T21:16:26.000Z",
    "lastUsedAt": null,
    "requestCount": 0
  }
}
```

`prefix` is the first 15 characters of the key, kept so a key can be recognized in logs without storing it.

## GET /v1/keys — list

```bash
curl "https://tikdata.io/v1/keys" -H "x-admin-token: $INGEST_TOKEN"
```

```json
{ "keys": [ { "id": "rk_…", "label": "…", "project": "…", "enabled": true, "scopes": null,
              "prefix": "thr_live_…", "createdAt": "…", "lastUsedAt": "…", "requestCount": 0 } ] }
```

Newest first. Plaintext keys are never included.

## PATCH /v1/keys — enable / disable

```bash
curl -X PATCH "https://tikdata.io/v1/keys" \
  -H "x-admin-token: $INGEST_TOKEN" \
  -H "content-type: application/json" \
  -d '{"id":"rk_1630e4b68fd5","enabled":false}'
```

```json
{ "id": "rk_1630e4b68fd5", "enabled": false }
```

A disabled key gets 401 on its next call. Disabling is instant and reversible; prefer it to deleting when a consumer may come back.

## DELETE /v1/keys?id=… — remove

```bash
curl -X DELETE "https://tikdata.io/v1/keys?id=rk_1630e4b68fd5" -H "x-admin-token: $INGEST_TOKEN"
```

```json
{ "deleted": "rk_1630e4b68fd5" }
```

Per-key usage rows survive deletion — `project` is denormalized onto them — so history in `/v1/usage?day=` stays intact.

## Errors

| status | body |
|---|---|
| 401 | `{"error":"admin token required","hint":"send: x-admin-token: <INGEST_TOKEN>"}` |
| 400 | `{"error":"label and project are required"}` |
| 400 | `{"error":"id and enabled are required"}` (PATCH) |
| 400 | `{"error":"id query param is required"}` (DELETE) |
| 404 | `{"error":"no such key"}` |
| 500 | `{"error":"could not create key (no database)"}` — `DATABASE_URL` unset |
