# API Specification — Accessible Video Processing Platform

<!-- SCOPE: REST API contracts — endpoints, auth scheme, request/response schemas, error codes. No implementation code. -->
<!-- DOC_KIND: reference -->
<!-- DOC_ROLE: canonical -->
<!-- READ_WHEN: Read when implementing a client call, debugging a 4xx/5xx, or adding a new route. -->
<!-- SKIP_WHEN: Skip when you only need database schema or infrastructure details. -->
<!-- PRIMARY_SOURCES: backend/app/api/v1/routes_*.py, backend/app/schemas/ -->

**Generated:** 2026-05-01

---

## Quick Navigation

- [Docs Hub](../README.md)
- [Architecture](architecture.md)
- [Database Schema](database_schema.md)
- [Tech Stack](tech_stack.md)

## Agent Entry

| Signal | Value |
|--------|-------|
| Purpose | Authoritative REST API contract for all backend routes |
| Read When | You need endpoint paths, auth requirements, or request/response shapes |
| Skip When | You need DB schema → database_schema.md; infrastructure → infrastructure.md |
| Canonical | Yes |
| Next Docs | [Architecture](architecture.md), [Database Schema](database_schema.md) |
| Primary Sources | `backend/app/api/v1/routes_*.py` |

---

## Base URLs

| Environment | URL |
|-------------|-----|
| Production | `https://optical-dev.oliver.solutions/video-accessibility-back` |
| Local (Docker) | `http://localhost:8012` |
| OpenAPI (Swagger) | `{base_url}/docs` |

All routes are prefixed with `/api/v1/`.

---

## Authentication

**Scheme:** Bearer token (access token in memory) + HttpOnly refresh cookie.

| Header / Cookie | Description |
|-----------------|-------------|
| `Authorization: Bearer <access_token>` | Required for all protected endpoints |
| `refresh_token` cookie (HttpOnly) | Used by `/auth/refresh` only |

Roles: `client`, `linguist`, `reviewer`, `production`, `project_manager`, `admin`.

### Auth Endpoints

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/auth/login` | None | Email/password login; returns access token + sets refresh cookie |
| POST | `/auth/microsoft` | None | Microsoft OIDC token validation; returns access + refresh |
| POST | `/auth/refresh` | Cookie | Exchange refresh cookie for new access token |
| POST | `/auth/logout` | Bearer | Revoke refresh token, clear cookie |

**Login request:**
```json
{ "email": "user@example.com", "password": "secret" }
```

**Login response:**
```json
{ "access_token": "eyJ...", "token_type": "bearer", "user": { "id": "...", "email": "...", "role": "admin" } }
```

---

## Jobs

### Upload (resumable)

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/jobs/upload/init` | Bearer | Initialise resumable GCS upload; returns signed upload URL |
| POST | `/jobs/upload/complete` | Bearer | Finalise upload; creates Job record and enqueues ingestion |

**`/jobs/upload/init` request:**
```json
{ "filename": "video.mp4", "content_type": "video/mp4", "size_bytes": 104857600 }
```

**`/jobs/upload/complete` request:**
```json
{
  "gcs_uri": "gs://accessible-video/jobs/{job_id}/source.mp4",
  "filename": "video.mp4",
  "source_language": "en",
  "requested_outputs": {
    "captions_vtt": true,
    "audio_description_vtt": true,
    "audio_description_mp3": true,
    "accessible_video_mp4": false,
    "languages": ["fr", "de"],
    "tts_preferences": { "provider": "gemini", "model": "flash", "default_voice": "Kore" }
  }
}
```

