PictureFlow API

Plug PictureFlow into your pipeline. Keep the tools you already built.

ShotGrid, ftrack, in-house trackers, publish tools and editorial scripts can read your shows and bids straight from PictureFlow. If you’d rather not write the glue yourself, we’ll help you hook in.

API keys

Make a key in Settings. It sees only what you can see, can be limited to one show, and can be revoked any time.

Available now

Read and write

Read shows, shots, tasks, notes, people, bids and topsheets today. Creating and updating shots, tasks and notes by your own ids is next.

Reading now · writing next

Change alerts

Signed webhooks when a status changes, a note lands or a version is posted, plus a change feed for studios behind a firewall.

Coming soon

ShotGrid link

Connect a show to a ShotGrid project with your admin’s script key. Import shots and tasks, push awarded bids and statuses back.

Coming soon

Quick start

Three steps. About five minutes.

  1. 1. Make a key. In Settings › API keys, name it after the tool that will use it and pick what it can read. Copy it into your pipeline’s secrets store.
  2. 2. Check it works. Call /me. You get back who the key belongs to and what it may do.
  3. 3. Sync. Page through shots or tasks with updated_since and keep the newest updated_at for next time.
Shell
curl https://www.pictureflow.pro/api/v1/me \
  -H "Authorization: Bearer $PICTUREFLOW_KEY"
Python: incremental shot sync
import os, requests

API = "https://www.pictureflow.pro/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['PICTUREFLOW_KEY']}"

def shots(show_id, updated_since=None):
    """Every shot that changed since the last sync, one page at a time."""
    params = {"limit": 500}
    if updated_since:
        params["updated_since"] = updated_since
    while True:
        page = session.get(f"{API}/shows/{show_id}/shots", params=params).json()
        yield from page["data"]
        if not page["has_more"]:
            break
        params["cursor"] = page["next_cursor"]

for show in session.get(f"{API}/shows").json()["data"]:
    for shot in shots(show["id"]):
        print(show["code"], shot["code"], shot["status"], shot.get("bid_hours"))

How it works

The rules, in one place.

Keys act as you
Send Authorization: Bearer pf_live_…. A key sees exactly what its owner sees on the website: a vendor’s key gets that vendor’s shots, a client’s key gets client-facing shots and client notes. Keys can be limited to one show.
Permissions
  • tracker:read Shows, episodes, shots, tasks, notes, people and activity.
  • budgets:read Projects, versions, topsheets and VFX shot bids.
  • finance:read Day rates, vendor costs and contract value, for people who already see Finance.
Pages
Lists return data, has_more and next_cursor. Pass the cursor back as ?cursor=. Up to 500 per page.
Limits
600 requests a minute per key. Every response carries RateLimit-Remaining; over the limit you get a 429 with Retry-After.
Errors
Always JSON: {"error": {"type", "code", "message", "request_id"}}. Types: invalid_request 400, authentication 401, plan_required 402, permission 403, not_found 404, rate_limited 429.
Format
snake_case JSON. Dates are YYYY-MM-DD, times are ISO 8601 in UTC, money is in the show or project currency. The full description is at openapi.json for Postman and code generators.

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.

archivedquerytrue 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.

showIdpathThe show's id, from GET /shows.
Example response
{
  "object": "show",
  "id": "cm3show0001",
  "name": "Northbound",
  "code": "NB",
  "…": "…"
}
GET/shows/{showId}/episodestracker:read

List episodes

showIdpathThe 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.

showIdpathThe show's id, from GET /shows.
statusqueryOne or more status codes, comma separated.wtg · ip · int · crv · fin · dlv · hld · omt
kindqueryshot or asset.shot · asset
episode_idqueryOnly this episode.
codequeryOne or more shot codes, comma separated.
vendorqueryOnly shots awarded to this vendor.
updated_sincequeryOnly items changed at or after this ISO timestamp. Use it for incremental syncs.
limitqueryItems per page, 1–500. Default 100.
cursorquerynext_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": {},
      "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

shotIdpathThe shot's id.
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": {},
  "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.

shotIdpathThe shot's id.
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.

showIdpathThe show's id, from GET /shows.
statusqueryOne or more status codes, comma separated.wtg · ip · int · crv · fin · dlv · hld · omt
shot_idqueryOnly this shot's tasks.
step_idqueryOnly this pipeline step.
assigneequeryOnly tasks assigned to this name.
updated_sincequeryOnly items changed at or after this ISO timestamp. Use it for incremental syncs.
limitqueryItems per page, 1–500. Default 100.
cursorquerynext_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

taskIdpathThe task's id.
Example response
{
  "object": "task",
  "id": "cm3task0001",
  "…": "…"
}
GET/shows/{showId}/notestracker:read

List notes

Oldest first. Clients only see client notes.

showIdpathThe show's id, from GET /shows.
shot_idqueryOnly this shot's notes.
kindqueryclient or internal.client · internal
statusqueryopen or addressed.open · addressed
created_sincequeryOnly items created at or after this ISO timestamp.
limitqueryItems per page, 1–500. Default 100.
cursorquerynext_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.

showIdpathThe 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.

showIdpathThe show's id, from GET /shows.
shot_idqueryOnly this shot's history.
created_sincequeryOnly items created at or after this ISO timestamp.
limitqueryItems per page, 1–500. Default 100.
cursorquerynext_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/projectsbudgets:read

List projects

Budgets and bids from every PictureFlow app that the key's owner owns or was invited to.

archivedquerytrue 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.

projectIdpathThe 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

projectIdpathThe 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.

versionIdpathThe 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.

versionIdpathThe 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
}

Already have a pipeline? We’ll help you hook in.

Whether you run ShotGrid, ftrack or tools you built yourselves, tell us how your studio publishes, reviews and delivers. We’ll help connect PictureFlow to it, in whichever direction you want the data to flow.