# VFXReview External API Reference Base URL: `https://review.twotalesvfx.com` All requests require an `Authorization` header. Two formats are accepted: ``` Authorization: Bearer X-Api-Key: ``` --- ## Endpoints Overview | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/ext/projects` | List all projects with showIds | | `GET` | `/api/ext/shots/lookup` | Look up a shot by code — primary pipeline endpoint | | `GET` | `/api/ext/projects/{projectCode}/episodes` | List episodes for a project | | `GET` | `/api/ext/projects/{projectCode}/shots` | List shots for a project by code | | `GET` | `/api/ext/shots` | List shots by database project ID | | `GET` | `/api/ext/shots/{shotId}` | Get a single shot by DB id or shot code | | `POST` | `/api/ext/shots` | Create a new shot | | `PATCH` | `/api/ext/shots/{shotId}` | Update shot fields (e.g. increment version) | --- ## Shot Status Values | Value | Description | |---|---| | `WAITING` | Not yet started | | `IN_PROGRESS` | Currently being worked on | | `IN_REVIEW` | Submitted for review | | `REVISIONS` | Changes requested | | `COMPLETE` | Approved and complete | --- ## Shot Priority Values | Value | Description | |---|---| | `LOW` | Low priority | | `NORMAL` | Default | | `HIGH` | High priority | | `URGENT` | Urgent | --- ## Shot Code Format ``` {showId}_{episode}_{scene}_{shotNumber} e.g. UNG_106_010_020 ^^^ ^^^ ^^^ ^^^ | | | shot number (zero-padded) | | scene | episode show ID ``` --- ## 1. List Projects ``` GET /api/ext/projects ``` Returns all projects with their `showId`, `code`, and metadata. Useful for populating project selectors in pipeline tools or resolving a showId → projectCode mapping. ### Query Parameters | Parameter | Required | Default | Description | |---|---|---|---| | `status` | No | `ACTIVE` | `ACTIVE`, `ARCHIVED`, or `ALL` | ### Response `200 OK` ```json { "projects": [ { "id": "cmp6l5mzq0001ua0gz07bk72f", "name": "Ungo Season 1", "code": "UNG_S1", "showId": "UNG", "projectType": "EPISODIC", "status": "ACTIVE", "startDate": "2026-01-01T00:00:00.000Z", "dueDate": "2026-12-01T00:00:00.000Z", "_count": { "shots": 312 } } ], "total": 1 } ``` ### Project Type Values | Value | Description | |---|---| | `STANDARD` | Non-episodic project | | `EPISODIC` | Series with episode numbers | ### Project Status Values | Value | Description | |---|---| | `ACTIVE` | Currently in production | | `ARCHIVED` | Completed or archived | --- ## 2. Look Up Shot by Code **Primary endpoint used by pipeline tools (After Effects, Nuke, etc.)** ``` GET /api/ext/shots/lookup?shotCode={shotCode}&projectCode={projectCode} ``` ### Query Parameters | Parameter | Required | Description | |---|---|---| | `shotCode` | Yes | Human-readable shot code, e.g. `UNG_106_010_020` | | `projectCode` | Recommended | Project code, e.g. `UNG_S1` — disambiguates if same shot code appears in multiple projects | ### Response `200 OK` ```json { "shot": { "id": "clxxxxxxxxxxxxxx", "shotCode": "UNG_106_010_020", "scene": "010", "episode": "106", "sequence": "010", "shotNumber": 20, "description": "Hero establishing wide", "notes": "Keep sky practical", "status": "IN_PROGRESS", "priority": "NORMAL", "frameStart": 1001, "frameEnd": 1120, "fps": 24, "dueDate": "2026-08-01T00:00:00.000Z", "thumbnailUrl": "/api/files/thumb_abc123", "createdAt": "2026-06-01T10:00:00.000Z", "updatedAt": "2026-07-15T14:30:00.000Z", "shotVersion": "v003", "exrOutput": "UNG_106_010_020_cmp_TT_v003", "sourceClip": "A106_C020_0601AB", "timecodeStart": "01:00:10:00", "timecodeEnd": "01:00:15:00", "clipDuration": "00:00:05:00", "seqTimecodeStart": "00:59:49:12", "seqTimecodeEnd": "00:59:55:00", "project": { "id": "cmp6l5mzq0001ua0gz07bk72f", "name": "Ungo Season 1", "code": "UNG_S1", "showId": "UNG" }, "artist": { "id": "clxxxxxxxxxxxxxx", "name": "Jane Smith", "email": "jane@studio.com" }, "tasks": [ { "id": "clxxxxxxxxxxxxxx", "title": "Comp", "type": "COMP", "status": "IN_PROGRESS" } ], "versions": [ { "id": "clxxxxxxxxxxxxxx", "versionNumber": 3, "approvalStatus": "PENDING", "reviewStatus": "PENDING", "fileUrl": "/api/files/vid_abc123", "thumbnailUrl": "/api/files/thumb_abc123", "createdAt": "2026-07-15T14:30:00.000Z" } ] } } ``` ### Pipeline-Specific Fields | Field | Type | Description | |---|---|---| | `shotVersion` | `string` | Current version string, e.g. `v003`. Used for output naming. | | `exrOutput` | `string` | Base filename for EXR renders, e.g. `UNG_106_010_020_cmp_TT_v003` | | `seqTimecodeStart` | `string` | Shot in-point in the locked picture sequence (HH:MM:SS:FF) | | `seqTimecodeEnd` | `string` | Shot out-point in the locked picture sequence (HH:MM:SS:FF) | | `sourceClip` | `string` | Original camera roll/clip name from the EDL | | `timecodeStart` | `string` | Source clip timecode in (HH:MM:SS:FF) | | `timecodeEnd` | `string` | Source clip timecode out (HH:MM:SS:FF) | | `clipDuration` | `string` | Clip duration (HH:MM:SS:FF) | --- ## 3. List Episodes ``` GET /api/ext/projects/{projectCode}/episodes ``` ### Path Parameters | Parameter | Description | |---|---| | `projectCode` | Human-readable project code, e.g. `UNG_S1` | ### Query Parameters | Parameter | Required | Description | |---|---|---| | `shotNames` | No | Pass `1` to include full per-episode shot list (default off) | ### Response `200 OK` ```json { "project": { "id": "cmp6l5mzq0001ua0gz07bk72f", "name": "Ungo Season 1", "code": "UNG_S1", "showId": "UNG" }, "episodes": [ { "episode": "106", "shotCount": 42, "sequences": ["010", "020", "030"] } ] } ``` When `shotNames=1`, each episode entry also includes a `shots` array with: `id`, `shotCode`, `episode`, `sequence`, `status`, `exrOutput`, `seqTimecodeStart`, `seqTimecodeEnd`, `sourceClip`, `timecodeStart`, `timecodeEnd`, `clipDuration`. --- ## 4. List Shots by Project Code ``` GET /api/ext/projects/{projectCode}/shots ``` ### Path Parameters | Parameter | Description | |---|---| | `projectCode` | Human-readable project code, e.g. `UNG_S1` | ### Query Parameters | Parameter | Required | Default | Description | |---|---|---|---| | `episode` | No | — | Filter by episode, e.g. `106` | | `sequence` | No | — | Filter by sequence, e.g. `010` | | `status` | No | — | Filter by shot status | | `page` | No | `1` | Page number | | `limit` | No | `200` | Results per page (max `500`) | ### Response `200 OK` ```json { "project": { "id": "...", "name": "...", "code": "UNG_S1", "showId": "UNG" }, "pagination": { "page": 1, "limit": 200, "total": 42, "pages": 1 }, "shots": [ { "id": "clxxxxxxxxxxxxxx", "shotCode": "UNG_106_010_020", "scene": "010", "episode": "106", "sequence": "010", "shotNumber": 20, "description": "Hero establishing wide", "status": "IN_PROGRESS", "priority": "NORMAL", "frameStart": 1001, "frameEnd": 1120, "fps": 24, "dueDate": "2026-08-01T00:00:00.000Z", "thumbnailUrl": null, "exrOutput": "UNG_106_010_020_cmp_TT_v003", "seqTimecodeStart": "00:59:49:12", "seqTimecodeEnd": "00:59:55:00", "sourceClip": "A106_C020_0601AB", "timecodeStart": "01:00:10:00", "timecodeEnd": "01:00:15:00", "clipDuration": "00:00:05:00", "artist": { "id": "...", "name": "Jane Smith", "email": "jane@studio.com" }, "_count": { "versions": 3, "tasks": 2 } } ] } ``` > **Note:** `shotVersion` is not included in this response. Use the lookup endpoint to get it. --- ## 5. List Shots by Project ID ``` GET /api/ext/shots?projectId={projectId} ``` Legacy endpoint. Prefer `/api/ext/projects/{projectCode}/shots` for new integrations. ### Query Parameters | Parameter | Required | Description | |---|---|---| | `projectId` | Yes | Database CUID of the project | | `episode` | No | Filter by episode | | `status` | No | Filter by shot status | | `shotCode` | No | Return only the shot matching this exact code | ### Response `200 OK` ```json { "shots": [ /* array of shot objects */ ], "total": 42 } ``` Shot objects include: `id`, `shotCode`, `scene`, `episode`, `shotNumber`, `description`, `status`, `priority`, `frameStart`, `frameEnd`, `fps`, `dueDate`, `createdAt`, `updatedAt`, `artist`, `_count`. > **Note:** Pipeline-specific fields (`exrOutput`, `shotVersion`, `seqTimecodes`, etc.) are **not** included. Use the lookup endpoint. --- ## 6. Get Single Shot ``` GET /api/ext/shots/{shotId} ``` ### By Database ID ```http GET /api/ext/shots/clxxxxxxxxxxxxxx ``` ### By Shot Code ```http GET /api/ext/shots/UNG_106_010_020?byCode=1&projectId=cmp6l5mzq0001ua0gz07bk72f ``` | Query Param | Description | |---|---| | `byCode` | Set to `1` to treat the path segment as a shot code | | `projectId` | Required when `byCode=1` to scope the lookup | ### Response `200 OK` Returns the same full shot object as the lookup endpoint, including tasks and latest version. Pipeline-specific fields (`exrOutput`, `shotVersion`, `seqTimecodeStart`, `seqTimecodeEnd`, `sourceClip`, etc.) are included. --- ## 7. Create Shot ``` POST /api/ext/shots Content-Type: application/json ``` Accepts either `application/json` or `multipart/form-data` (when uploading a thumbnail file). ### Request Body | Field | Type | Required | Description | |---|---|---|---| | `projectId` | string (CUID) | Yes | Database project ID | | `scene` | string | Yes | Scene identifier, alphanumeric + underscore, e.g. `010` | | `episode` | string | No | Episode number. Required for `EPISODIC` projects | | `description` | string | No | Shot description | | `artistId` | string (CUID) | No | Assign to an artist by their DB id | | `priority` | string | No | `LOW`, `NORMAL`, `HIGH`, or `URGENT` (default `NORMAL`) | | `fps` | number | No | Frame rate (default `24`) | | `frameStart` | integer | No | First frame number | | `frameEnd` | integer | No | Last frame number | | `dueDate` | string (ISO 8601) | No | Due date | | `thumbnailUrl` | string (URL) | No | Pre-hosted thumbnail URL | | `thumbnail` | File | No | Thumbnail image upload (multipart only, max 50 MB) | | `shotGroupName` | string | No | Shot group name — created automatically if it doesn't exist | | `shotCode` | string | No | Explicit shot code — bypasses auto-generation, must be unique in the project | ### Response `201 Created` ```json { "shot": { "id": "clxxxxxxxxxxxxxx", "shotCode": "UNG_106_010_0010", "scene": "010", "episode": "106", "shotNumber": 10, "description": null, "status": "WAITING", "priority": "NORMAL", "fps": 24, "frameStart": null, "frameEnd": null, "dueDate": null, "thumbnailUrl": null, "projectId": "cmp6l5mzq0001ua0gz07bk72f", "artistId": null, "shotGroupId": null, "createdAt": "2026-07-21T09:00:00.000Z" } } ``` ### Error `409 Conflict` Returned when an explicit `shotCode` already exists in the project: ```json { "error": "A shot with that code already exists in this project", "shotCode": "UNG_106_010_020" } ``` --- ## 8. Update Shot ``` PATCH /api/ext/shots/{shotId} Content-Type: application/json ``` Used by pipeline tools to write back metadata after a render/upload. ### Path Parameters | Parameter | Description | |---|---| | `shotId` | Database ID of the shot (from the lookup response `shot.id`) | ### Request Body | Field | Type | Required | Validation | Description | |---|---|---|---|---| | `shotVersion` | string | Yes* | Must match `v###`, e.g. `v004` | Sets the current version string | *At least one field must be provided; currently only `shotVersion` is supported. ### Example Request ```http PATCH /api/ext/shots/clxxxxxxxxxxxxxx Authorization: Bearer your-api-key Content-Type: application/json { "shotVersion": "v004" } ``` ### Response `200 OK` ```json { "success": true, "shot": { "id": "clxxxxxxxxxxxxxx", "shotVersion": "v004" } } ``` --- ## Error Responses | Status | Meaning | |---|---| | `400` | Missing required parameter | | `401` | Missing or invalid API key | | `403` | Forbidden (session-auth routes only) | | `404` | Resource not found | | `409` | Conflict (duplicate shot code) | | `413` | Payload too large (thumbnail > 50 MB) | | `422` | Validation error | | `500` | Internal server error | ```json { "error": "shotCode query param is required" } { "error": "Validation error", "details": [ ... ] } ``` --- ## After Effects Connector — Endpoints Used For reference, the endpoints called by `VFXReviewConnector.jsx`: | Action | Endpoint | |---|---| | Populate Episode dropdown | `GET /api/ext/projects/{PROJECT_CODE}/episodes` | | Populate Shot dropdown | `GET /api/ext/projects/{PROJECT_CODE}/shots?episode={value}` | | All shot operations (overlay, queue, preview, delivery) | `GET /api/ext/shots/lookup?shotCode={code}&projectCode={PROJECT_CODE}` | | Increment Shot Version | `PATCH /api/ext/shots/{shot.id}` with `{ "shotVersion": "v00X" }` |