### Job CRUD & Listing

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/jobs` | Bearer | Create job directly (small upload, no resumable) |
| GET | `/jobs` | Bearer | List jobs (paginated, filterable by status/org/client/project) |
| GET | `/jobs/{job_id}` | Bearer | Get single job with full language output map |
| PATCH | `/jobs/{job_id}` | Bearer | Update job metadata (title, notes, requested outputs) |
| DELETE | `/jobs/{job_id}` | Bearer | Soft-delete job |
| POST | `/jobs/{job_id}/clone` | Bearer | Clone a job (new upload, same config) |
| POST | `/jobs/{job_id}/cancel` | Bearer | Cancel a running job |
| POST | `/jobs/{job_id}/retry` | Bearer | Retry failed job from the failed step |
| GET | `/jobs/{job_id}/validate` | Bearer | Validate all GCS assets exist for a completed job |

**Job list query params:** `status`, `org_id`, `client_id`, `project_id`, `page`, `size`, `sort`.

**Job list response:**
```json
{ "items": [ { "id": "...", "status": "pending_qc", "title": "...", "created_at": "..." } ], "total": 42, "page": 1, "size": 20 }
```

### Job State Transitions (Actions)

| Method | Path | Auth | Roles | Description |
|--------|------|------|-------|-------------|
| POST | `/jobs/{job_id}/actions/approve_source` | Bearer | reviewer, production, admin | Approve source language VTT |
| POST | `/jobs/{job_id}/actions/approve_english` | Bearer | reviewer, production, admin | Approve English source specifically |
| POST | `/jobs/{job_id}/actions/reject` | Bearer | reviewer, production, admin | Reject job, return to creator |
| POST | `/jobs/{job_id}/actions/complete` | Bearer | project_manager, admin | Mark job fully complete |
| POST | `/jobs/{job_id}/actions/reject_final` | Bearer | project_manager, admin | Reject at final review stage |
| POST | `/jobs/{job_id}/actions/return_to_qc` | Bearer | project_manager, admin | Return from final review to QC |
| POST | `/jobs/{job_id}/actions/blocked_on_source` | Bearer | production, admin | Flag source video issue |
| POST | `/jobs/{job_id}/actions/promote_to_qc` | Bearer | production, admin | Manually advance to pending_qc |
| POST | `/jobs/{job_id}/actions/retry_tts` | Bearer | production, admin | Retry TTS generation for a language |

### Bulk Job Actions

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| DELETE | `/jobs/bulk` | Bearer | Bulk delete jobs by IDs |
| POST | `/jobs/bulk/approve` | Bearer | Bulk approve source for multiple jobs |
| POST | `/jobs/bulk/return-to-qc` | Bearer | Bulk return jobs to QC |
| POST | `/jobs/bulk/download` | Bearer | Generate signed download ZIP for multiple jobs |

### VTT Editing

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/jobs/{job_id}/vtt` | Bearer | Get VTT content for a language/kind (`?lang=en&kind=captions`) |
| PATCH | `/jobs/{job_id}/vtt` | Bearer | Save edited VTT content (creates new version) |
| POST | `/jobs/{job_id}/vtt/adjust-timing` | Bearer | Bulk shift cue timestamps |

### VTT Versions

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/jobs/{job_id}/vtt/versions` | Bearer | List VTT version history |
| GET | `/jobs/{job_id}/vtt/versions/{version}` | Bearer | Get specific version content |
| GET | `/jobs/{job_id}/vtt/versions/diff` | Bearer | Diff two versions |
| POST | `/jobs/{job_id}/vtt/versions/restore` | Bearer | Restore an earlier version |

### Downloads & Accessible Video

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/jobs/{job_id}/downloads` | Bearer | Get signed GCS download URLs for all assets |
| GET | `/jobs/{job_id}/accessible-video/{language}/edit-state` | Bearer | Get pause-point edit state |
| PATCH | `/jobs/{job_id}/accessible-video/{language}/pause-points/{cue_index}` | Bearer | Adjust a pause point timestamp |

---

## Language QC

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/language-qc/jobs/{job_id}/language-qc` | Bearer | Get QC state map for all languages |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/assign` | Bearer | Assign linguist to language |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/reassign` | Bearer | Reassign linguist |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/assign-reviewer` | Bearer | Assign reviewer |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/reassign-reviewer` | Bearer | Reassign reviewer |
| POST | `/language-qc/jobs/{job_id}/languages/bulk-assign` | Bearer | Bulk assign linguist/reviewer for multiple languages |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/start-work` | Bearer | Linguist starts working |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/submit` | Bearer | Linguist submits for review |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/open-review` | Bearer | Reviewer opens language for review |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/approve` | Bearer | Reviewer approves language |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/reject` | Bearer | Reviewer rejects language |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/reopen` | Bearer | Reopen after rejection |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/mark-cue-reviewed` | Bearer | Mark individual VTT cue reviewed |
| GET | `/language-qc/jobs/{job_id}/languages/{lang}/comments` | Bearer | List reviewer comments |
| POST | `/language-qc/jobs/{job_id}/languages/{lang}/comments` | Bearer | Add reviewer comment |
| GET | `/language-qc/me/language-qc-queue` | Bearer | Get current user's QC queue |

