# Python API 參數速查（FastAPI）

這份文件對齊目前程式實作（`backend/app/api/*` + `backend/app/schemas/*`），給你在看 `http://127.0.0.1:8000/docs` 時快速判斷：

- 哪些欄位是必填
- 哪些欄位可選
- 有哪些業務限制會造成 4xx

> Base URL（local）：`http://127.0.0.1:8000`  
> API Prefix：`/api`

---

## 1. 驗證與權限

### Authorization Header

- Admin / Editor API 幾乎都需要：`Authorization: Bearer <DIRECTUS_ACCESS_TOKEN>`
- Runtime API 目前不要求 Bearer token（公開給 snippet / SDK）

### 常見錯誤碼

- `401`：缺少或無效 token
- `403`：token 有效但缺少對應 permission
- `404`：資源不存在
- `422`：請求 body 結構不符 schema

### Permission 對照（路由層）

| 模組 | 權限旗標 |
|---|---|
| Sites | `can_manage_sites` |
| Experiments | `can_manage_experiments` |
| Variants | `can_manage_variants` |
| Releases | `can_manage_releases` |
| Goals | `can_manage_goals` |
| SDK Configs | `can_manage_sdk_configs` |
| Editor | `can_use_editor` |

---

## 2. 必要參數總表（Swagger 快速對照）

> 這張表是「你在 `/docs` 要先看哪個欄位必填」的最短路徑。

| 端點 | 必要 Path / Query | 必要 Body | 主要限制 |
|---|---|---|---|
| `GET /api/auth/me` | 無 | 無 | 必須帶 `Authorization: Bearer <token>` |
| `POST /api/admin/experiments` | 無 | `site_id`, `name` | `experiment_key` 系統生成，不可自填 |
| `PATCH /api/admin/experiments/{experiment_id}` | `experiment_id` | JSON body（欄位可全選填） | body 本身必帶（可傳 `{}`） |
| `POST /api/admin/variants` | 無 | `experiment_id`, `name` | 權重總和必須 = 100 |
| `PATCH /api/admin/variants/{variant_id}` | `variant_id` | JSON body（欄位可全選填） | body 本身必帶（可傳 `{}`）；若改 `traffic_weight` 需滿足總和 = 100 |
| `POST /api/admin/releases/build` | 無 | `experiment_id` | 會產生新 `draft` release，`version_no` 遞增 |
| `POST /api/editor/sessions` | 無 | `variant_id`, `base_url` | 需 editor 權限 |
| `PATCH /api/editor/sessions/{session_id}` | `session_id` | JSON body（`status`/`draft_changes` 皆可不帶） | body 本身必帶（可傳 `{}`） |
| `PUT /api/editor/variants/{variant_id}/changes` | `variant_id` | `items[]` | `items[].change_type`、`items[].selector_value` 必填；整包 full-replace |
| `POST /api/editor/previews/build` | 無 | `variant_id`, `items[]` | 欄位名稱必須是 `items`（不是 `changes`） |
| `POST /api/runtime/bootstrap` | 無 | `url`, `visitor_id` | 實務上建議必帶 `site_id` 或 `site_key`，否則常拿不到候選實驗 |
| `POST /api/runtime/assign` | 無 | `visitor_id`, `experiment` | `experiment` 內 `experiment_id/experiment_key/status/variants[]` 必填；無可分流 variant 會 `422` |
| `POST /api/runtime/payload` | 無 | 同 `/assign` | 同 `/assign`；回傳 assigned variant + payload |
| `POST /api/runtime/events/impression|conversion` | 無 | `visitor_id`, `event_name` | 其餘欄位可選（建議盡量帶 site/experiment/variant） |

---

## 3. Health / Auth

### `GET /health`

- 必填：無
- 用途：服務存活檢查

### `GET /health/ready`

- 必填：無
- 用途：環境就緒檢查（DB URL / Directus URL / Token）

### `GET /api/auth/me`

- 必填 Header：`Authorization`
- 回傳：目前使用者與 permission context

---

## 4. Admin API（實際已掛載）

## Sites

### `GET /api/admin/sites`

- 必填：無（需 Authorization）

