# API Specification — Accessible Video Processing Platform

<!-- SCOPE: api-spec | owner: ln-113 | generated: 2026-04-29 -->

**Base URL (production):** `https://ai-sandbox.oliver.solutions/video-accessibility-back`
**Base URL (local):** `http://localhost:8003`
**OpenAPI docs:** `{base_url}/docs` (Swagger UI)

All endpoints require `Authorization: Bearer <access_token>` except `/auth/login`, `/auth/refresh`, `/auth/microsoft/*`, and `/health`.

---

## Authentication

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/v1/auth/login` | None | Email/password login; returns access token + sets refresh cookie |
| POST | `/api/v1/auth/refresh` | Cookie | Exchange refresh cookie for new access token |
| POST | `/api/v1/auth/logout` | Bearer | Revoke refresh token, clear cookie |
| POST | `/api/v1/auth/microsoft/callback` | None | Microsoft SSO callback; validates OIDC token |
| GET | `/api/v1/auth/microsoft/login` | None | Redirect to Microsoft login |
| POST | `/api/v1/auth/change-password` | Bearer | Change own password |

**Login response fields:**

| Field | Type | Notes |
|-------|------|-------|
| access_token | string | JWT, 15-minute TTL |
| token_type | string | Always "bearer" |
| user | object | User profile (id, email, role, org_id) |

---

## Jobs

| Method | Path | Roles | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/jobs` | ALL | List jobs (role-filtered: client sees own, reviewer/admin see all) |
| POST | `/api/v1/jobs` | CLIENT, ADMIN | Create job with MP4 upload |
| GET | `/api/v1/jobs/{id}` | ALL | Job detail with current status + outputs |
| DELETE | `/api/v1/jobs/{id}` | ADMIN | Delete job and GCS files |
| GET | `/api/v1/jobs/{id}/downloads` | ALL | Signed download URLs for deliverables (24h expiry) |
| POST | `/api/v1/jobs/{id}/actions/approve` | REVIEWER, ADMIN | Approve job at current QC stage |
| POST | `/api/v1/jobs/{id}/actions/reject` | REVIEWER, ADMIN | Reject job with reason |
| POST | `/api/v1/jobs/{id}/actions/feedback` | REVIEWER, ADMIN | Send QC feedback without rejection |
| POST | `/api/v1/jobs/{id}/actions/retry` | ADMIN | Retry failed task (TTS_FAILED, RENDER_FAILED) |

**Job object key fields:**

| Field | Type | Notes |
|-------|------|-------|
| _id | string | MongoDB ObjectId |
| status | string | JobStatus enum — see architecture.md |
| org_id | string | Organisation that owns the job |
| source_language | string | BCP-47 language code |
| requested_outputs | array | Output language codes requested |
| outputs | object | Per-language GCS paths |
| language_qc | object | Per-language QC state |
| created_at | datetime | ISO 8601 |
| updated_at | datetime | ISO 8601 |
| error | string | Last error message if failed |

---

## VTT Management

| Method | Path | Roles | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/jobs/{id}/vtt/{lang}` | REVIEWER, ADMIN | Get VTT content for language |
| PATCH | `/api/v1/jobs/{id}/vtt/{lang}` | REVIEWER, LINGUIST, ADMIN | Update VTT content (auto-snapshots before save) |
| POST | `/api/v1/vtt/adjust-timing` | REVIEWER, ADMIN | Bulk shift all cue timings |

---

## VTT Version Control

| Method | Path | Roles | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/jobs/{id}/vtt-versions/{lang}` | REVIEWER, ADMIN | List version history |
| GET | `/api/v1/jobs/{id}/vtt-versions/{lang}/{version_id}` | REVIEWER, ADMIN | Get specific version content |
| POST | `/api/v1/jobs/{id}/vtt-versions/{lang}/{version_id}/restore` | REVIEWER, ADMIN | Restore a previous version (creates new snapshot) |
| GET | `/api/v1/jobs/{id}/vtt-versions/{lang}/diff` | REVIEWER, ADMIN | Diff two versions (`?from=v1_id&to=v2_id`) |

