# control API 契約（gateway 提供 / admin 消費）

gateway と admin の唯一の結合点。両者はこの契約にのみ依存する。
admin は SQLite ファイルを直接触らない。

## 接続

| 項目 | 値 |
|---|---|
| ベース URL | `http://gateway:3001`（Compose ネットワーク内。ホストへは公開しない） |
| 認証 | ヘッダ `X-Control-Token: <CONTROL_TOKEN>` |
| 監査用ヘッダ | 更新系のみ `X-Actor`（管理者名）と `X-Actor-IP` を任意で付与 |
| 認証失敗 | 401。本文は共通エラー形 |

## 共通エラー形

すべての 4xx / 5xx は次の形を返す。

```json
{"error": {"message": "...", "type": "invalid_request_error", "code": "unauthorized"}}
```

## エンドポイント

### GET /healthz（認証不要）

Docker healthcheck が使用する。

```json
{"status": "ok", "upstream": "ok", "version": "1.0.0"}
```

`upstream` は `ok` / `unreachable` / `unknown`。`status` が `ok` なら 200、それ以外は 503。

### GET /keys

クエリ `include_disabled`（既定 true）。

```json
{"keys": [{
  "id": 1,
  "name": "team-a",
  "key_prefix": "sk-aig-ab12",
  "enabled": true,
  "created_at": "2026-09-09T10:00:00+09:00",
  "expires_at": null,
  "note": "",
  "limits": {"rpm": 60, "rpd": 5000, "concurrency": 2, "tpm": null},
  "models": ["qwen3-8b"],
  "usage_24h": {"requests": 120, "prompt_tokens": 4000, "completion_tokens": 9000}
}]}
```

`models` が空配列なら全モデル許可を意味する。
`limits` の各値が `null` なら当該軸の制限なし。

### POST /keys

```json
{"name": "team-a", "note": "", "expires_at": null,
 "limits": {"rpm": 60, "rpd": 5000, "concurrency": 2, "tpm": null},
 "models": ["qwen3-8b"]}
```

201 を返す。`secret` はこの応答でのみ返り、以後どこからも取得できない。

```json
{"key": { ...GET /keys と同じ形... }, "secret": "sk-aig-............"}
```

### PATCH /keys/{id}

`name` `note` `enabled` `expires_at` `limits` `models` の任意の部分集合を受ける。
`limits` は指定されたキーのみ更新する。`models` は指定時に全置換する。

200 で `{"key": {...}}` を返す。

### DELETE /keys/{id}

204 を返す。実体は物理削除とし、監査ログに削除前の内容を残す。

### GET /usage

クエリ `days`（既定 7、最大 90）。

```json
{"totals": {"requests": 0, "prompt_tokens": 0, "completion_tokens": 0},
 "by_day": [{"date": "2026-09-09", "requests": 0, "prompt_tokens": 0, "completion_tokens": 0}],
 "by_key": [{"key_id": 1, "name": "team-a", "requests": 0, "prompt_tokens": 0, "completion_tokens": 0}],
 "by_model": [{"model": "qwen3-8b", "requests": 0}],
 "recent_errors": [{"ts": "...", "key_id": 1, "path": "/v1/chat/completions", "status": 429}]}
```

### GET /models

上流の `/v1/models` を内部キーで叩いた結果をキャッシュして返す。

```json
{"models": ["qwen3-8b", "gemma3-12b"], "fetched_at": "2026-09-09T10:00:00+09:00", "stale": false}
```

上流到達不能時は直近のキャッシュを `stale: true` で返す。キャッシュも無ければ空配列。

### GET /audit

クエリ `limit`（既定 100、最大 1000）。

```json
{"entries": [{"id": 1, "ts": "...", "actor": "admin", "action": "key.create",
              "target_id": "1", "before": null, "after": {}, "source_ip": "203.0.113.1"}]}
```

`action` は `key.create` `key.update` `key.delete` `auth.fail` のいずれか。