### `GET /api/admin/sites/{site_id}`

- Path 必填：`site_id`

## Experiments

### `GET /api/admin/experiments`

- 必填：無

### `GET /api/admin/experiments/{experiment_id}`

- Path 必填：`experiment_id`

### `POST /api/admin/experiments`

- Body 必填：
  - `site_id: string`
  - `name: string`
- Body 可選：
  - `module_type`（預設 `visual`）
  - `status`（預設 `draft`）
  - `start_at`
  - `end_at`
  - `targeting_config`
- 注意：
  - `experiment_key` 由後端系統生成（`EX+timestamp`）
  - 建立成功後會自動建立一筆「原始版本」Variant

### `PATCH /api/admin/experiments/{experiment_id}`

- Path 必填：`experiment_id`
- Body 全部可選：
  - `name`
  - `module_type`
  - `status`
  - `start_at`
  - `end_at`
  - `targeting_config`
- 注意：
  - JSON body 本身是必填（即使欄位都可選，仍需送 `{}` 或至少一個欄位）

### `GET /api/admin/experiments/{experiment_id}/activity`

- Path 必填：`experiment_id`
- 用途：讀 Directus activity log（操作紀錄）

## Variants

### `GET /api/admin/variants`

- Query 可選：
  - `experiment_id`

### `GET /api/admin/variants/{variant_id}`

- Path 必填：`variant_id`

### `POST /api/admin/variants`

- Body 必填：
  - `experiment_id: string`
  - `name: string`
- Body 可選：
  - `traffic_weight`（預設 0）
  - `content_config`
- 注意：
  - `variant_key` 由後端系統生成（`VA+timestamp`）
  - 業務限制：同一 experiment 全部 variants 權重總和必須等於 100（不符會 `400`）

### `PATCH /api/admin/variants/{variant_id}`

- Path 必填：`variant_id`
- Body 可選：
  - `name`
  - `traffic_weight`
  - `content_config`
- 注意：
  - 若更新 `traffic_weight`，同樣會觸發總和=100 驗證
  - JSON body 本身是必填（即使欄位都可選，仍需送 `{}` 或至少一個欄位）

## Releases

### `GET /api/admin/releases`

- Query 可選：
  - `experiment_id`

### `GET /api/admin/releases/{release_id}`

- Path 必填：`release_id`

### `POST /api/admin/releases/build`

- Body 必填：
  - `experiment_id: string`
- 用途：
  - 讀取該 experiment 下所有 variant changes
  - 產出 `runtime_payload`
  - 建立新 release（`status=draft`，`version_no` 遞增）

### `POST /api/admin/releases/{release_id}/publish`

- Path 必填：`release_id`
- 業務行為：
  - 同 experiment 其他 `published` release 會先降為 `draft`
  - 目標 release 再升為 `published`

### `POST /api/admin/releases/{release_id}/rollback`

- Path 必填：`release_id`
- 業務行為：
  - 目標 release 先降為 `draft`
  - 嘗試把同 experiment、版本號較小、最新的一筆 `draft` 升為 `published`

### `POST /api/admin/releases/{release_id}/archive`

- Path 必填：`release_id`
- 業務行為：把 release 狀態改成 `archived`

## Goals

### `GET /api/admin/goals`

- Query 可選：
  - `site_id`

### `GET /api/admin/goals/{goal_id}`

- Path 必填：`goal_id`

## SDK Configs

### `GET /api/admin/sdk-configs`

- 必填：無

### `GET /api/admin/sdk-configs/{sdk_config_id}`

- Path 必填：`sdk_config_id`

---

## 5. Editor API

## Session

### `POST /api/editor/sessions`

- Body 必填：
  - `variant_id: string`
  - `base_url: string`
- Body 可選：
  - `mode`（預設 `edit`）

### `GET /api/editor/sessions/{session_id}`

- Path 必填：`session_id`

### `PATCH /api/editor/sessions/{session_id}`

- Path 必填：`session_id`
- Body 可選：
  - `status`
  - `draft_changes`
- 注意：
  - JSON body 本身是必填（即使欄位都可選，仍需送 `{}` 或至少一個欄位）

