a3859aa61b
Build QC Review Session (QC section) makes a QC_{shot}_{version} comp
for every shot in the project's QC queue. The export sits over its
earlier versions and plates, and only the export and the main plate are
switched on. Each scene also gets a continuous edit, cut to the edit
with the handles trimmed, and a contact sheet. QC Pass/Fail now act on
the shot comp in the viewer, the selected shot in a scene comp, or the
shot under the playhead in a scene edit, and label the result
green/red. The QC queue now also returns seqTimecodeStart, handleHead
and handleTail.
Compare Versions now lines versions up by EXR timecode, then by file
frame number. It used to use Export.frameStart, which is
timecode-anchored, not 1001-based.
Also includes Keep AEP (panel 2.9.0): Queue Export saves a reduced
.aep copy in each export's version folder.
Panel 2.10.0. Not yet run in After Effects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
547 lines
15 KiB
Markdown
547 lines
15 KiB
Markdown
# VFXReview External API Reference
|
|
|
|
Base URL: `https://review.twotalesvfx.com`
|
|
|
|
All requests require an `Authorization` header. Two formats are accepted:
|
|
|
|
```
|
|
Authorization: Bearer <API_SECRET_KEY>
|
|
X-Api-Key: <API_SECRET_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) |
|
|
|
|
### Render pipeline (RenderPipeline2)
|
|
|
|
| # | Method | Path | Caller | Description |
|
|
|---|--------|------|--------|-------------|
|
|
| E1 | `POST` | `/api/ext/exports` | panel | Queue Export — creates Export + RenderJob |
|
|
| E2 | `GET` | `/api/ext/exports/latest` | panel | Latest export + live status for the panel header |
|
|
| E3 | `GET` | `/api/ext/exports/{exportId}` | panel/worker | Export detail incl. validations and QC |
|
|
| E4 | `POST` | `/api/ext/exports/{exportId}/qc` | panel | QC Pass / QC Fail |
|
|
| E5 | `GET` | `/api/ext/qc/queue` | panel | Exports in READY_FOR_QC |
|
|
| E6 | `POST` | `/api/ext/workers/register` | worker | Register machine, receive tuning |
|
|
| E7 | `POST` | `/api/ext/workers/{machineId}/heartbeat` | worker | Heartbeat + cancel channel |
|
|
| E8 | `POST` | `/api/ext/render/jobs/claim` | worker | Atomically claim the next job |
|
|
| E9 | `PATCH` | `/api/ext/render/jobs/{jobId}/progress` | worker | Progress/ETA; renews the lease |
|
|
| E10 | `POST` | `/api/ext/render/jobs/{jobId}/fail` | worker | Report failure |
|
|
| E11 | `POST` | `/api/ext/render/jobs/{jobId}/complete-render` | worker | aerender finished → VALIDATING |
|
|
| E12 | `POST` | `/api/ext/render/jobs/{jobId}/validation` | worker | Report all validation results |
|
|
| E13 | `POST` | `/api/ext/render/jobs/{jobId}/finalize` | worker | Preview artifacts done → READY_FOR_QC |
|
|
| E14 | `POST` | `/api/ext/exports/{exportId}/retry` | panel | Retry a failed export |
|
|
| E20 | `POST` | `/api/ext/render/jobs/{jobId}/artifact-presign` | worker | Presigned upload URL |
|
|
| — | `POST` | `/api/ext/render/jobs/{jobId}/delivery` | worker | Report a DELIVERY_BUILD result |
|
|
|
|
Session-authenticated twins for the web UI live under `/api/render/*` and
|
|
`/api/deliveries/*` — same service layer, no API key.
|
|
|
|
---
|
|
|
|
## 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" }` |
|
|
| Queue Export | `POST /api/ext/exports` (E1) |
|
|
| Refresh Status / poll | `GET /api/ext/exports/latest` (E2) |
|
|
| Retry Export | `POST /api/ext/exports/{id}/retry` (E14) |
|
|
| Regenerate Previews | `POST /api/ext/exports/{id}/regenerate-preview` |
|
|
| Load QC Queue | `GET /api/ext/qc/queue?projectCode={PROJECT_CODE}` (E5) |
|
|
| Build QC Review Session | `GET /api/ext/qc/queue?projectCode={PROJECT_CODE}` (E5): uses `scene`, `episode`, `seqTimecodeStart`, `handleHead`, `handleTail` |
|
|
| QC Pass / QC Fail | `POST /api/ext/exports/{id}/qc` (E4) |
|