---

## Language QC

| Method | Path | Roles | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/jobs/{id}/language-qc` | REVIEWER, PM, ADMIN | Get per-language QC status for all languages |
| POST | `/api/v1/jobs/{id}/language-qc/{lang}/assign` | PM, ADMIN | Assign linguist to language |
| POST | `/api/v1/jobs/{id}/language-qc/{lang}/approve` | LINGUIST (assigned), PM, ADMIN | Approve language |
| POST | `/api/v1/jobs/{id}/language-qc/{lang}/reject` | LINGUIST (assigned), PM, ADMIN | Reject language with reason |
| POST | `/api/v1/jobs/{id}/language-qc/{lang}/feedback` | LINGUIST (assigned), PM, ADMIN | Send feedback without rejection |

---

## Glossaries

| Method | Path | Roles | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/glossaries` | ALL | List glossaries for current org |
| POST | `/api/v1/glossaries` | ADMIN | Create glossary |
| GET | `/api/v1/glossaries/{id}` | ALL | Get glossary with terms |
| PUT | `/api/v1/glossaries/{id}` | ADMIN | Update glossary metadata |
| DELETE | `/api/v1/glossaries/{id}` | ADMIN | Delete glossary |
| POST | `/api/v1/glossaries/{id}/terms` | ADMIN | Add term |
| DELETE | `/api/v1/glossaries/{id}/terms/{term_id}` | ADMIN | Delete term |

---

## Files

| Method | Path | Roles | Description |
|--------|------|-------|-------------|
| POST | `/api/v1/files/upload-url` | CLIENT, ADMIN | Get signed GCS upload URL |
| GET | `/api/v1/files/{job_id}/{path}` | ALL | Get signed download URL |

---

## Users and Organisations

| Method | Path | Roles | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/users/me` | ALL | Current user profile |
| GET | `/api/v1/organizations` | ADMIN | List organisations |
| POST | `/api/v1/organizations` | ADMIN | Create organisation |
| GET | `/api/v1/organizations/{id}/members` | PM, ADMIN | List org members |
| POST | `/api/v1/organizations/{id}/invite` | PM, ADMIN | Invite member |
| DELETE | `/api/v1/organizations/{id}/members/{user_id}` | PM, ADMIN | Remove member |

---

## Admin

| Method | Path | Roles | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/admin/users` | ADMIN | List all users |
| PATCH | `/api/v1/admin/users/{id}` | ADMIN | Update user role or status |
| GET | `/api/v1/admin/audit-log` | ADMIN, PM | Query audit log |

---

## WebSocket

| Path | Auth | Description |
|------|------|-------------|
| `WS /api/v1/ws/jobs/{id}` | Query param `token=<access_token>` | Real-time job status updates |
| `WS /api/v1/ws/org/{org_id}` | Query param `token=<access_token>` | Org-scoped event stream |

**Message format:**

| Field | Type | Notes |
|-------|------|-------|
| type | string | `job_status_update`, `notification`, `ping` |
| job_id | string | Job ObjectId |
| status | string | New JobStatus value |
| updated_at | datetime | ISO 8601 |

---

## Health

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/health` | None | Returns `{"status":"healthy","version":"1.0.0"}` |
| GET | `/metrics` | None (internal) | Prometheus metrics |

---

## Error Response Format

All errors return:

| Field | Type | Notes |
|-------|------|-------|
| detail | string | Human-readable error message (never internal exception text) |

Common status codes:

| Code | Meaning |
|------|---------|
| 400 | Bad request / validation error |
| 401 | Unauthenticated or invalid token |
| 403 | Forbidden — insufficient role |
| 404 | Resource not found |
| 422 | Pydantic validation error |
| 429 | Rate limit exceeded |
| 500 | Internal server error (details logged, not returned) |

---

## Maintenance

**Update triggers:** New endpoint added, request/response schema changed, auth flow change.
**Verification:** All endpoints listed here exist in `backend/app/api/v1/routes_*.py`. OpenAPI schema at `/docs` matches this table.

<!-- END SCOPE: api-spec -->
