Backend (Phase A): - A1: Adaptive silence buffer — natural_gap_ms persisted per cue; renderer computes per-cue silence_before/silence_after instead of fixed 500ms; per-cue silence files - A2: Forward-preferred snap — snap_pause_point prefers boundaries up to 4s ahead over boundaries within 1.5s behind, reducing mid-scene cuts - A3: Min-gap validation — pause points with < 200ms gap trigger forward search to the next acceptable gap - natural_gap_ms added to PausePointData model and api.ts type - New config fields: whisper_snap_forward_window, whisper_snap_backward_window, ad_silence_buffer_default, ad_silence_buffer_min_after, ad_min_acceptable_gap - Tests: test_whisper_snap.py (13 tests), test_video_renderer_buffers.py Frontend (Phase B): - B1: Drag pause-point markers — pointer state machine with 3px move threshold, clamp to min/max bounds, click-without-move still opens PausePointEditor - B2: Drag freeze blocks — orange blocks translate with linked pause point - B3: Time tooltip visible during drag, hidden on release - Tests: TimelinePreview.drag.test.tsx (10 tests) Fixes: - Share link pointed to ai-sandbox.oliver.solutions — added app_url to Settings with correct optical-dev.oliver.solutions default; share_url now configurable via APP_URL env var - Removed all ai-sandbox.oliver.solutions references from docker-compose, apache config, docs, and scripts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
397 lines
17 KiB
Markdown
397 lines
17 KiB
Markdown
# 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
|