API Update
Deploy / deploy (push) Successful in 2m45s

This commit is contained in:
twotalesanimation
2026-07-21 09:53:12 +02:00
parent 3494c9a2b3
commit 243fbbce7a
2 changed files with 575 additions and 0 deletions
+515
View File
@@ -0,0 +1,515 @@
# 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) |
---
## 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" }` |
+60
View File
@@ -0,0 +1,60 @@
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
// ── Auth ─────────────────────────────────────────────────────────────────────
function isAuthorized(req: NextRequest): boolean {
const apiKey = process.env.API_SECRET_KEY;
if (!apiKey) return false;
const authHeader = req.headers.get("authorization") ?? "";
if (authHeader.startsWith("Bearer ")) return authHeader.slice(7) === apiKey;
return (req.headers.get("x-api-key") ?? "") === apiKey;
}
// ── GET /api/ext/projects ─────────────────────────────────────────────────────
//
// Returns all projects with their showIds. Useful for pipeline tools that need
// to enumerate available projects and resolve a showId → projectCode mapping.
//
// Query params (all optional):
// status Filter by project status: ACTIVE (default) | ARCHIVED | ALL
//
// Example:
// GET /api/ext/projects
// GET /api/ext/projects?status=ALL
export async function GET(req: NextRequest) {
if (!isAuthorized(req)) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { searchParams } = new URL(req.url);
const statusParam = searchParams.get("status")?.toUpperCase() ?? "ACTIVE";
const whereStatus =
statusParam === "ALL"
? undefined
: statusParam === "ARCHIVED"
? { status: "ARCHIVED" as const }
: { status: "ACTIVE" as const };
const projects = await db.project.findMany({
where: whereStatus,
orderBy: [{ showId: "asc" }, { code: "asc" }],
select: {
id: true,
name: true,
code: true,
showId: true,
projectType: true,
status: true,
startDate: true,
dueDate: true,
_count: {
select: { shots: true },
},
},
});
return NextResponse.json({ projects, total: projects.length });
}