### `DELETE /api/editor/sessions/{session_id}`

- Path 必填：`session_id`

## Variant Changes

### `GET /api/editor/variants/{variant_id}/changes`

- Path 必填：`variant_id`

### `PUT /api/editor/variants/{variant_id}/changes`

- Path 必填：`variant_id`
- Body 必填：
  - `items: EditorChangeWrite[]`
- `EditorChangeWrite` 每筆必填：
  - `change_type`
  - `selector_value`
- `EditorChangeWrite` 每筆可選：
  - `id`
  - `selector_type`（預設 `css`）
  - `sort_order`（預設 `0`）
  - `payload`
- 注意：
  - 目前是「完整覆蓋」語意：request 沒帶到的舊 change 會被刪除
  - `enabled` 欄位已移除

## Preview

### `POST /api/editor/previews/build`

- Body 必填：
  - `variant_id: string`
  - `items: EditorChangeWrite[]`
- 注意：
  - 欄位名稱是 `items`，不是 `changes`

---

## 6. Runtime API

## Bootstrap

### `POST /api/runtime/bootstrap`

- Body 必填：
  - `url: string`
  - `visitor_id: string`
- Body 可選：
  - `site_id`
  - `site_key`
  - `user_agent`
- 注意：
  - `site_id` / `site_key` 至少建議帶一個，否則通常拿不到 candidate experiments

## Assign

### `POST /api/runtime/assign`

- Body 必填：
  - `visitor_id`
  - `experiment`
- `experiment` 必填：
  - `experiment_id`
  - `experiment_key`
  - `status`
  - `variants`（陣列，可空但可能回 `422`）
- `variants[]` 每筆必填：
  - `id`
  - `variant_key`
  - `traffic_weight`
- 注意：
  - 若 `variants` 為空，或全部 `traffic_weight <= 0`，會回 `422 No assignable variants were provided.`

## Payload

### `POST /api/runtime/payload`

- Body 結構與 `/assign` 相同（`visitor_id + experiment`）
- 回傳包含：
  - `assigned_variant_id`
  - `assigned_variant_key`
  - `payload`

## Events

### `POST /api/runtime/events/impression`
### `POST /api/runtime/events/conversion`

- Body 必填：
  - `visitor_id`
  - `event_name`
- Body 可選：
  - `site_id`
  - `site_key`
  - `experiment_id`
  - `experiment_key`
  - `variant_id`
  - `variant_key`
  - `payload`

---

## 7. 資料取得時，如何判斷「是缺參數還是被規則擋」

### `bootstrap` 回傳 `candidate_experiments = []` 常見原因

1. `site_id/site_key` 無法解析成站點。
2. 該 site 沒有 `status=running` 的實驗。
3. URL 規則不匹配（`contains/equals/starts_with/regex`）。
4. 裝置不匹配（`device_targets` 與 user-agent 判斷不一致）。
5. 實驗下沒有 variants。

### `assign` 回 `422` 常見原因

1. `experiment.variants` 空陣列。
2. `experiment.variants` 全部權重為 `0` 或負值。

### `variants` 建立/更新回 `400` 常見原因

1. 同一 experiment 下，所有 variant 的 `traffic_weight` 加總不等於 `100`。

### `editor PUT changes` 看起來「少資料」的常見原因

1. 你沒有把舊 change id 一起送回去，會被 full-replace 刪掉。
2. 你送的是 `changes` 欄位而不是 `items`。

---

## 8. 你在 Swagger（/docs）建議先看哪裡

1. 先看 `auth -> GET /api/auth/me`，確認 token 與 permission 正常。
2. 再看 `admin/experiments` 與 `admin/variants` 的 request body schema。
3. 看 `editor -> PUT changes` / `POST previews/build`，確認你送的是 `items`。
4. 看 `runtime -> bootstrap/assign/payload` 的 nested schema，特別是 `experiment.variants[]`。

---

## 9. 已知注意事項（目前版本）

- `API_CONTRACT_MASTER.md` 已同步目前路由；這份文件偏重「必填參數 + 業務限制」。
- 自動化測試（P2）尚未補齊，建議以 staging smoke + 這份欄位表當暫時驗證基準。
