Reference
Every endpoint.
Base URL https://www.pictureflow.pro/api/v1
GET/me
Check a key
Who the key belongs to, its permissions and limits. Any key can call it.
Example response
{
"object": "key_owner",
"user": {
"id": "cm3user01",
"name": "Sam Lee",
"email": "sam@example.com",
"company": "Northbound Pictures"
},
"plan": {
"state": "subscribed"
},
"key": {
"id": "cm3key01",
"name": "ShotGrid sync",
"prefix": "pf_live_Ab3dE9xK",
"scopes": [
"tracker:read"
],
"show_ids": [],
"project_ids": []
},
"api_version": "2026-11-01",
"rate_limit_per_minute": 600
}
GET/showstracker:read
List shows
Tracker shows the key's owner runs or was invited to. Add archived=true to include archived shows.
| archived | query | true to include archived shows. |
Example response
{
"object": "list",
"data": [
{
"object": "show",
"id": "cm3show0001",
"name": "Northbound",
"code": "NB",
"client": "Harbor Light",
"status": "active",
"archived": false,
"start_date": "2026-11-01",
"delivery_date": "2027-03-12",
"hours_per_day": 10,
"currency": "USD",
"your_role": "owner",
"steps": [
{
"id": "comp",
"name": "Compositing",
"short": "CP",
"group": "2d",
"color": "#5e5ce6"
}
],
"source_bid": null,
"url": "https://tracker.pictureflow.pro/shows/cm3show0001"
}
],
"has_more": false,
"next_cursor": null
}
GET/shows/{showId}tracker:read
Get a show
One show with its pipeline steps. Day rates and contract value are included only when the key has finance:read and its owner sees Finance.
| showId | path | The show's id, from GET /shows. |
Example response
{
"object": "show",
"id": "cm3show0001",
"name": "Northbound",
"code": "NB",
"…": "…"
}
GET/shows/{showId}/episodestracker:read
List episodes
| showId | path | The show's id, from GET /shows. |
Example response
{
"object": "list",
"data": [
{
"object": "episode",
"id": "cm3ep0001",
"code": "NB101",
"name": "Pilot",
"turnover": "2026-11-03",
"temp_date": null,
"picture_final": "2027-01-15",
"final_delivery": "2027-02-01",
"air_date": null,
"sort": 0
}
],
"has_more": false,
"next_cursor": null
}
GET/shows/{showId}/shotstracker:read
List shots and assets
Sorted oldest change first, so a sync can page through and keep the last updated_at for next time. Clients see client-facing shots only, without people or hours; vendors see their own shots.
| showId | path | The show's id, from GET /shows. |
| status | query | One or more status codes, comma separated.wtg · ip · int · crv · fin · dlv · hld · omt |
| kind | query | shot or asset.shot · asset |
| episode_id | query | Only this episode. |
| code | query | One or more shot codes, comma separated. |
| vendor | query | Only shots awarded to this vendor. |
| external_source | query | With external_id: find items by their id in another system, like shotgrid. |
| external_id | query | One or more outside ids, comma separated. |
| updated_since | query | Only items changed at or after this ISO timestamp. Use it for incremental syncs. |
| limit | query | Items per page, 1–500. Default 100. |
| cursor | query | next_cursor from the previous page. |
Example response
{
"object": "list",
"data": [
{
"object": "shot",
"id": "cm3shot0001",
"show_id": "cm3show0001",
"kind": "shot",
"code": "NB101_010",
"sequence": "NB101_010",
"episode_id": "cm3ep0001",
"episode_code": "NB101",
"description": "Sky replacement through the windshield, add snow",
"status": "ip",
"complexity": "medium",
"frames": 96,
"tags": [
"sky"
],
"due_date": "2026-12-04",
"delivered_at": null,
"thumbnail_url": "https://www.pictureflow.pro/api/v1/shots/cm3shot0001/thumbnail",
"open_notes": 1,
"cut_from_bid": false,
"sort": 1,
"assumptions": "Clean plate provided",
"lead": "Alex Rivera",
"vendor": null,
"bid_hours": 62,
"logged_hours": 18.5,
"external_ids": {
"shotgrid": "1234"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-02T17:04:11.000Z",
"updated_at": "2026-11-05T09:12:40.000Z"
}
],
"has_more": false,
"next_cursor": null
}
GET/shots/{shotId}tracker:read
Get a shot
Example response
{
"object": "shot",
"id": "cm3shot0001",
"show_id": "cm3show0001",
"kind": "shot",
"code": "NB101_010",
"sequence": "NB101_010",
"episode_id": "cm3ep0001",
"episode_code": "NB101",
"description": "Sky replacement through the windshield, add snow",
"status": "ip",
"complexity": "medium",
"frames": 96,
"tags": [
"sky"
],
"due_date": "2026-12-04",
"delivered_at": null,
"thumbnail_url": "https://www.pictureflow.pro/api/v1/shots/cm3shot0001/thumbnail",
"open_notes": 1,
"cut_from_bid": false,
"sort": 1,
"assumptions": "Clean plate provided",
"lead": "Alex Rivera",
"vendor": null,
"bid_hours": 62,
"logged_hours": 18.5,
"external_ids": {
"shotgrid": "1234"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-02T17:04:11.000Z",
"updated_at": "2026-11-05T09:12:40.000Z"
}
GET/shots/{shotId}/thumbnailtracker:read
Download a shot's thumbnail
Returns the image itself (JPEG, PNG or WebP), or redirects to it.
Example response
(image bytes)
GET/shows/{showId}/taskstracker:read
List tasks
One task per shot and pipeline step. step_id matches the show's steps.
| showId | path | The show's id, from GET /shows. |
| status | query | One or more status codes, comma separated.wtg · ip · int · crv · fin · dlv · hld · omt |
| shot_id | query | Only this shot's tasks. |
| step_id | query | Only this pipeline step. |
| assignee | query | Only tasks assigned to this name. |
| external_source | query | With external_id: find items by their id in another system, like shotgrid. |
| external_id | query | One or more outside ids, comma separated. |
| updated_since | query | Only items changed at or after this ISO timestamp. Use it for incremental syncs. |
| limit | query | Items per page, 1–500. Default 100. |
| cursor | query | next_cursor from the previous page. |
Example response
{
"object": "list",
"data": [
{
"object": "task",
"id": "cm3task0001",
"show_id": "cm3show0001",
"shot_id": "cm3shot0001",
"shot_code": "NB101_010",
"step_id": "comp",
"step_name": "Compositing",
"status": "ip",
"start_date": "2026-11-10",
"due_date": "2026-11-21",
"assignee": "Alex Rivera",
"bid_hours": 30,
"logged_hours": 12,
"external_ids": {},
"updated_at": "2026-11-05T09:12:40.000Z"
}
],
"has_more": false,
"next_cursor": null
}
GET/tasks/{taskId}tracker:read
Get a task
Example response
{
"object": "task",
"id": "cm3task0001",
"…": "…"
}
GET/shows/{showId}/notestracker:read
List notes
Oldest first. Clients only see client notes.
| showId | path | The show's id, from GET /shows. |
| shot_id | query | Only this shot's notes. |
| kind | query | client or internal.client · internal |
| status | query | open or addressed.open · addressed |
| created_since | query | Only items created at or after this ISO timestamp. |
| limit | query | Items per page, 1–500. Default 100. |
| cursor | query | next_cursor from the previous page. |
Example response
{
"object": "list",
"data": [
{
"object": "note",
"id": "cm3note0001",
"show_id": "cm3show0001",
"shot_id": "cm3shot0001",
"shot_code": "NB101_010",
"author_name": "Jordan Park",
"kind": "client",
"body": "Warm the sky slightly on the last 20 frames.",
"status": "open",
"created_at": "2026-11-06T18:30:00.000Z",
"resolved_at": null
}
],
"has_more": false,
"next_cursor": null
}
GET/shows/{showId}/memberstracker:read
List the team
People on the show and their roles. Not available to client or vendor keys. Day rates need finance:read.
| showId | path | The show's id, from GET /shows. |
Example response
{
"object": "list",
"data": [
{
"object": "member",
"id": "cm3mem0001",
"name": "Alex Rivera",
"email": "alex@example.com",
"role": "artist",
"access": "edit",
"step_id": "comp",
"signed_in": true
}
],
"has_more": false,
"next_cursor": null
}
GET/shows/{showId}/activitytracker:read
List activity
What changed on the show, oldest first. Not available to client or vendor keys.
| showId | path | The show's id, from GET /shows. |
| shot_id | query | Only this shot's history. |
| created_since | query | Only items created at or after this ISO timestamp. |
| limit | query | Items per page, 1–500. Default 100. |
| cursor | query | next_cursor from the previous page. |
Example response
{
"object": "list",
"data": [
{
"object": "activity",
"id": "cm3act0001",
"show_id": "cm3show0001",
"shot_id": "cm3shot0001",
"user_name": "Sam Lee",
"text": "NB101_010 status → In progress",
"created_at": "2026-11-05T09:12:40.000Z"
}
],
"has_more": false,
"next_cursor": null
}
GET/notes/{noteId}tracker:read
Get a note
Example response
{
"object": "note",
"id": "cm3note0001",
"…": "…"
}
GET/episodes/{episodeId}tracker:read
Get an episode
| episodeId | path | The episode's id. |
Example response
{
"object": "episode",
"id": "cm3ep0001",
"…": "…"
}
POST/shows/{showId}/shotstracker:write
Add a shot
Adds a shot or asset with its first tasks (by step_id from GET /shows/{showId}; without steps it gets a Compositing task). Any shot field can be set at the same time, and external_ids links it to your system. Needs to be on the show's team with edit access. Send an Idempotency-Key header so a retry can't add it twice.
| showId | path | The show's id, from GET /shows. |
Example body
{
"code": "NB101_020",
"episode_code": "NB101",
"description": "Wide of the harbor, add fog",
"status": "wtg",
"frames": 120,
"due_date": "2026-12-11",
"steps": [
{
"step_id": "roto",
"bid_hours": 8
},
{
"step_id": "comp",
"bid_hours": 24
}
],
"external_ids": {
"shotgrid": "1240"
}
}
Example response
{
"object": "shot",
"id": "cm3shot0002",
"show_id": "cm3show0001",
"kind": "shot",
"code": "NB101_020",
"sequence": "NB101_010",
"episode_id": "cm3ep0001",
"episode_code": "NB101",
"description": "Sky replacement through the windshield, add snow",
"status": "wtg",
"complexity": "medium",
"frames": 96,
"tags": [
"sky"
],
"due_date": "2026-12-04",
"delivered_at": null,
"thumbnail_url": "https://www.pictureflow.pro/api/v1/shots/cm3shot0001/thumbnail",
"open_notes": 1,
"cut_from_bid": false,
"sort": 1,
"assumptions": "Clean plate provided",
"lead": "Alex Rivera",
"vendor": null,
"bid_hours": 62,
"logged_hours": 18.5,
"external_ids": {
"shotgrid": "1240"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-02T17:04:11.000Z",
"updated_at": "2026-11-05T09:12:40.000Z"
}
PATCH/shots/{shotId}tracker:write
Change a shot
Send only the fields that change. A status change runs the show's status rules and sends change alerts, the same as on the website. vendor_cost needs finance:read and a producer. Vendors can change status, due date, lead and description on their own shots.
Example body
{
"status": "ip",
"lead": "Alex Rivera",
"due_date": "2026-12-04"
}
Example response
{
"object": "shot",
"id": "cm3shot0001",
"show_id": "cm3show0001",
"kind": "shot",
"code": "NB101_010",
"sequence": "NB101_010",
"episode_id": "cm3ep0001",
"episode_code": "NB101",
"description": "Sky replacement through the windshield, add snow",
"status": "ip",
"complexity": "medium",
"frames": 96,
"tags": [
"sky"
],
"due_date": "2026-12-04",
"delivered_at": null,
"thumbnail_url": "https://www.pictureflow.pro/api/v1/shots/cm3shot0001/thumbnail",
"open_notes": 1,
"cut_from_bid": false,
"sort": 1,
"assumptions": "Clean plate provided",
"lead": "Alex Rivera",
"vendor": null,
"bid_hours": 62,
"logged_hours": 18.5,
"external_ids": {
"shotgrid": "1234"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-02T17:04:11.000Z",
"updated_at": "2026-11-05T09:12:40.000Z"
}
POST/shows/{showId}/shots/batchtracker:write
Send many shots at once
Up to 500 shots per request. Each is matched first by external_id (with source), then by code. Matched shots are updated, and a different code renames them; the rest are added, unless create_missing is false. Each shot's result is reported separately, so one bad shot doesn't stop the others. Fields are the same as Add a shot. Updates are quick; adding hundreds of new shots takes longer, so a first import is smoothest in requests of 100 to 200.
| showId | path | The show's id, from GET /shows. |
Example body
{
"source": "shotgrid",
"shots": [
{
"external_id": 1234,
"code": "NB101_010",
"status": "ip",
"frames": 96
},
{
"external_id": 1240,
"code": "NB101_020",
"episode_code": "NB101",
"description": "Wide of the harbor, add fog"
}
]
}
Example response
{
"object": "batch_result",
"created": 1,
"updated": 1,
"failed": 0,
"data": [
{
"index": 0,
"ok": true,
"action": "updated",
"id": "cm3shot0001",
"code": "NB101_010"
},
{
"index": 1,
"ok": true,
"action": "created",
"id": "cm3shot0002",
"code": "NB101_020"
}
]
}
POST/shots/{shotId}/taskstracker:write
Add a task
Adds the task for one pipeline step, or updates it if the shot already has one (then the answer is 200, not 201). Takes the same fields as Change a task.
Example body
{
"step_id": "fx",
"assignee": "Morgan Diaz",
"bid_hours": 16,
"due_date": "2026-12-01"
}
Example response
{
"object": "task",
"id": "cm3task0002",
"shot_id": "cm3shot0001",
"step_id": "fx",
"step_name": "FX",
"status": "wtg",
"assignee": "Morgan Diaz",
"bid_hours": 16,
"logged_hours": 0,
"…": "…"
}
PATCH/tasks/{taskId}tracker:write
Change a task or log hours
Status, assignee, dates and hours. add_hours adds to the hours already logged, for time tracking tools that send each session (use logged_hours to set the total instead). A new assignee is told, and status changes run the show's status rules.
Example body
{
"status": "int",
"add_hours": 2.5
}
Example response
{
"object": "task",
"id": "cm3task0001",
"status": "int",
"logged_hours": 14.5,
"…": "…"
}
POST/shows/{showId}/notestracker:write
Leave a note
An internal or client note on a shot (by shot_id or shot_code), or a reply with parent_id. @mentions and the show's people are told, the same as on the website. Notes from a key show the key's name, like "Sam Lee via Review tool".
| showId | path | The show's id, from GET /shows. |
Example body
{
"shot_code": "NB101_010",
"kind": "internal",
"body": "v012 is up for review. @Jordan Park can you take a look?",
"external_ids": {
"shotgrid": "77120"
}
}
Example response
{
"object": "note",
"id": "cm3note0002",
"shot_code": "NB101_010",
"author_name": "Sam Lee via Review tool",
"kind": "internal",
"status": "open",
"…": "…"
}
PATCH/notes/{noteId}tracker:write
Change a note
Mark it addressed or open again, or change its text. The team can change any note; others only their own.
Example body
{
"status": "addressed"
}
Example response
{
"object": "note",
"id": "cm3note0001",
"status": "addressed",
"…": "…"
}
POST/shows/{showId}/episodestracker:write
Add an episode
Team only. Dates are YYYY-MM-DD.
| showId | path | The show's id, from GET /shows. |
Example body
{
"code": "NB102",
"name": "The Crossing",
"turnover": "2026-12-01",
"final_delivery": "2027-02-20",
"external_ids": {
"shotgrid": "89"
}
}
Example response
{
"object": "episode",
"id": "cm3ep0002",
"code": "NB102",
"name": "The Crossing",
"…": "…"
}
PATCH/episodes/{episodeId}tracker:write
Change an episode
| episodeId | path | The episode's id. |
Example body
{
"picture_final": "2027-01-22"
}
Example response
{
"object": "episode",
"id": "cm3ep0001",
"picture_final": "2027-01-22",
"…": "…"
}
POST/shows/{showId}/uploadstracker:write
Upload a review copy or thumbnail
Step one of hosting a file. Say what you're sending; you get back an upload id and an address. PUT the file's bytes to upload.url with exactly the headers in upload.headers (no API key), then pass the id as review_upload_id or thumbnail_upload_id when you send the version. Review copies (MP4, MOV, WebM or an image) count against the account's render storage, are cleared after 30 days unless approved or kept, and can be up to 250 MB; thumbnails (JPEG, PNG, WebP) up to 10 MB. If the show's owner connected their own storage in Tracker Settings, the address points at their bucket instead: files up to 5 GB, nothing counted against our storage. The address works for an hour. Too big, or no storage left: 413 with code file_too_large or storage_full; send a source_url instead.
| showId | path | The show's id, from GET /shows. |
Example body
{
"kind": "review",
"content_type": "video/mp4",
"size": 18432000,
"filename": "NB101_010_v012.mp4"
}
Example response
{
"object": "upload",
"id": "up_eyJzIjoi…",
"kind": "review",
"content_type": "video/mp4",
"size": 18432000,
"expires_at": "2026-11-06T18:02:11.000Z",
"upload": {
"url": "https://blob.vercel-storage.com/?pathname=…",
"method": "PUT",
"headers": {
"authorization": "Bearer vercel_blob_client_…",
"x-api-version": "9",
"x-content-type": "video/mp4",
"x-add-random-suffix": "0"
}
}
}
POST/shots/{shotId}/versionstracker:write
Send a version for review
Adds the shot's next version (v001, v002…) to the Review page and tells the shot's lead, the same as sending one on the website. Point source_url at the original on your own storage (free, shown as "Watch"), add a hosted review copy and thumbnail from POST /shows/{showId}/uploads, or both. Pick the task with task_id or step_id. Vendors can send versions on their own shots. keep (hold the review copy past 30 days) is for the show's team.
Example body
{
"step_id": "comp",
"source_url": "https://media.northbound.example/NB101_010/comp/v012/NB101_010_comp_v012.mov",
"review_upload_id": "up_eyJzIjoi…",
"thumbnail_upload_id": "up_eyJrIjoi…",
"comment": "Sky warmed on the last 20 frames",
"external_ids": {
"shotgrid": "45210"
}
}
Example response
{
"object": "shot_version",
"id": "cm3sv0012",
"show_id": "cm3show0001",
"shot_id": "cm3shot0001",
"shot_code": "NB101_010",
"task_id": "cm3task0001",
"number": 12,
"name": "v012",
"status": "pending",
"comment": "Sky warmed on the last 20 frames",
"source_url": "https://media.northbound.example/NB101_010/comp/v012/NB101_010_comp_v012.mov",
"review": {
"url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.mp4",
"content_type": "video/mp4",
"size": 18432000,
"removed_at": null
},
"thumbnail_url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.jpg",
"keep": false,
"submitted_by": "Sam Lee via Publish tool",
"external_ids": {
"shotgrid": "45210"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-06T17:02:11.000Z",
"reviewed_at": null
}
GET/shots/{shotId}/versionstracker:read
List a shot's versions
Newest first. Not available to client keys: clients review through review links.
Example response
{
"object": "list",
"data": [
{
"object": "shot_version",
"id": "cm3sv0012",
"show_id": "cm3show0001",
"shot_id": "cm3shot0001",
"shot_code": "NB101_010",
"task_id": "cm3task0001",
"number": 12,
"name": "v012",
"status": "pending",
"comment": "Sky warmed on the last 20 frames",
"source_url": "https://media.northbound.example/NB101_010/comp/v012/NB101_010_comp_v012.mov",
"review": {
"url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.mp4",
"content_type": "video/mp4",
"size": 18432000,
"removed_at": null
},
"thumbnail_url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.jpg",
"keep": false,
"submitted_by": "Sam Lee via Publish tool",
"external_ids": {
"shotgrid": "45210"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-06T17:02:11.000Z",
"reviewed_at": null
}
],
"has_more": false,
"next_cursor": null
}
GET/shows/{showId}/versionstracker:read
List a show's versions
Oldest first, paged. Vendors see their own shots' versions.
| showId | path | The show's id, from GET /shows. |
| shot_id | query | Only this shot's versions. |
| status | query | pending, notes or approved, comma separated.pending · notes · approved |
| created_since | query | Only items created at or after this ISO timestamp. |
| external_source | query | With external_id: find items by their id in another system, like shotgrid. |
| external_id | query | One or more outside ids, comma separated. |
| limit | query | Items per page, 1–500. Default 100. |
| cursor | query | next_cursor from the previous page. |
Example response
{
"object": "list",
"data": [
{
"object": "shot_version",
"id": "cm3sv0012",
"show_id": "cm3show0001",
"shot_id": "cm3shot0001",
"shot_code": "NB101_010",
"task_id": "cm3task0001",
"number": 12,
"name": "v012",
"status": "pending",
"comment": "Sky warmed on the last 20 frames",
"source_url": "https://media.northbound.example/NB101_010/comp/v012/NB101_010_comp_v012.mov",
"review": {
"url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.mp4",
"content_type": "video/mp4",
"size": 18432000,
"removed_at": null
},
"thumbnail_url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.jpg",
"keep": false,
"submitted_by": "Sam Lee via Publish tool",
"external_ids": {
"shotgrid": "45210"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-06T17:02:11.000Z",
"reviewed_at": null
}
],
"has_more": false,
"next_cursor": null
}
GET/shot_versions/{versionId}tracker:read
Get a version
| versionId | path | The version's id (from a shot_version, not a budget version). |
Example response
{
"object": "shot_version",
"id": "cm3sv0012",
"show_id": "cm3show0001",
"shot_id": "cm3shot0001",
"shot_code": "NB101_010",
"task_id": "cm3task0001",
"number": 12,
"name": "v012",
"status": "pending",
"comment": "Sky warmed on the last 20 frames",
"source_url": "https://media.northbound.example/NB101_010/comp/v012/NB101_010_comp_v012.mov",
"review": {
"url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.mp4",
"content_type": "video/mp4",
"size": 18432000,
"removed_at": null
},
"thumbnail_url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.jpg",
"keep": false,
"submitted_by": "Sam Lee via Publish tool",
"external_ids": {
"shotgrid": "45210"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-06T17:02:11.000Z",
"reviewed_at": null
}
PATCH/shot_versions/{versionId}tracker:write
Review or change a version
status (pending, notes, approved) and keep are for the show's team; approving can finish the task when the show's status rule is on, the same as on the website. Anyone who can work on the shot can change comment and source_url.
| versionId | path | The version's id (from a shot_version, not a budget version). |
Example body
{
"status": "approved"
}
Example response
{
"object": "shot_version",
"id": "cm3sv0012",
"show_id": "cm3show0001",
"shot_id": "cm3shot0001",
"shot_code": "NB101_010",
"task_id": "cm3task0001",
"number": 12,
"name": "v012",
"status": "approved",
"comment": "Sky warmed on the last 20 frames",
"source_url": "https://media.northbound.example/NB101_010/comp/v012/NB101_010_comp_v012.mov",
"review": {
"url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.mp4",
"content_type": "video/mp4",
"size": 18432000,
"removed_at": null
},
"thumbnail_url": "https://abc123.public.blob.vercel-storage.com/tracker/cm3show0001/api/NB101_010_v012.jpg",
"keep": false,
"submitted_by": "Sam Lee via Publish tool",
"external_ids": {
"shotgrid": "45210"
},
"url": "https://tracker.pictureflow.pro/shows/cm3show0001/shots?shot=cm3shot0001",
"created_at": "2026-11-06T17:02:11.000Z",
"reviewed_at": "2026-11-07T10:15:00.000Z"
}
GET/shot_versions/{versionId}/reviewtracker:read
Download the review copy
Returns the file, or redirects to it.
| versionId | path | The version's id (from a shot_version, not a budget version). |
Example response
(video or image bytes)
GET/shot_versions/{versionId}/thumbnailtracker:read
Download the thumbnail
Returns the image, or redirects to it.
| versionId | path | The version's id (from a shot_version, not a budget version). |
Example response
(image bytes)
GET/projectsbudgets:read
List projects
Budgets and bids from every PictureFlow app that the key's owner owns or was invited to.
| archived | query | true to include archived projects. |
Example response
{
"object": "list",
"data": [
{
"object": "project",
"id": "cm3proj01",
"name": "Northbound VFX bid",
"client": "Harbor Light",
"kind": "vfx",
"type": "shot_bid",
"app": "vfx",
"currency": "USD",
"archived": false,
"your_role": "owner",
"section_limited": false,
"url": "https://vfx.pictureflow.pro/app/p/cm3proj01"
}
],
"has_more": false,
"next_cursor": null
}
GET/projects/{projectId}budgets:read
Get a project
The project with its versions and their totals.
| projectId | path | The project's id. |
Example response
{
"object": "project",
"id": "cm3proj01",
"…": "…",
"versions": [
{
"object": "version",
"id": "cm3ver01",
"name": "Bid v2",
"locked": false,
"currency": "USD",
"total": 412500
}
]
}
GET/projects/{projectId}/versionsbudgets:read
List versions
| projectId | path | The project's id. |
Example response
{
"object": "list",
"data": [
{
"object": "version",
"id": "cm3ver01",
"name": "Bid v2",
"locked": false,
"currency": "USD",
"total": 412500
}
],
"has_more": false,
"next_cursor": null
}
GET/versions/{versionId}/topsheetbudgets:read
Get a topsheet
Section and account totals, fringes, fees, discounts and actuals, calculated the same way as the app. Includes the locked-in bid beside each total when there is one.
| versionId | path | The version's id. |
Example response
{
"object": "topsheet",
"version_id": "cm3ver01",
"version_name": "Bid v2",
"currency": "USD",
"sections": [
{
"id": "s1",
"code": "A",
"name": "Pre-production",
"kind": "btl",
"subtotal": 18200,
"fringe": 3100,
"total": 21300,
"actual": 0,
"bid": 21300,
"accounts": [
{
"id": "a1",
"code": "1",
"name": "Producer",
"total": 6500,
"actual": 0,
"bid": 6500
}
]
}
],
"sections_total": 371000,
"fees": [
{
"name": "Production fee",
"percent": 10,
"base": 371000,
"amount": 37100
}
],
"grand_total": 412500,
"actual_total": 0
}
GET/versions/{versionId}/shotsbudgets:read
Get a VFX shot bid
Every shot and asset in a VFX bid with days per step and cost. Use it to create shots and tasks in your own tracker.
| versionId | path | The version's id. |
Example response
{
"object": "shot_bid",
"version_id": "cm3ver01",
"currency": "USD",
"group_by": "episode",
"hours_per_day": 10,
"steps": [
{
"id": "comp",
"name": "Compositing",
"day_rate": 650,
"days": 42,
"cost": 27300
}
],
"shots": [
{
"object": "bid_shot",
"id": "b1",
"code": "NB101_010",
"description": "Sky replacement",
"status": "awarded",
"complexity": "medium",
"frames": 96,
"days": {
"comp": 3,
"roto": 2
},
"total_days": 5,
"cost": 3050
}
],
"assets": [],
"counts": {
"bid": 0,
"awarded": 10,
"omit": 0
},
"total": 61200
}
POST/shows/{showId}/external_idstracker:write
Link items to your ids
Tie shots, tasks, notes and episodes to their ids in ShotGrid, ftrack or your own tools, up to 1,000 at a time. Find each item by our id, by code (shots, episodes), or by shot_code and step_id (tasks). The link then shows in external_ids, and you can look items up with external_source and external_id. Send external_id null to remove a link. Only the links change; the items themselves don't. Needs edit access to the show.
| showId | path | The show's id, from GET /shows. |
Example body
{
"links": [
{
"entity": "shot",
"code": "NB101_010",
"source": "shotgrid",
"external_id": "1234"
},
{
"entity": "task",
"shot_code": "NB101_010",
"step_id": "comp",
"source": "shotgrid",
"external_id": "56789"
},
{
"entity": "episode",
"code": "NB101",
"source": "shotgrid",
"external_id": "88"
}
]
}
Example response
{
"object": "list",
"data": [
{
"index": 0,
"ok": true,
"entity": "shot",
"id": "cm3shot0001",
"source": "shotgrid",
"external_id": "1234"
},
{
"index": 1,
"ok": false,
"entity": "task",
"error": "not_found"
}
],
"linked": 1,
"not_found": 1
}
GET/webhookswebhooks:manage
List webhooks
Webhooks belonging to the key's owner.
Example response
{
"object": "list",
"data": [
{
"object": "webhook",
"id": "cm3hook01",
"url": "https://pipeline.example.com/pictureflow",
"description": "Slack #vfx-status",
"show_id": null,
"events": [
"shot.status_changed",
"note.created"
],
"active": true,
"disabled_reason": null
}
],
"has_more": false,
"next_cursor": null
}
POST/webhookswebhooks:manage
Add a webhook
PictureFlow will POST matching events to this https URL. events takes exact types, patterns like shot.* or * for everything. Leave show_id out for every show you're on the team of. The signing secret is only returned here, so store it.
Example body
{
"url": "https://pipeline.example.com/pictureflow",
"events": [
"shot.status_changed",
"task.status_changed",
"note.created"
],
"show_id": "cm3show0001",
"description": "Slack #vfx-status"
}
Example response
{
"object": "webhook",
"id": "cm3hook01",
"url": "https://pipeline.example.com/pictureflow",
"description": "Slack #vfx-status",
"show_id": null,
"events": [
"shot.status_changed",
"note.created"
],
"active": true,
"disabled_reason": null,
"secret": "whsec_…"
}
GET/webhooks/{webhookId}webhooks:manage
Get a webhook
| webhookId | path | The webhook's id. |
Example response
{
"object": "webhook",
"id": "cm3hook01",
"url": "https://pipeline.example.com/pictureflow",
"description": "Slack #vfx-status",
"show_id": null,
"events": [
"shot.status_changed",
"note.created"
],
"active": true,
"disabled_reason": null
}
PATCH/webhooks/{webhookId}webhooks:manage
Change a webhook
Change its URL, events or description, or turn it off and on. Turning it back on clears a switch-off after repeated failures.
| webhookId | path | The webhook's id. |
Example body
{
"events": [
"*"
],
"active": true
}
Example response
{
"object": "webhook",
"id": "cm3hook01",
"url": "https://pipeline.example.com/pictureflow",
"description": "Slack #vfx-status",
"show_id": null,
"events": [
"shot.status_changed",
"note.created"
],
"active": true,
"disabled_reason": null
}
DELETE/webhooks/{webhookId}webhooks:manage
Delete a webhook
| webhookId | path | The webhook's id. |
Example response
{
"object": "webhook",
"id": "cm3hook01",
"deleted": true
}
POST/webhooks/{webhookId}/rotate_secretwebhooks:manage
Get a new signing secret
The old secret stops working straight away.
| webhookId | path | The webhook's id. |
Example response
{
"object": "webhook",
"id": "cm3hook01",
"url": "https://pipeline.example.com/pictureflow",
"description": "Slack #vfx-status",
"show_id": null,
"events": [
"shot.status_changed",
"note.created"
],
"active": true,
"disabled_reason": null,
"secret": "whsec_…"
}
POST/webhooks/{webhookId}/testwebhooks:manage
Send a test
Sends a ping event now and tells you what your server answered.
| webhookId | path | The webhook's id. |
Example response
{
"object": "webhook_delivery",
"id": "cm3del01",
"event_type": "ping",
"status": "ok",
"attempts": 1,
"response_status": 200,
"error": null
}
GET/webhooks/{webhookId}/deliverieswebhooks:manage
List deliveries
Recent deliveries, newest first: sent, retrying (with when) or failed.
| webhookId | path | The webhook's id. |
| limit | query | Items per page, 1–500. Default 100. |
Example response
{
"object": "list",
"data": [
{
"object": "webhook_delivery",
"id": "cm3del01",
"event_id": "cm3evt01",
"event_type": "shot.status_changed",
"status": "failed",
"attempts": 2,
"response_status": 503,
"error": "Your server answered 503.",
"next_attempt_at": "2026-11-05T09:42:00.000Z"
}
]
}
POST/webhooks/{webhookId}/deliveries/{deliveryId}/retrywebhooks:manage
Send a delivery again
| webhookId | path | The webhook's id. |
| deliveryId | path | The delivery's id. |
Example response
{
"object": "webhook_delivery",
"id": "cm3del01",
"status": "ok"
}
GET/eventswebhooks:manage
Read the change feed
Every event on the shows you're on the team of, oldest first, kept 30 days. For studios that can't receive webhooks through their firewall: poll with after set to the last seq you read.
| after | query | The seq of the last event you read. |
| show_id | query | Only this show. |
| types | query | Only these event types, comma separated. |
| limit | query | Items per page, 1–500. Default 100. |
Example response
{
"object": "list",
"data": [
{
"object": "event",
"id": "cm3evt01",
"seq": 1042,
"type": "shot.status_changed",
"show_id": "cm3show0001",
"actor": {
"name": "Sam Lee",
"source": "ui"
},
"data": {
"object": {
"object": "shot",
"code": "NB101_010",
"status": "crv"
},
"previous": {
"status": "int"
}
},
"created_at": "2026-11-05T09:12:40.000Z"
}
],
"has_more": false,
"last_seq": 1042
}