---

## Files

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/files/signed-upload` | Bearer | Get a signed GCS upload URL for arbitrary file upload |

---

## TTS

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/tts/voices` | Bearer | List available voices per provider |
| GET | `/tts/languages` | Bearer | List supported language codes |
| GET | `/tts/options` | Bearer | Get provider/model options |
| POST | `/tts/preview` | Bearer | Generate a short TTS audio preview for a voice |

---

## Briefs

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/briefs` | Bearer | List briefs (filtered by org/status) |
| POST | `/briefs` | Bearer | Create a brief (draft) |
| GET | `/briefs/{brief_id}` | Bearer | Get brief detail |
| PATCH | `/briefs/{brief_id}` | Bearer | Update brief (draft only) |
| POST | `/briefs/{brief_id}/submit` | Bearer | Submit brief for PM approval |
| POST | `/briefs/{brief_id}/approve` | Bearer | PM approves brief |

---

## Glossaries

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/glossaries` | Bearer | List glossaries for current client |
| POST | `/glossaries` | Bearer | Create glossary (XLSX upload) |
| GET | `/glossaries/{glossary_id}` | Bearer | Get glossary detail |
| GET | `/glossaries/{glossary_id}/terms` | Bearer | List terms (paginated) |
| POST | `/glossaries/{glossary_id}/versions` | Bearer | Upload new version (XLSX) |
| POST | `/glossaries/{glossary_id}/activate` | Bearer | Activate a glossary version |
| POST | `/glossaries/{glossary_id}/versions/{version_id}/reembed` | Bearer | Re-trigger embedding for a version |
| DELETE | `/glossaries/{glossary_id}` | Bearer | Archive/delete glossary |

---

## Organizations

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/organizations` | Bearer | List organisations |
| POST | `/organizations` | Bearer (admin) | Create organisation |
| GET | `/organizations/{org_id}` | Bearer | Get organisation |
| PATCH | `/organizations/{org_id}` | Bearer (admin) | Update organisation |
| GET | `/organizations/{org_id}/members` | Bearer | List members |
| POST | `/organizations/{org_id}/members` | Bearer (admin) | Add member |
| PATCH | `/organizations/{org_id}/members/{user_id}` | Bearer (admin) | Update member role |
| DELETE | `/organizations/{org_id}/members/{user_id}` | Bearer (admin) | Remove member |
| GET | `/organizations/me/memberships` | Bearer | Get current user's org memberships |

---

## Clients, Teams & Projects

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/clients` | Bearer | List clients |
| POST | `/clients` | Bearer (admin) | Create client |
| GET | `/clients/{client_id}` | Bearer | Get client |
| PATCH | `/clients/{client_id}` | Bearer (admin) | Update client |
| DELETE | `/clients/{client_id}` | Bearer (admin) | Delete client |
| POST | `/clients/{client_id}/pm` | Bearer (admin) | Assign PM to client |
| DELETE | `/clients/{client_id}/pm/{user_id}` | Bearer (admin) | Remove PM from client |
| GET | `/clients/{client_id}/pm` | Bearer | List PMs for client |
| GET | `/clients/{client_id}/teams` | Bearer | List teams |
| POST | `/clients/{client_id}/teams` | Bearer (admin) | Create team |
| PATCH | `/clients/{client_id}/teams/{team_id}` | Bearer (admin) | Update team |
| DELETE | `/clients/{client_id}/teams/{team_id}` | Bearer (admin) | Delete team |
| POST | `/clients/{client_id}/teams/{team_id}/members` | Bearer | Add team member |
| DELETE | `/clients/{client_id}/teams/{team_id}/members/{user_id}` | Bearer | Remove team member |
| GET | `/clients/all-projects` | Bearer | List all projects across clients |
| GET | `/clients/{client_id}/projects` | Bearer | List client projects |
| POST | `/clients/{client_id}/projects` | Bearer (admin) | Create project |
| PATCH | `/clients/{client_id}/projects/{project_id}` | Bearer (admin) | Update project |
| DELETE | `/clients/{client_id}/projects/{project_id}` | Bearer (admin) | Delete project |

