Skip to content

Project Canvas API

Base path: /api/projects/{project_id}

All endpoints require authentication. See API Overview for auth details. The views these endpoints serve are described in the Project Timeline & Canvas guide.

Live updates arrive on /ws/events as project.schedule.updated, project.canvas.updated and project.canvas.generation (each payload names its project_id), plus swarm.schedule.slip for a swarm run whose forecast slips past a confirmed deadline.


GET /api/projects/{project_id}/schedule

Returns the schedule settings and every item: {settings, items}.

PUT /api/projects/{project_id}/schedule

Any of timezone (IANA name), start_date (YYYY-MM-DD), workdays (a list of weekday numbers, Monday = 0 to Sunday = 6, never empty) and default_task_days (number). Only the keys you send change; null clears timezone, start_date or default_task_days.

POST /api/projects/{project_id}/schedule/items
PATCH /api/projects/{project_id}/schedule/items/{item_id}
DELETE /api/projects/{project_id}/schedule/items/{item_id}
{
"kind": "deadline",
"title": "Qloo contest due",
"end_at": "2026-10-30",
"task_ids": ["P2.4", "P2.5"],
"confirmed": true,
"note": "From the contest rules"
}
FieldNotes
kinddeadline, milestone, operator_step, or task_dates (a date override for tasks)
start_at, end_atA date (YYYY-MM-DD) or an ISO instant. A deadline uses end_at
task_idsLinked tasks: a swarm task’s short id (P1.4) or a standard project’s task id
confirmedItems the canvas generator proposes arrive false; only confirmed items count
statusplanned, done, missed or unknown

GET /api/projects/{project_id}/timeline?run_id=&tz=&group=

The plan on a calendar. Rows come with start, end, basis (actual, running, scheduled or derived), status and slip (the deadline a row ends after, and by how many days). A running row’s start is real and its end is a forecast. Deadlines and milestones come back separately.

ParameterDescription
run_idSwarm projects: which run (default: the newest)
tzThe viewer’s zone, used only when the schedule has none
groupauto (default), generated, milestone, phase or owner

GET /api/projects/{project_id}/canvas
{
"canvas": {"canvas_id": "cnv_…", "project_kind": "swarm", "current_version_id": "cnvv_…"},
"version": {"version_id": "cnvv_…", "status": "ready", "tokens": 478146, "model": "…"},
"document": {"schema": "snippbot.canvas/1", "timeline": {}, "map": {}, "pairs": [], "layout": {}},
"overlay": [{"target": "/pairs/home/changes/0/text", "op": "set", "value": "…", "status": "ok"}],
"staleness": {"stale": false, "affected_sections": []},
"generation": {"generation_id": "cgen_…", "state": "done"},
"mockups": {"mk_…": {"width": 390, "height": 844, "thumb_asset": "…"}},
"task_labels": {}
}

overlay holds your edits, each with a status: ok, conflict (the generator changed what the edit was made over; generated holds the new value) or orphaned (its target is gone). task_labels maps a standard project’s task ids to the short labels the canvas shows.

GET /api/projects/{project_id}/canvas/versions
POST /api/projects/{project_id}/canvas/versions/{version_id}/restore

Each version has its scope, tokens, model, status and plan basis. Restore makes it current; your edits stay and are re-applied.

GET /api/projects/{project_id}/canvas/mockups/{mockup_id}
GET /api/projects/{project_id}/canvas/assets/{asset_id}?size=thumb|full
POST /api/projects/{project_id}/canvas/assets (multipart "file")
POST /api/projects/{project_id}/canvas/assets/from-file {"path": "docs/shot.png"}
GET /api/projects/{project_id}/canvas/project-images?q=
GET /api/projects/{project_id}/canvas/captures
POST /api/projects/{project_id}/canvas/captures/reindex

A mockup is served as a complete, sanitised document with a default-src 'none' CSP and its images inlined. Show it only in an <iframe sandbox="">.

PATCH /api/projects/{project_id}/canvas/overlay
DELETE /api/projects/{project_id}/canvas/overlay?target=&op=
{
"ops": [
{"target": "/boards/next:home", "op": "move", "value": {"x": 900, "y": 4100}},
{"target": "/pairs/home/changes/0/text", "op": "set", "value": "Cold-open first"},
{"target": "/notes/n1a2b3", "op": "add_note", "value": {"text": "Ask Sai", "fill": "signal", "x": 0, "y": 0}},
{"target": "/boards/changes:chat", "op": "hide", "remove": true}
]
}

Up to 100 edits per call, applied in order, all or none.

TargetOps
/title, /subtitle, /timeline/lanes/{key}/title, /timeline/rows/{key}/title|subtitleset
/map/screens/{key}/pins/{n}/text, /pairs/{key}/changes/{i}/textset
/system_map/nodes/{key}/title|notesset
/pairs/{key}/nowset {asset_id, capture_id?, caption?}
/boards/{board} (timeline, screen:{key}, now:{key}, next:{key}, changes:{key}, sys:{key})move, resize, hide, lock
/notes/{id}add_note (a sticky)
/pairsorder (pair keys, first to last)

Send "remove": true to revert an edit. "restore": true with the edit’s base_hash puts one back exactly as it was (this is how undo restores an edit whose target is gone).

GET /api/projects/{project_id}/canvas/estimate?scope=&map_type=&cap=&run_id=
POST /api/projects/{project_id}/canvas/generate
GET /api/projects/{project_id}/canvas/generations/{generation_id}?raw=1
POST /api/projects/{project_id}/canvas/generations/{generation_id}/cancel
{"scope": "all", "map_type": "screens", "cap": 600000, "model": "claude-native:claude-opus-5-5"}
FieldNotes
scopeall, timeline, map, mockups, pairs, stale, or screen:{key} (with an optional note)
capBudget tokens for this generation (default 400,000, up to 5,000,000)
retry_ofA failed, partial or cancelled generation’s id: re-runs it, reusing finished stages

Returns 202 with the generation; 409 while another generation of the canvas runs; 400 with code: "over_cap" or "over_run_budget" when the estimate doesn’t fit. The estimate returns kind (swarm or standard), agent (a standard project’s agent) and model.

POST /api/projects/{project_id}/canvas/export

Renders any missing mockup thumbnails before an export, and returns {thumbs, missing, unavailable}, where unavailable says why when Playwright can’t render. The PNG and PDF themselves are drawn in the browser.