---

## Share Tokens

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/share/jobs/{job_id}/share` | Bearer | Create share token for a job |
| GET | `/share/jobs/{job_id}/share` | Bearer | List active share tokens |
| DELETE | `/share/jobs/{job_id}/share/{token_id}` | Bearer | Revoke share token |
| GET | `/share/public/share/{token}` | None | Public job preview (client portal) |

---

## Invitations

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/invitations/preview` | None | Preview invitation details from token |
| POST | `/invitations/accept` | None | Accept invitation and create/link account |

---

## Review Notes

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/jobs/{job_id}/review-notes` | Bearer | List timestamped notes for a job asset |
| POST | `/jobs/{job_id}/review-notes` | Bearer | Create note at a video timestamp |
| GET | `/jobs/{job_id}/review-notes/{note_id}` | Bearer | Get single note |
| PATCH | `/jobs/{job_id}/review-notes/{note_id}` | Bearer | Update note |
| DELETE | `/jobs/{job_id}/review-notes/{note_id}` | Bearer | Delete note |

---

## Admin

| Method | Path | Auth | Roles | Description |
|--------|------|------|-------|-------------|
| GET | `/admin/users` | Bearer | admin | List all users |
| POST | `/admin/users` | Bearer | admin | Create user |
| GET | `/admin/users/{user_id}` | Bearer | admin | Get user |
| PATCH | `/admin/users/{user_id}` | Bearer | admin | Update user |
| DELETE | `/admin/users/{user_id}` | Bearer | admin | Delete user |
| POST | `/admin/users/{user_id}/reset-password` | Bearer | admin | Trigger password reset email |
| POST | `/admin/users/{user_id}/password/reset` | Bearer | admin | Set new password directly |
| GET | `/admin/stats` | Bearer | admin | Platform-wide stats |
| GET | `/admin/jobs/stats` | Bearer | admin | Job pipeline stats |
| GET | `/admin/health/detailed` | Bearer | admin | Detailed health check (DB, Redis, queues) |
| POST | `/admin/maintenance/reprocess-job/{job_id}` | Bearer | admin | Force reprocess a stuck job |
| GET | `/admin/audit-logs` | Bearer | admin | List audit log entries |
| GET | `/admin/audit-logs/user/{user_id}` | Bearer | admin | Audit log for a specific user |
| GET | `/admin/audit-logs/security` | Bearer | admin | Security-related audit events |
| DELETE | `/admin/audit-logs/cleanup` | Bearer | admin | Purge old audit logs |

---

## Production / Admin Production

| Method | Path | Auth | Roles | Description |
|--------|------|------|-------|-------------|
| GET | `/production/failures` | Bearer | production, admin | List failed jobs |
| POST | `/production/bulk-retry` | Bearer | production, admin | Bulk retry failed jobs |
| GET | `/production/queue-stats` | Bearer | production, admin | Celery queue depths and worker counts |
| POST | `/production/jobs/{job_id}/upload-final-vtt` | Bearer | production, admin | Manually upload a corrected VTT file |

---

## WebSocket

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/ws/status` | Bearer | WebSocket endpoint for real-time job status updates |

---

## Standard Error Responses

| Status | Meaning |
|--------|---------|
| 400 | Validation error — see `detail` field |
| 401 | Missing or expired access token |
| 403 | Insufficient role permissions |
| 404 | Resource not found |
| 409 | Conflict (e.g. duplicate email) |
| 422 | Pydantic schema validation failure |
| 500 | Internal server error |

**Error body:**
```json
{ "detail": "Job not found" }
```

---

## Maintenance

**Last Updated:** 2026-05-01

**Update Triggers:**
- New route file added (`routes_*.py`)
- Existing endpoint path or method changes
- New request/response fields in schemas
- Auth requirements change for an endpoint

**Verification:**
- [ ] All `@router.*` decorators in `backend/app/api/v1/routes_*.py` reflected here
- [ ] Auth column accurate for each endpoint
- [ ] Base URL correct for production deployment
