Connect Claude, ChatGPT, Cursor or any MCP client to your workspace. Your assistant can then create tasks, upload files, schedule posts and more.
https://timeliner.io/api/mcpPick your client.
Sign in with your Timeliner account. No key needed.
Sign in with your Timeliner account. No key needed.
Run this in your terminal:
claude mcp add --transport http timeliner https://timeliner.io/api/mcp --header "Authorization: Bearer tlsk_..."Add to ~/.cursor/mcp.json:
{
"mcpServers": {
"timeliner": {
"url": "https://timeliner.io/api/mcp",
"headers": {
"Authorization": "Bearer tlsk_..."
}
}
}
}Add to your client's MCP configuration:
{
"mcpServers": {
"timeliner": {
"type": "http",
"url": "https://timeliner.io/api/mcp",
"headers": {
"Authorization": "Bearer tlsk_..."
}
}
}
}Key-based clients: replace tlsk_... with a key from Settings → Developers → Timeliner MCP → Generate setup.
Workspace admins, plus supervisors once an admin turns on Let supervisors connect. Included on the Agency and Scale plans.
147 tools, grouped by what they touch. Tap a tool to see its inputs and an example.
create_taskCreate taskWriteCreate a task inside a project. For a variant of an existing task, set kind=variant and pass the parentTaskId of a regular or parent task together with THAT task's own projectId - a variant is always created in its parent's project (get_task and list_tasks return each task's projectId). A parentTaskId from another project, or one that is not a task in this workspace, is refused before anything is created, naming the project to use. Set internalDeadline / externalDeadline (YYYY-MM-DD) to schedule it at creation. A text-first social post (LinkedIn / Facebook) is a type=doc task created WITH its text in this one call - pass `text` (plain string, blank lines kept exactly) or `contentHtml`; then upload_file_from_url with the new taskId for an optional attachment, and schedule_post. Pass pricingTemplateId to price the task at creation (its pricing tag, default client price and editor pay); assign editors and tag their template roles afterwards with update_task (assigneeIds + assigneeRoles).
| Name | Type | Details |
|---|---|---|
projectIdrequired | uuid | |
titlerequired | string | |
type | enum | Defaults to media.One of: media, doc |
text | string | type=doc only: the document as PLAIN TEXT. Each line becomes a paragraph and each blank line a blank line, so a post publishes with exactly this spacing; markup is treated as literal text. Send this OR contentHtml, never both. |
contentHtml | string | type=doc only: the document as HTML (<p>, <strong>, <em>, <ul>/<ol>) when it needs bold or lists. An empty <p></p> is a blank line. Send this OR text, never both. |
kind | enum | Defaults to regular.One of: regular, variant |
parentTaskId | uuid | null | ONLY valid when kind=variant, where it is required. Must be a task in the same project - projectId must be the parent's own projectId (get_task returns it). Sending it without kind=variant is rejected (400) - set kind=variant too. |
subFolderId | uuid | null | Sub-folder (task group) inside the project to place the task in. Must belong to the same project. Omit or null for the project root. Sub-folder ids come from get_project's subFolders. |
statusGroup | enum | Defaults to toDo.One of: toDo, inProgress, supervisorApproval, clientApproval, endClientApproval, approved, posted, inRevision |
internalDeadline | string | null | Internal team deadline, YYYY-MM-DD or null. |
externalDeadline | string | null | Client-facing deadline, YYYY-MM-DD or null. |
startDate | string | null | Workload work-window start, YYYY-MM-DD or null. With a deadline + estimatedMinutes the task appears on the Workload view spread across working days. |
estimatedMinutes | integer | null | Total time estimate in minutes (e.g. 1440 = 24h), or null. |
externalMetadata | object | null | Arbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear. |
pricingTemplateId | uuid | Pricing template to apply (id from list_pricing_templates) - sets the task's pricing tag, its default client price and the editors' default pay, exactly as picking the template in the app's pricing dialog. Needs the finance write scope. |
projectIdrequireduuidtitlerequiredstringtypeenumDefaults to media.One of: media, doctextstringtype=doc only: the document as PLAIN TEXT. Each line becomes a paragraph and each blank line a blank line, so a post publishes with exactly this spacing; markup is treated as literal text. Send this OR contentHtml, never both.contentHtmlstringtype=doc only: the document as HTML (<p>, <strong>, <em>, <ul>/<ol>) when it needs bold or lists. An empty <p></p> is a blank line. Send this OR text, never both.kindenumDefaults to regular.One of: regular, variantparentTaskIduuid | nullONLY valid when kind=variant, where it is required. Must be a task in the same project - projectId must be the parent's own projectId (get_task returns it). Sending it without kind=variant is rejected (400) - set kind=variant too.subFolderIduuid | nullSub-folder (task group) inside the project to place the task in. Must belong to the same project. Omit or null for the project root. Sub-folder ids come from get_project's subFolders.statusGroupenumDefaults to toDo.One of: toDo, inProgress, supervisorApproval, clientApproval, endClientApproval, approved, posted, inRevisioninternalDeadlinestring | nullInternal team deadline, YYYY-MM-DD or null.externalDeadlinestring | nullClient-facing deadline, YYYY-MM-DD or null.startDatestring | nullWorkload work-window start, YYYY-MM-DD or null. With a deadline + estimatedMinutes the task appears on the Workload view spread across working days.estimatedMinutesinteger | nullTotal time estimate in minutes (e.g. 1440 = 24h), or null.externalMetadataobject | nullArbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear.pricingTemplateIduuidPricing template to apply (id from list_pricing_templates) - sets the task's pricing tag, its default client price and the editors' default pay, exactly as picking the template in the app's pricing dialog. Needs the finance write scope.{
"projectId": "2f6c1b9e-4a7d-4c3e-9b21-6d8e0f4a7c15",
"title": "Launch reel 03 - 30s cut",
"statusGroup": "toDo",
"externalDeadline": "2026-11-20"
}Key scope: write:tasks
create_task_commentPost a comment on a taskWritePost a plain team comment on a task's review thread, authored by the API key's creator - or, with parentId, a REPLY inside that comment's thread (it shows nested under it in the app, at the same timecode and version; a reply to a reply joins the thread's first comment). A new top-level comment is left on the task's CURRENT version (its fileId), so it stays with that version and does not show on versions uploaded later. Silent - no one is notified (no @mentions). Returns the created comment.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
textrequired | string | The comment text. |
parentId | uuid | Reply in this comment's thread - a comment id from list_task_comments on the SAME task (team or client comment). Omit for a new top-level comment. |
idrequireduuidThe task's UUID - from list_tasks.textrequiredstringThe comment text.parentIduuidReply in this comment's thread - a comment id from list_task_comments on the SAME task (team or client comment). Omit for a new top-level comment.{
"id": "00000000-0000-0000-0000-000000000000",
"text": "example"
}Key scope: write:tasks
delete_taskDelete taskWriteDeletesDESTRUCTIVE. Move a task to Trash (soft-delete). The task and its files leave every active list and disappear from the client's view immediately; any running timer on it is stopped. Restorable from the app's Trash page, but treat it as irreversible when planning. CONFIRM WITH THE USER FOR EACH TASK, naming it; NEVER loop this tool over a list of tasks. Idempotent; no notifications fire. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID. |
idrequireduuidThe task's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:tasks
edit_task_docEdit part of a task's document body / briefWriteChange PART of a task's rich-text content (a doc task's document body, or a media task's brief) without resending the whole thing - the right tool for any small change, and the only safe one on a large document. Each edit names where it goes: append / prepend (end / start of the document); replaceSection (a section = a top-level heading plus everything up to the next heading of the same or a higher level - html replaces the heading TOO, so start it with the heading to keep it, or send "" to delete the section); appendToSection (adds html at the end of that section); replaceText (find = an exact piece of the stored HTML as get_task_doc returns it, which must occur exactly once; html "" deletes it). Headings match on their visible text, case and spacing ignored. Edits apply in order and all or nothing, to whatever the document holds when the call lands - so you do NOT need a fresh rev, and someone editing another part of the document in the app meanwhile does not make this fail. Everything outside your edits is kept exactly as it is. A 422 means an anchor was not found or matched more than once (code anchorNotFound / anchorAmbiguous, index = which edit) and nothing was written: read the document with get_task_doc and pick an exact anchor. For doc tasks each call saves a restore point in the document's version history. Use update_task_doc only to rewrite the whole content.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
editsrequired | object[] | 1–50 edits, applied in order. |
baseRev | integer | null | Optional. Only apply if the document is still at this rev (from get_task_doc) - a 409 otherwise. Omit it (the usual case) to apply the edits to the current content. |
idrequireduuidThe task's UUID - from list_tasks.editsrequiredobject[]1–50 edits, applied in order.baseRevinteger | nullOptional. Only apply if the document is still at this rev (from get_task_doc) - a 409 otherwise. Omit it (the usual case) to apply the edits to the current content.{
"id": "00000000-0000-0000-0000-000000000000",
"edits": [
{
"op": "append",
"html": "example"
}
]
}Key scope: write:tasks
get_taskGet taskReadGet one task: title, statusGroup, customStatus ({id, name} of the workspace custom status it sits in - e.g. 'Legal sign-off' inside inProgress - or null), updatedAt (moves on any change, a status move included), kind (regular | parent | variant - a parent holds no file, its variants do), deadlines, brandId, media download links where available, externalMetadata, customValues, pricing (template, pricing tag, client price and each editor's role and pay - null without the finance read scope), and taskId (the permanent 'TL-1042' handle, null when the workspace has task IDs off). Takes the UUID - to go the other way, from a 'TL-1042' code someone quoted, call list_tasks with taskId first.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID. |
idrequireduuidThe task's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:tasks
get_task_commentGet one task comment with its repliesReadRead ONE comment of a task's review thread - a team comment or a client's share-link comment - in the same full shape list_task_comments returns (timecode, frames, smpte, version, drawings, attachments, transcriptExcerpt…), plus replies: every reply in its thread, oldest first. A reply's own id returns that reply with replies []. 404 for another task's comment, a chat message or an unknown id. Use it to re-read one note in full before acting on it.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
commentIdrequired | uuid | The comment's id - from list_task_comments. |
idrequireduuidThe task's UUID - from list_tasks.commentIdrequireduuidThe comment's id - from list_task_comments.{
"id": "00000000-0000-0000-0000-000000000000",
"commentId": "00000000-0000-0000-0000-000000000000"
}Key scope: read:tasks
get_task_docGet a task's document body / briefReadRead a task's rich-text content - for a document task this is the document body (scripts, notes); for a media task it is the brief/description. Returns contentHtml (the raw HTML), type (doc | media), and rev (a version counter). To change part of it, read it once here and then use edit_task_doc - its edits are anchored on headings or exact HTML, so they need no fresh rev. Before update_task_doc (a full rewrite), ALWAYS call this and pass the returned rev back as baseRev so your write is rejected instead of silently overwriting an edit someone made in the app meanwhile.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
idrequireduuidThe task's UUID - from list_tasks.{
"id": "9b3e7a41-5c2d-4f8e-a6b0-1d7c3e9f2a64"
}Key scope: read:tasks
get_task_transcriptRead a task's video transcriptReadRead the transcript of a media task's current video - the spoken words as plain text, plus language, word count and duration. Use it to analyze WHAT a video is about (who speaks, topics, places, products) before tagging it, summarizing it, or writing about it. status tells you what you got: ready (text present), none (never transcribed - generate_post_caption or suggest_task_tags with startTranscription:true starts one), pending/extracting/processing (in progress - retry in ~20s), no_speech, or failed. Read-only: never starts a transcription itself. 404 when the task has no media file (doc tasks, or nothing uploaded yet), and 404 code file_content_missing when the video was removed after the workspace's subscription ended (its transcript is hidden with it, and deleted with it once the file is permanently deleted). Text is bounded to the transcript's own length - long videos can be several thousand words.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID. |
idrequireduuidThe task's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:tasks
list_custom_columnsList custom columnsReadList the workspace's custom columns - the user-defined extra fields on tasks (e.g. a Dropbox link, a priority, a select, a tag). Each has an id, kind (text|date|priority|link|dateRange|select|tag), name, and - for select kinds - its options (id + label; to set a select cell send that option's id, never its label; options cannot be added through the API - a person adds them in Timeliner), or - for tag kinds - its tags (id + label + brandId). Only a tag kind column takes new labels (create_custom_column_tags / suggest_task_tags); any other kind there is a 400. A tag's brandId is null for a SHARED tag (usable on every brand) or a brand node id for a brand-scoped tag (usable only on tasks under that brand - resolve a task's brand via its project's brand). Use the column id with set_task_custom_values / create_custom_column_tags; read a task's current values from get_task's customValues. Not paginated (10 columns on most plans; unlimited on the higher plans).
No inputs.
{}Key scope: read:tasks
list_custom_statusesList custom statusesReadList the workspace's CUSTOM statuses - extra steps in the workflow such as "Legal sign-off" after In Progress. Each has an id, name, color, baseStatus (the built-in statusGroup it sits after and behaves like), brandId (null = every brand; set = that brand only) and hiddenForBrandIds (brands that turned a workspace-wide one off). Pass brandId to get only the statuses a task under that brand can use. Board order: each base, then its custom statuses. A task's current one shows as customStatus on get_task / list_tasks (null = the plain statusGroup); move a task into one with update_task's customStatusId. Not paginated (at most 15).
| Name | Type | Details |
|---|---|---|
brandId | uuid | Only the statuses usable on this brand (the task's brandId from get_task). |
brandIduuidOnly the statuses usable on this brand (the task's brandId from get_task).{}Key scope: read:tasks
list_recent_eventsList recent eventsReadWhat happened in the workspace recently, oldest first: a new version uploaded to a task (version.uploaded), a comment added (comment.created) and a task approved (task.completed) - each the exact envelope a webhook subscriber receives (id, type, createdAt, data with the task title, comment text or version number). Built for a scheduled check: pass since = when you last checked, or better, the nextCursor from your previous call as cursor (it continues exactly after the last event you saw, never repeating one; it is returned even when nothing new arrived). hasMore: true means call again with nextCursor now. Default: the last 24 hours; nothing older than 30 days is returned. Events are recorded only while the workspace has an API key, and nothing from before this feature existed is included. Comment events include team-only (visibility: team) comments - never forward those to a client.
| Name | Type | Details |
|---|---|---|
since | string | ISO-8601 instant, e.g. 2026-10-15T08:00:00Z - events at or after it. Ignored when cursor is given. Default: 24 hours ago; nothing older than 30 days is returned. |
types | enum[] | Only these event types. Default: all three.One of: version.uploaded, comment.created, task.completed |
limit | integer | Events per call, 1-100 (default 50). |
cursor | string | The nextCursor from a previous call - continue right after the last event it returned. |
sincestringISO-8601 instant, e.g. 2026-10-15T08:00:00Z - events at or after it. Ignored when cursor is given. Default: 24 hours ago; nothing older than 30 days is returned.typesenum[]Only these event types. Default: all three.One of: version.uploaded, comment.created, task.completedlimitintegerEvents per call, 1-100 (default 50).cursorstringThe nextCursor from a previous call - continue right after the last event it returned.{}Key scope: read:tasks
list_task_activityRead a task's activity / status timelineReadRead a task's full event history - created, moved between statuses, new version uploaded, commented, viewed, downloaded - oldest first, so it reads as a timeline. Use it for 'when did this go to the client?', 'how long was it in revision?', or 'who moved it?'. Each entry: createdAt, action, movedFrom/movedTo (+ the rename-proof movedFromStatusKey/movedToStatusKey), and the actor with the role they held at the time. Durations are the gap between consecutive `moved` entries - subtract the timestamps yourself. Filter with action=moved for status history alone. Pass a task id from list_tasks.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
limit | integer | How many of the newest entries (default 200, max 500). |
action | enum | Only this kind of entry. `moved` alone is the status history; `postScheduleChanged` alone is the post's schedule history (scheduled, rescheduled, set to draft, unscheduled, networks added, deleted - including changes made by schedule_post / unschedule_post).One of: viewed, downloaded, deleted, created, newVersion, moved, commented, discussed, captionEditedAfterApproval, settingsChanged, postScheduleChanged |
from | string | Only entries at or after this ISO-8601 instant. |
to | string | Only entries at or before this ISO-8601 instant. |
idrequireduuidThe task's UUID - from list_tasks.limitintegerHow many of the newest entries (default 200, max 500).actionenumOnly this kind of entry. `moved` alone is the status history; `postScheduleChanged` alone is the post's schedule history (scheduled, rescheduled, set to draft, unscheduled, networks added, deleted - including changes made by schedule_post / unschedule_post).One of: viewed, downloaded, deleted, created, newVersion, moved, commented, discussed, captionEditedAfterApproval, settingsChanged, postScheduleChangedfromstringOnly entries at or after this ISO-8601 instant.tostringOnly entries at or before this ISO-8601 instant.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:tasks
list_task_commentsList a task's commentsReadRead a task's review thread - team/authed comments AND anonymous client comments (from share links), merged oldest-first. Each comment: id, text, author, isGuest (true = client/guest), type, visibility (public = the client sees it, team = internal), mentions ({id,name} of who was tagged), resolved, parentId (a reply's thread), createdAt, editedAt. WHERE: timecode / endTimecode (seconds; endTimecode null = a point), frames {start,end} (0-based frame numbers at the version's real fps) and smpte {start,end} (HH:MM:SS:FF), null when there is no timecode or the fps is unknown; page (a PDF comment's page). WHICH VERSION: fileId and version {fileId, number (V1, V2…), current, kind video|image|pdf|audio|other, width, height, duration, fps} - version null = a comment with no version, which shows on every version. transcriptExcerpt {text, turns[{speaker,text}], truncated}: the words spoken IN THE VIDEO during a range comment (on a thread's first comment; null otherwise). textAnchor {quotedText}: the passage a document or caption comment was left on. attachments (name, mimeType, size, a downloadUrl valid 7 days - fetch it to see an attached image or file; removed:true = purged, no link; a voice memo's transcript = what the REVIEWER said) and drawings (type freehand|rectangle|circle|arrow|pin, color, lineThickness, opacity, page, normalized 0-1 bounds on the picture with the letterbox excluded - x × the version's width is the source pixel; arrows add from/to, circles center/radius, pins point, freehand points ≤128). A comment can be ONLY a drawing or an attachment with empty text - read both before calling it empty. Filters: version (current, or a fileId from version.fileId), resolved (open | resolved - a reply follows its thread), sort (created | timecode = timeline order, each reply under its thread). Use it for 'what did the client say on this task?' and before editing from the notes. Pass a task id from list_tasks.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
version | string | Only this version's threads: `current` (the task's current version) or a version's fileId. Comments saved with no version show on every version, so they always come back. |
resolved | enum | Only open threads, or only resolved ones. Omit for both.One of: open, resolved |
sort | enum | created (oldest first, the default) or timecode (timeline order, replies under their thread).One of: created, timecode |
idrequireduuidThe task's UUID - from list_tasks.versionstringOnly this version's threads: `current` (the task's current version) or a version's fileId. Comments saved with no version show on every version, so they always come back.resolvedenumOnly open threads, or only resolved ones. Omit for both.One of: open, resolvedsortenumcreated (oldest first, the default) or timecode (timeline order, replies under their thread).One of: created, timecode{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:tasks
list_tasksList tasksReadList tasks across the workspace's projects, newest first. Filter to one project with projectId. When a person refers to a task by a short code like 'TL-1042', pass it as taskId to resolve it directly instead of scanning pages. Every task in the response carries its own taskId (null when the workspace has task IDs switched off), its customStatus ({id, name} of the workspace custom status it sits in inside its statusGroup, null when none - see list_custom_statuses) and its pricing (template, pricing tag, client price, each editor's pay - null without the finance read scope). Returns {data, nextBefore}; when nextBefore is non-null, call again with before=nextBefore for the next page.
| Name | Type | Details |
|---|---|---|
limit | integer | Items per page, 1-100. Defaults to 50. |
before | string | Opaque pagination cursor - pass the previous response's nextBefore value verbatim. |
projectId | uuid | Restrict results to a single project. |
taskId | string | Exact lookup by the task's permanent handle, e.g. 'TL-1042'. Case-insensitive. This is the human-readable id people quote in email and chat - NOT the uuid. Returns an empty page if the workspace has not enabled task IDs. |
statusGroup | string | One status group or a comma-separated list (OR): toDo, inProgress, supervisorApproval, clientApproval, endClientApproval, approved, posted, inRevision. E.g. 'inProgress,inRevision' for the open editing work. |
assigneeId | string | Only tasks this member (id from list_members) is assigned to - or the literal 'none' for tasks with no assignee. Combine with statusGroup to answer 'what is this editor working on'. |
kind | enum | Only tasks of this kind. A variant's parentTaskId names the task it is a cut of.One of: regular, parent, variant |
approvedFrom | string | YYYY-MM-DD (UTC). Only tasks approved on or after this day. |
approvedTo | string | YYYY-MM-DD (UTC), inclusive. With approvedFrom this answers 'what was delivered in the last 28 days'. |
limitintegerItems per page, 1-100. Defaults to 50.beforestringOpaque pagination cursor - pass the previous response's nextBefore value verbatim.projectIduuidRestrict results to a single project.taskIdstringExact lookup by the task's permanent handle, e.g. 'TL-1042'. Case-insensitive. This is the human-readable id people quote in email and chat - NOT the uuid. Returns an empty page if the workspace has not enabled task IDs.statusGroupstringOne status group or a comma-separated list (OR): toDo, inProgress, supervisorApproval, clientApproval, endClientApproval, approved, posted, inRevision. E.g. 'inProgress,inRevision' for the open editing work.assigneeIdstringOnly tasks this member (id from list_members) is assigned to - or the literal 'none' for tasks with no assignee. Combine with statusGroup to answer 'what is this editor working on'.kindenumOnly tasks of this kind. A variant's parentTaskId names the task it is a cut of.One of: regular, parent, variantapprovedFromstringYYYY-MM-DD (UTC). Only tasks approved on or after this day.approvedTostringYYYY-MM-DD (UTC), inclusive. With approvedFrom this answers 'what was delivered in the last 28 days'.{}Key scope: read:tasks
resolve_task_commentResolve or reopen a task commentWriteMark a task comment's thread resolved (resolved: true) or reopen it (resolved: false) - the check box on the thread in the app, as the API key's creator. Works on a team comment or a client's share-link comment, but only on a thread's FIRST comment (a reply → 400). 403 when the key's user is not allowed to resolve comments. Returns the updated comment.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
commentIdrequired | uuid | The top-level comment to resolve - from list_task_comments (parentId null). |
resolvedrequired | boolean | true resolves the thread, false reopens it. |
idrequireduuidThe task's UUID - from list_tasks.commentIdrequireduuidThe top-level comment to resolve - from list_task_comments (parentId null).resolvedrequiredbooleantrue resolves the thread, false reopens it.{
"id": "00000000-0000-0000-0000-000000000000",
"commentId": "00000000-0000-0000-0000-000000000000",
"resolved": true
}Key scope: write:tasks
set_task_custom_valuesSet task custom-column valuesWriteSet one or more custom-column cell values on a task. Pass values as a map of columnId → value (get the column ids from list_custom_columns). Value by kind: text/date/priority → a string (date = YYYY-MM-DD; priority = critical|high|medium|low); link → a URL string (sets ONLY the URL - the raw files already linked in the cell stay), or { url?, items? } to link raw files: items is the FULL list of linked files/folders the cell should end up with - a full replace, not an add; [] removes them all - each { kind: projectFile | projectFolder | brandFile | brandFolder, id } with ids from list_project_raw_files (this task's own project) or list_brand_raw_files (its project's brand); any other id is a 400 and nothing is written; only the keys you send change; dateRange → { start, end }; select → the `id` of ONE of THAT column's `options` in list_custom_columns, sent as a plain string - never an array (only tag columns take arrays), never the whole option object, never the option's label, never another column's option (a wrong one is refused with the column's valid label = optionId pairs - retry with one of those); tag → an ARRAY of tagIds (multi-value - send the full set you want the cell to end up with, not a delta; a tag scoped to ANOTHER brand than the task's is a 400 - pick shared tags or the task's own brand's tags, creating missing ones with create_custom_column_tags first). Pass null for any column to clear it. Returns the task's full custom values after the write.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The TASK's UUID - from list_tasks, search_tasks or create_task. NOT a project, brand or custom-column id: the column ids go in `values`, never in this field. A wrong entity here is a 404 and nothing is written. |
valuesrequired | object | Map of columnId → value (string | { start, end } | optionId | tagId[] | { url?, items: [{ kind, id }] } | null), keyed by custom-column id. A select value is ONE option's id from that SAME column's options in list_custom_columns, as a plain string (e.g. "<optionId>") - not an array, not the option object, not its label. Only a tag column takes an array. |
idrequireduuidThe TASK's UUID - from list_tasks, search_tasks or create_task. NOT a project, brand or custom-column id: the column ids go in `values`, never in this field. A wrong entity here is a 404 and nothing is written.valuesrequiredobjectMap of columnId → value (string | { start, end } | optionId | tagId[] | { url?, items: [{ kind, id }] } | null), keyed by custom-column id. A select value is ONE option's id from that SAME column's options in list_custom_columns, as a plain string (e.g. "<optionId>") - not an array, not the option object, not its label. Only a tag column takes an array.{
"id": "00000000-0000-0000-0000-000000000000",
"values": {}
}Key scope: write:tasks
update_taskUpdate taskWriteUpdate a task's allow-listed fields - provide at least one. Changing statusGroup fires the same notification flow the UI uses (in-app, email, WhatsApp where configured). Move a task into one of the workspace's CUSTOM statuses with customStatusId (ids from list_custom_statuses; statusGroup follows its base - omit statusGroup or send that base), or pass customStatusId null to put it back in the plain base; a move inside one base notifies no one. Assign team members with assigneeIds (member ids from list_members) - this is a FULL REPLACE of the task's assignees: pass the complete list you want, [] to clear all, and to add one person read the task's current assigneeIds (from get_task) and send that list plus the new id. Newly-added members are notified the same way the UI does; guests cannot be assigned. Date fields use YYYY-MM-DD; pass null to clear them. Price the task with pricingTemplateId and say which template role each assignee fills with assigneeRoles - e.g. assigneeIds [nimat] + assigneeRoles [{memberId: nimat, roleLabel: 'Online Editor'}] + pricingTemplateId assigns, tags and prices in one call; their pay then shows in get_pricing_payables. Changing assigneeIds on a priced task recomputes its pay, as in the app. Refused (409) once money has moved on the task.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID. |
title | string | |
statusGroup | enum | One of: toDo, inProgress, supervisorApproval, clientApproval, endClientApproval, approved, posted, inRevision |
customStatusId | uuid | null | A custom status id from list_custom_statuses to move the task into (it must be used on the task's brand; statusGroup becomes its base), or null to take the task out of its custom status. A variant follows its parent - set it on the parent. |
internalDeadline | string | null | YYYY-MM-DD or null to clear. |
externalDeadline | string | null | YYYY-MM-DD or null to clear. |
assigneeIds | uuid[] | FULL REPLACE of the assigned team members (Task editors) - member ids from list_members. Send the complete desired list; [] clears all. To add one person, send the current assigneeIds (from get_task) plus the new id. Guests cannot be assigned. |
subFolderId | uuid | null | Move the task into a sub-folder (task group) of its project, or null to move it back to the project root. Sub-folder ids come from get_project's subFolders. |
startDate | string | null | Workload work-window start, YYYY-MM-DD or null to clear. |
estimatedMinutes | integer | null | Total time estimate in minutes. Null clears the estimate AND any per-assignee breakdown. |
assigneeEstimates | object[] | FULL REPLACE of the per-assignee estimate breakdown (minutes each). Members must be assignees of the task; [] clears the breakdown (keeping any total). When present, the breakdown is authoritative and the task total becomes its sum. |
externalMetadata | object | null | Arbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear. |
pricingTemplateId | uuid | Pricing template to apply (id from list_pricing_templates) - sets the task's pricing tag, its default client price and the editors' default pay, exactly as picking the template in the app's pricing dialog. Needs the finance write scope. |
assigneeRoles | object[] | Which pricing-template role each assignee fills, so only they draw that role's pay - e.g. [{memberId: <online editor>, roleLabel: 'Online Editor'}]. Send with assigneeIds to assign and tag in one call. Uses the task's template, or pricingTemplateId when sent together. A custom pay amount is not supported: pay comes from the template. Needs the finance write scope. |
idrequireduuidThe task's UUID.titlestringstatusGroupenumOne of: toDo, inProgress, supervisorApproval, clientApproval, endClientApproval, approved, posted, inRevisioncustomStatusIduuid | nullA custom status id from list_custom_statuses to move the task into (it must be used on the task's brand; statusGroup becomes its base), or null to take the task out of its custom status. A variant follows its parent - set it on the parent.internalDeadlinestring | nullYYYY-MM-DD or null to clear.externalDeadlinestring | nullYYYY-MM-DD or null to clear.assigneeIdsuuid[]FULL REPLACE of the assigned team members (Task editors) - member ids from list_members. Send the complete desired list; [] clears all. To add one person, send the current assigneeIds (from get_task) plus the new id. Guests cannot be assigned.subFolderIduuid | nullMove the task into a sub-folder (task group) of its project, or null to move it back to the project root. Sub-folder ids come from get_project's subFolders.startDatestring | nullWorkload work-window start, YYYY-MM-DD or null to clear.estimatedMinutesinteger | nullTotal time estimate in minutes. Null clears the estimate AND any per-assignee breakdown.assigneeEstimatesobject[]FULL REPLACE of the per-assignee estimate breakdown (minutes each). Members must be assignees of the task; [] clears the breakdown (keeping any total). When present, the breakdown is authoritative and the task total becomes its sum.externalMetadataobject | nullArbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear.pricingTemplateIduuidPricing template to apply (id from list_pricing_templates) - sets the task's pricing tag, its default client price and the editors' default pay, exactly as picking the template in the app's pricing dialog. Needs the finance write scope.assigneeRolesobject[]Which pricing-template role each assignee fills, so only they draw that role's pay - e.g. [{memberId: <online editor>, roleLabel: 'Online Editor'}]. Send with assigneeIds to assign and tag in one call. Uses the task's template, or pricingTemplateId when sent together. A custom pay amount is not supported: pay comes from the template. Needs the finance write scope.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:tasks
update_task_docWrite a task's document body / briefWriteRewrite the rich-text content of a task - the document body of a doc task, or the brief/description of a media task. contentHtml is a FULL REPLACE of the existing content (send the complete HTML you want, not a fragment; simple tags like <h2>, <p>, <ul>/<li>, <strong> render as in the in-app editor). To change only part of it - add a paragraph, append to the end, replace or extend one section, fix one passage - use edit_task_doc instead: it sends just the change and is not blocked by someone editing another part of the document in the app. To put an image or a file in the content, call upload_doc_attachment_from_url first and paste the embedHtml it returns into contentHtml. For doc tasks a restore point is saved to the document's version history on every write, so edits are revertible in the app. Pass baseRev (the rev from get_task_doc) for a safe write: a 409 means the app copy changed since you read it - re-read with get_task_doc and merge before retrying. Every successful write returns the new rev; use that as baseRev for your next write to the same task. Omit baseRev to force-overwrite. Before writing, confirm WITH THE USER which task (and, for a new deliverable, which brand/project) the content belongs to, and whether to create a new task or update an existing one - don't guess placement.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
contentHtmlrequired | string | The full new HTML content. Empty string clears it. |
baseRev | integer | null | The rev value from get_task_doc, for a conflict-safe write. Omit or null to force-overwrite. |
idrequireduuidThe task's UUID - from list_tasks.contentHtmlrequiredstringThe full new HTML content. Empty string clears it.baseRevinteger | nullThe rev value from get_task_doc, for a conflict-safe write. Omit or null to force-overwrite.{
"id": "9b3e7a41-5c2d-4f8e-a6b0-1d7c3e9f2a64",
"contentHtml": "<p>Hook: the first 2 seconds show the product.</p>",
"baseRev": 12
}Key scope: write:tasks
upload_doc_attachment_from_urlUpload an image or file into a documentWritePut an IMAGE or a FILE inside a document - an HQ item's Document tab, a doc task's body, or a task brief. Give us a publicly reachable http(s) URL; we download it server-side and return embedHtml, a ready-made snippet to paste into the document HTML. Two steps: (1) call this tool, (2) include the returned embedHtml inside the contentHtml you send to write_hq_item_doc or update_task_doc - those tools REPLACE the whole body, so keep the rest of the existing HTML around it. Images render inline, videos as an inline player, audio as a chip with a play button, everything else as a download chip. Allowed: JPEG/PNG/GIF/WebP/TIFF/BMP/HEIC (max 10 MB), MP4/WebM/MOV/OGG (max 95 MB), audio such as sound effects - MP3/WAV/AIFF/AAC/M4A/FLAC/OGG (max 50 MB), PDF/Word/PowerPoint/Excel (max 25 MB), plain text (max 1 MB), DaVinci Resolve project files and XML timelines .xml/.fcpxml (max 25 MB, download only). The URL must be reachable without authentication; private, internal and localhost addresses are refused. Only a URL works - bytes from your own machine have to go through the app. This is NOT a task deliverable and NOT a project file (use upload_file_from_url for those), and NOT the HQ item's Files tab; it is document content only.
| Name | Type | Details |
|---|---|---|
urlrequired | url | Public http(s) URL of the image or file to pull in. No auth required. |
fileName | string | Override the name we infer from the URL (include the extension). |
urlrequiredurlPublic http(s) URL of the image or file to pull in. No auth required.fileNamestringOverride the name we infer from the URL (include the extension).{
"url": "https://example.com/file.mp4"
}Key scope: write:tasks
add_brand_memberAdd a team member to a brandWriteGrant an EXISTING workspace team member access to a brand. memberId is a member id from list_members; role defaults to editor (admin/supervisor/editor). The grant cascades to the brand's projects. Guests are NOT added here - they go through the invite flow. Returns the brand id, the granted role, and the member. 409 if they already have access.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand's (Node) UUID - from list_brands. |
memberIdrequired | uuid | The workspace member's id (from list_members) to grant access. |
role | enum | Brand-level team role. Defaults to editor.One of: admin, supervisor, editor |
idrequireduuidThe brand's (Node) UUID - from list_brands.memberIdrequireduuidThe workspace member's id (from list_members) to grant access.roleenumBrand-level team role. Defaults to editor.One of: admin, supervisor, editor{
"id": "00000000-0000-0000-0000-000000000000",
"memberId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:projects
create_brandCreate brandWriteCreate a top-level brand or client in the workspace (type defaults to brand). socialLinks are free-form named links - social platforms or anything else (Website, Drive, ...). Returns the full brand detail.
| Name | Type | Details |
|---|---|---|
namerequired | string | |
type | enum | Defaults to brand.One of: brand, client |
description | string | null | |
language | string | null | BCP-47 language code for the brand. |
picture | string | null | Logo / avatar URL, if you host one externally. |
socialLinks | object[] | |
externalMetadata | object | null | Arbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear. |
namerequiredstringtypeenumDefaults to brand.One of: brand, clientdescriptionstring | nulllanguagestring | nullBCP-47 language code for the brand.picturestring | nullLogo / avatar URL, if you host one externally.socialLinksobject[]externalMetadataobject | nullArbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear.{
"name": "example"
}Key scope: write:projects
create_projectCreate projectWriteCreate a project under a brand or folder Node (nodeId - find it via list_brands or get_project). Optionally pre-create up to 20 top-level folders by name; folder failures do not roll back the project. Returns the project with its created folders.
| Name | Type | Details |
|---|---|---|
namerequired | string | |
nodeIdrequired | uuid | UUID of the parent brand or folder Node in the workspace. |
description | string | null | |
picture | string | null | URL of a project image, if you maintain one externally. |
folders | string[] | Optional starter folder names to create inside the project. |
externalMetadata | object | null | Arbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear. |
namerequiredstringnodeIdrequireduuidUUID of the parent brand or folder Node in the workspace.descriptionstring | nullpicturestring | nullURL of a project image, if you maintain one externally.foldersstring[]Optional starter folder names to create inside the project.externalMetadataobject | nullArbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear.{
"name": "example",
"nodeId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:projects
delete_projectDelete projectWriteDeletesDESTRUCTIVE. Move a project to Trash (soft-delete) and CASCADE: every active task in the project is trashed with it, and all of them - plus their files - disappear from the client's view immediately. Restorable from the app's Trash page, but a restore is a manual, workspace-wide operation, so treat this as irreversible when planning. CONFIRM WITH THE USER FOR EACH PROJECT, naming it; NEVER loop this tool over a list of projects. Idempotent; no notifications fire. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The project's UUID. |
idrequireduuidThe project's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:projects
get_brandGet brandReadGet a brand's full detail: identity fields, socialLinks, externalMetadata, connected socialAccounts (the brand's social channels - platform, handle, displayName, avatar, follower count, status; credentials excluded), the effective intakeForm (brand brief), and shareLink - the brand's working client-portal share link, or null (create one with create_brand_share_link).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand's (Node) UUID. |
idrequireduuidThe brand's (Node) UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:projects
get_brand_headerGet a brand's header contentReadRead a brand's header in one call: guidelines/brief, docs, links, attached docs, the raw brand files (bounded), and the header card list (the canvas). Pass the brand id from list_brands.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand's (Node) UUID - from list_brands. |
idrequireduuidThe brand's (Node) UUID - from list_brands.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:projects
get_brand_voiceGet brand voice profileReadRead a brand's writing-style profile: freeform voice instructions (how this brand titles, tags, and captions its content) plus optional per-platform notes (e.g. YouTube title conventions vs Instagram caption register). ALWAYS read this before composing captions, titles, or descriptions for the brand and follow it. Empty instructions with updatedAt:null = no profile saved yet - compose from the brand's published posts instead (Timeliner's own caption AI also mines those) and consider offering to save the user's preferences via update_brand_voice. The same profile is injected into Timeliner's own caption generation (generate_post_caption and the in-app composer).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand's UUID (from list_brands). |
idrequireduuidThe brand's UUID (from list_brands).{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:projects
get_projectGet projectReadGet one project plus its active folders, sorted by name.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The project's UUID. |
idrequireduuidThe project's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:projects
get_project_headerGet a project's header contentReadRead a project's header in one call: the brief, every doc (guidelines / moodboard), links, attached docs, the raw files (Assets, bounded), and the header card list (the canvas - which sections exist). Pass the project id from list_projects.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The project's UUID - from list_projects. |
idrequireduuidThe project's UUID - from list_projects.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:projects
list_brand_membersList a brand's membersReadWho currently has access to a brand, with each member's brand-level role. Use this before add_brand_member to see who is already on the brand - adding someone who already has access returns 409, which tells you nothing about anyone else. Each entry has `automatic: true` when the access comes from the member being a workspace ADMIN (admins get every brand automatically) rather than from an explicit assignment. Team members only by default.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand's (Node) UUID - from list_brands. |
includeGuests | boolean | Include external guest reviewers. Defaults to false. |
includeDeactivated | boolean | Include deactivated / removed members. Defaults to false. |
idrequireduuidThe brand's (Node) UUID - from list_brands.includeGuestsbooleanInclude external guest reviewers. Defaults to false.includeDeactivatedbooleanInclude deactivated / removed members. Defaults to false.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:projects
list_brand_raw_filesList a brand's raw filesReadList a brand header's RAW files (the brand's Raw files & Assets) one folder level at a time. Each file: id, name, size (bytes), mimeType, folderId, durationSeconds (null when not media or not known yet), createdAt, hasTranscript (a ready transcript exists) and transcriptStatus (none | pending | extracting | processing | ready | no_speech | failed). The level's subfolders come on the first page (folders[]: id, name, parentId) - pass a folder's id as folderId to open it. Newest file first; when nextBefore is not null, call again with before = nextBefore for the next page. A task can link a brand file only when its project sits directly under this brand: a link column value { items: [{ kind: "brandFile", id }] } (a folder: kind "brandFolder").
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand (client) node's UUID (from list_brands) - never a project id. |
folderId | uuid | A folder to open (folders[].id from a previous call). Omit for the top level. |
limit | integer | Files per page, 1–200 (default 50). |
before | uuid | The nextBefore of the previous page - omit for the first page. |
idrequireduuidThe brand (client) node's UUID (from list_brands) - never a project id.folderIduuidA folder to open (folders[].id from a previous call). Omit for the top level.limitintegerFiles per page, 1–200 (default 50).beforeuuidThe nextBefore of the previous page - omit for the first page.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:projects
list_brandsList brands and clientsReadList the workspace's brands and clients (Nodes of type brand or client), newest first. Use type to restrict to one kind. Brand ids are the nodeId values create_project expects. Each brand includes its connected socialAccounts (the social channels - platform, handle, displayName, avatar, status - with credentials excluded), so you can answer which channels are connected to which brand in one call. Returns {data, nextBefore}; when nextBefore is non-null, call again with before=nextBefore for the next page.
| Name | Type | Details |
|---|---|---|
limit | integer | Items per page, 1-100. Defaults to 50. |
before | string | Opaque pagination cursor - pass the previous response's nextBefore value verbatim. |
type | enum | Omit to return both brands and clients.One of: brand, client |
limitintegerItems per page, 1-100. Defaults to 50.beforestringOpaque pagination cursor - pass the previous response's nextBefore value verbatim.typeenumOmit to return both brands and clients.One of: brand, client{}Key scope: read:projects
list_project_raw_filesList a project's raw filesReadList a project's RAW files (Raw files & Assets) one folder level at a time - the footage of a shoot, never its task files (deliverables, versions, project-file backups are not listed). Each file: id, name, size (bytes), mimeType, folderId, durationSeconds (null when not media or not known yet), createdAt, hasTranscript (a ready transcript exists) and transcriptStatus (none | pending | extracting | processing | ready | no_speech | failed). The level's subfolders come on the first page (folders[]: id, name, parentId) - pass a folder's id as folderId to open it. Newest file first; when nextBefore is not null, call again with before = nextBefore for the next page. Link a file to a task with set_task_custom_values: a link column value { items: [{ kind: "projectFile", id }] } (a folder: kind "projectFolder"). Read what a video says with get_file_transcript.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The project's UUID (from list_projects). |
folderId | uuid | A folder to open (folders[].id from a previous call). Omit for the top level. |
limit | integer | Files per page, 1–200 (default 50). |
before | uuid | The nextBefore of the previous page - omit for the first page. |
idrequireduuidThe project's UUID (from list_projects).folderIduuidA folder to open (folders[].id from a previous call). Omit for the top level.limitintegerFiles per page, 1–200 (default 50).beforeuuidThe nextBefore of the previous page - omit for the first page.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:projects
list_projectsList projectsReadList the workspace's projects, newest first. Returns {data, nextBefore}; when nextBefore is non-null, call again with before=nextBefore for the next page.
| Name | Type | Details |
|---|---|---|
limit | integer | Items per page, 1-100. Defaults to 50. |
before | string | Opaque pagination cursor - pass the previous response's nextBefore value verbatim. |
limitintegerItems per page, 1-100. Defaults to 50.beforestringOpaque pagination cursor - pass the previous response's nextBefore value verbatim.{}Key scope: read:projects
remove_brand_memberRemove a team member from a brandWriteDeletesRevoke a team member's access to a brand - the undo for add_brand_member. Scoped to the brand: access granted directly on one of its projects is left alone. Workspace ADMINS cannot be removed from a single brand (409 - they hold every brand automatically; change their workspace role instead), and guests must be removed in the Timeliner app (400 - revoking a guest also rotates their share link). 404 if that member has no access to the brand. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand's (Node) UUID - from list_brands. |
memberIdrequired | uuid | The member to revoke - from list_brand_members or list_members. |
idrequireduuidThe brand's (Node) UUID - from list_brands.memberIdrequireduuidThe member to revoke - from list_brand_members or list_members.{
"id": "00000000-0000-0000-0000-000000000000",
"memberId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:projects
update_brandUpdate brand metadataWriteUpdate a brand's writable fields - provide at least one. description is the brand's brief / guidelines text (a brand header doc card that still shows the description - usually Guidelines - follows it until that card is written on its own with update_header_card) - a full text replace; pass "" to clear it, null to leave it untouched. externalMetadata is a FULL-OBJECT REPLACE (read the current value with get_brand and merge client-side before sending; null clears it). socialLinks is the brand's Brand Links card - any label, not just social (Website, Drive, instagram) - and is likewise a FULL-ARRAY REPLACE: read the current list with get_brand, add to it, and send the WHOLE array, or you will wipe the links you left out. [] or null clears them all. Writing description notifies no one. adminApprovalMode is this brand's Admin Approval override. On the Agency + 2 approvers workflow "on" forces the stage, "off" removes it, null inherits the workspace setting; on the Agency, Agency + End Client and Internal workflows "on" ADDS the stage after Supervisor Approval for this brand alone, and null/"off" leave it out. Turning it OFF does NOT move tasks already sitting in Admin Approval; they stay in a stage the brand no longer shows, so prefer the in-app Edit Brand → Workflow flow, which moves them back to Supervisor Approval first. guestAllStagesVisible is this brand's "Show clients every workflow stage" override - true lets the brand's clients (in-app and on its share links) see tasks in every stage, to-do and in-progress included, as VIEW-ONLY cards (stage, deadlines, planned posting time, a thumbnail if any; early-stage media is not playable, commentable or approvable until it reaches client review - except for a guest the task reaches through an assignment, who keeps its pre-setting rights); false hides them; null inherits the workspace setting. guestEarlyStageDocAccess decides how far a DOCUMENT in such a stage goes: placeholder (default) | view | comment | edit; null inherits. Widening either is plan-gated (403 - relay the message verbatim). Suggest these when a client asks to see their whole pipeline or planned work.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand's (Node) UUID. |
description | string | null | The brand's brief / guidelines text. "" clears it; omit or null to leave it unchanged. |
socialLinks | object[] | null | The brand's COMPLETE new link list (replaces the existing one - merge first), or null/[] to clear. |
externalMetadata | object | null | The brand's complete new externalMetadata object, or null to clear. |
adminApprovalMode | enum | null | Per-brand Admin Approval override: "on" | "off" | null (inherit the workspace's adminApprovalScope on Agency + 2 approvers; no stage on Agency / Agency + End Client / Internal). Omit to leave unchanged.One of: on, off |
guestAllStagesVisible | boolean | null | Per-brand "Show clients every workflow stage" override: true (clients see every stage, view-only) | false (client stages only) | null (inherit the workspace setting). Omit to leave unchanged. Enabling is plan-gated. |
guestEarlyStageDocAccess | enum | null | Per-brand early-stage DOCUMENT access while every stage is visible: placeholder | view | comment | edit | null (inherit). Media is a placeholder; a guest the task reaches through an assignment keeps its pre-setting rights. Omit to leave unchanged.One of: placeholder, view, comment, edit |
idrequireduuidThe brand's (Node) UUID.descriptionstring | nullThe brand's brief / guidelines text. "" clears it; omit or null to leave it unchanged.socialLinksobject[] | nullThe brand's COMPLETE new link list (replaces the existing one - merge first), or null/[] to clear.externalMetadataobject | nullThe brand's complete new externalMetadata object, or null to clear.adminApprovalModeenum | nullPer-brand Admin Approval override: "on" | "off" | null (inherit the workspace's adminApprovalScope on Agency + 2 approvers; no stage on Agency / Agency + End Client / Internal). Omit to leave unchanged.One of: on, offguestAllStagesVisibleboolean | nullPer-brand "Show clients every workflow stage" override: true (clients see every stage, view-only) | false (client stages only) | null (inherit the workspace setting). Omit to leave unchanged. Enabling is plan-gated.guestEarlyStageDocAccessenum | nullPer-brand early-stage DOCUMENT access while every stage is visible: placeholder | view | comment | edit | null (inherit). Media is a placeholder; a guest the task reaches through an assignment keeps its pre-setting rights. Omit to leave unchanged.One of: placeholder, view, comment, edit{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:projects
update_brand_voiceUpdate brand voice profileWriteSave or refine a brand's writing-style profile - the durable memory every caption/title generation for the brand obeys (yours via update_post, Timeliner's via generate_post_caption, and the team's in-app composer). Use it when the user teaches you their style ('my YouTube titles are always under 60 chars, curiosity-gap, no clickbait words') so the preference persists beyond this conversation. instructions REPLACES the whole freeform text (read get_brand_voice first and merge - don't drop what's there; ≤4000 chars; "" clears). platformNotes merges per key: string sets that platform's note (≤1000 chars), null (or "") deletes it, omitted platforms are untouched. Write plain prose rules, not JSON. Confirm with the user before overwriting a non-empty existing profile.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The brand's UUID (from list_brands). |
instructions | string | Full replacement freeform voice/style instructions ("" clears). |
platformNotes | object | Per-platform style notes, merged key-by-key: string sets, null deletes, omitted untouched. |
idrequireduuidThe brand's UUID (from list_brands).instructionsstringFull replacement freeform voice/style instructions ("" clears).platformNotesobjectPer-platform style notes, merged key-by-key: string sets, null deletes, omitted untouched.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:projects
update_header_cardWrite a header card's contentWriteWrite the content of ONE card on a brand's or project's header canvas. Get the card id from get_project_header / get_brand_header (`cards[].id`) - one endpoint serves both scopes. Two card types are writable: a `doc` card (send html - a FULL REPLACE of its rich text; simple tags like <h2>, <p>, <ul>/<li>, <strong> render as in the in-app editor; "" clears it) and a `links` card (send links - a FULL REPLACE of the whole list, so include the entries you want to keep; [] clears it). Sending the wrong field for the card's type is a 400. assets/timeline/intake cards render live from other data and fileAttach needs a real upload, so none of them are writable. A PROJECT's Brief card is not writable here (409) - it's a live view of the project's description: use update_project's description instead. Any other doc card, including a brand card still showing the brand description, is written on its own: that one card gets its own text and stops following the description. Silent - no one is notified.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The header card's UUID - from get_project_header / get_brand_header (`cards[].id`). |
html | string | DOC cards only: the full new HTML body. "" clears it. |
links | object[] | LINKS cards only: the full new list (full replace). [] clears it. |
idrequireduuidThe header card's UUID - from get_project_header / get_brand_header (`cards[].id`).htmlstringDOC cards only: the full new HTML body. "" clears it.linksobject[]LINKS cards only: the full new list (full replace). [] clears it.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:projects
update_projectUpdate projectWriteUpdate a project's allow-listed fields - name, description, externalMetadata. Send at least one of them - a call with only the id (or only argument names this tool does not define, which are dropped) is refused before it runs and saves nothing. Returns the updated project with its active folders.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The project's UUID. |
name | string | |
description | string | null | Pass null to clear the description. |
externalMetadata | object | null | Arbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear. |
idrequireduuidThe project's UUID.namestringdescriptionstring | nullPass null to clear the description.externalMetadataobject | nullArbitrary JSON object for integration data; namespace by integration, e.g. {"crm":{"accountId":"A-123"}}. Pass null to clear.{
"id": "00000000-0000-0000-0000-000000000000",
"name": "example"
}Key scope: write:projects
abort_file_uploadAbort a file uploadWriteDeletesDiscard an in-progress upload: the parts already stored are freed and the file record is marked failed. Use it when the part PUTs cannot be made at all - for example your network blocks *.r2.cloudflarestorage.com - so a half-started upload does not linger against the storage plan. Refused (409) once the upload has completed - trash the file instead. Safe to repeat and safe to call after a failed upload: if nothing is in progress under that id it simply succeeds (204).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The upload id returned by start_file_upload (also the File id). |
idrequireduuidThe upload id returned by start_file_upload (also the File id).{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:files
cleanup_storageClean up storageWriteDeletesMove storage into Trash to free space - NOTHING is deleted permanently (items stay in Trash 30 days and are restorable there). Two modes, exactly one per call: `rule` runs ONE batch (≤500 items) of a cleanup rule and returns remainingCount (repeat until 0); `selection` trashes explicit file/brandFile/task ids from list_storage_items (a task's CURRENT file can only be reclaimed by trashing the whole task via taskIds). Set dryRun=true first to preview counts and freed bytes without changing anything. DESTRUCTIVE: everything it moves leaves the client's view at once. Always dry-run first and show the user the counts before running for real. Requires a write-scope key.
| Name | Type | Details |
|---|---|---|
selection | object | Explicit ids to trash. Provide either this or `rule`. |
rule | object | Cleanup rule to run one batch of. Provide either this or `selection`. |
dryRun | boolean | Preview only - validate and count, mutate nothing. |
selectionobjectExplicit ids to trash. Provide either this or `rule`.ruleobjectCleanup rule to run one batch of. Provide either this or `selection`.dryRunbooleanPreview only - validate and count, mutate nothing.{}Key scope: write:files
complete_file_uploadComplete a file uploadWriteFinish a multipart upload once every part has been PUT. Pass the {partNumber, etag} pairs from the PUT responses. taskId attaches the file as that task's new version (fires version automations, notifies reviewers, lands in the Activity Log) - a regular task or a variant, never a kind=parent task, which holds no file of its own; folderId files a project upload into a folder (never together with taskId). Returns the same UploadedFile shape as upload_file_from_url. Safe to retry: an already-completed upload returns 200 with the same result.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The upload id returned by start_file_upload (also the File id). |
partsrequired | object[] | Every uploaded part, in any order. |
taskId | uuid | Attach as this task's new version. The TASK's own id (from list_tasks or create_task) - never a project, brand or folder id. A regular task or a variant - never a task with kind=parent, which holds no file (use one of its variants: list_tasks kind=variant, matching parentTaskId). Never an HQ item id (attach a file to an HQ item with upload_hq_item_file_from_url) or a file id. |
folderId | uuid | Project-file folder (omit with taskId). |
idrequireduuidThe upload id returned by start_file_upload (also the File id).partsrequiredobject[]Every uploaded part, in any order.taskIduuidAttach as this task's new version. The TASK's own id (from list_tasks or create_task) - never a project, brand or folder id. A regular task or a variant - never a task with kind=parent, which holds no file (use one of its variants: list_tasks kind=variant, matching parentTaskId). Never an HQ item id (attach a file to an HQ item with upload_hq_item_file_from_url) or a file id.folderIduuidProject-file folder (omit with taskId).{
"id": "00000000-0000-0000-0000-000000000000",
"parts": [
{
"partNumber": 1,
"etag": "example"
}
]
}Key scope: write:files
get_file_download_urlGet file download URLReadGet a short-lived (about 1 hour) presigned download URL for a file. type picks the rendition: original (default), preview, thumbnail, or variant (an MP4 of a transcoded height - height is REQUIRED with type=variant and must be one of the file's media.availableHeights, e.g. 480, 1080, 2160). A variant's MP4 is made on first request: the call then returns status 'generating' with retryAfterSeconds - wait that long and call again until it returns a url. Set downloadName to force a browser download under that filename.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The file's UUID. |
type | enum | One of: original, preview, thumbnail, variant |
height | integer | Required when type=variant: the variant height in pixels. |
downloadName | string |
idrequireduuidThe file's UUID.typeenumOne of: original, preview, thumbnail, variantheightintegerRequired when type=variant: the variant height in pixels.downloadNamestring{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:files
get_file_transcriptRead a raw file's transcriptReadRead the transcript of a RAW file (a project or brand raw file id from list_project_raw_files / list_brand_raw_files) as plain text, with the same statuses as get_task_transcript: ready (text is set), none (never transcribed - start one with start_file_transcription), pending / extracting / processing (being made - call again in ~20s), no_speech, failed. Never starts a transcription. Use it to understand what each video says before writing briefs or tagging.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The raw file's UUID - files[].id from list_project_raw_files or list_brand_raw_files. |
idrequireduuidThe raw file's UUID - files[].id from list_project_raw_files or list_brand_raw_files.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:files
get_storage_cleanup_suggestionsGet storage cleanup suggestionsReadRule-based cleanup picks with reclaimable byte totals: oldVersions (superseded versions of delivered tasks), approvedOldTasks, archivedTaskMedia, oldProjectRawFiles, largeStaleFiles. The synthetic `_total` row is the DISTINCT union across rules - quote THAT as the reclaimable figure (rules overlap; summing rows double-counts). Feed a ruleKey into cleanup_storage to act on one.
| Name | Type | Details |
|---|---|---|
keepVersions | integer | oldVersions rule: superseded versions to keep per task (default 0). |
ageMonths | integer | Age cutoff (default 6). |
largeBytes | integer | largeStaleFiles threshold in bytes (default 1000000000 = 1 GB). |
keepVersionsintegeroldVersions rule: superseded versions to keep per task (default 0).ageMonthsintegerAge cutoff (default 6).largeBytesintegerlargeStaleFiles threshold in bytes (default 1000000000 = 1 GB).{}Key scope: read:files
get_storage_overviewGet storage overviewReadRead the workspace's storage picture: bytes used vs the plan's capacity plus the per-brand / per-project byte breakdown split by category - deliverableCurrent (tasks' current files), versionOld (superseded task versions, usually the biggest reclaimable chunk), raw (project raw files & assets), headerAttachment, brandFile, commentAttachment, and trashed (already in Trash, purged 30 days after trashing). Matches the in-app Storage page's figures.
No inputs.
{}Key scope: read:files
list_storage_itemsList storage itemsReadList stored files (project + brand) with size, media metadata (duration, fps, width/height), storage category, and task lifecycle context (status, approvedAt/postedAt/archivedAt). Filter by brandId, projectId, category (comma-separated), mimeClass (video|image|audio|doc|other), size and date cutoffs; sort defaults to size_desc (heaviest first). Keyset-paginated via the opaque `cursor`; totalCount/totalBytes describe the FULL filtered set (first page only). Item ids feed cleanup_storage's explicit selection.
| Name | Type | Details |
|---|---|---|
brandId | uuid | Restrict to one brand. |
projectId | uuid | Restrict to one project. |
category | string | Comma-separated categories: deliverableCurrent, versionOld, raw, headerAttachment, brandFile. |
mimeClass | enum | One of: video, image, audio, doc, other |
search | string | Case-insensitive file-name match. |
minSize | integer | Minimum total bytes (file + proxies). |
createdBefore | string | ISO date - file created before this. |
approvedBefore | string | ISO date - task approved before this. |
postedBefore | string | ISO date - task posted before this. |
archivedBefore | string | ISO date - task archived before this. |
taskStatus | string | Comma-separated task statusGroup values. |
sort | string | size_desc (default), size_asc, created_desc/asc, name_asc/desc, duration_desc/asc, approved/posted/archived_desc/asc, brand/project_asc/desc. |
cursor | string | Opaque nextCursor from the previous page. |
limit | integer | Page size (default 100, max 200). |
brandIduuidRestrict to one brand.projectIduuidRestrict to one project.categorystringComma-separated categories: deliverableCurrent, versionOld, raw, headerAttachment, brandFile.mimeClassenumOne of: video, image, audio, doc, othersearchstringCase-insensitive file-name match.minSizeintegerMinimum total bytes (file + proxies).createdBeforestringISO date - file created before this.approvedBeforestringISO date - task approved before this.postedBeforestringISO date - task posted before this.archivedBeforestringISO date - task archived before this.taskStatusstringComma-separated task statusGroup values.sortstringsize_desc (default), size_asc, created_desc/asc, name_asc/desc, duration_desc/asc, approved/posted/archived_desc/asc, brand/project_asc/desc.cursorstringOpaque nextCursor from the previous page.limitintegerPage size (default 100, max 200).{}Key scope: read:files
move_fileMove a raw fileWriteMove an existing RAW file (Raw files & assets) to another place: into a project (projectId), into a brand header's Raw files & assets (brandId), or only into a folder of the project or brand it is already in (folderId alone; null = its top level). Add folderId with projectId / brandId to land it in a folder there. The SAME file moves - same id, its comments, its transcript and its share link come along; nothing is downloaded, re-uploaded or copied, and storage is not counted twice. Get the file id from get_project_header or get_brand_header (files[].id). Task files - a deliverable or version (media.fileId from get_task), a task's project-file backup - are refused (409 task_file): download and re-upload those. A file used as a post cover, a published post's file, or in a link column / task hand-off moves only between projects or between brands, never across (409 file_in_use). A name already used at the destination gets a " (1)" suffix (renamed: true). The file shows in the app through the destination header's Raw files & Assets card: when that header has none (a brand starts without one) the move adds it (rawFilesCard.added: true), and a card an admin hid is shown again (rawFilesCard.restored: true) - tell the user when either happened. Who can see the file follows the destination (a brand's guests see its brand files). Silent - no one is notified. Move files one at a time, in sequence - never in parallel.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The raw file's UUID - files[].id from get_project_header or get_brand_header. |
projectId | uuid | Move it into this project's Raw files & assets. Never together with brandId. |
brandId | uuid | Move it into this brand header's Raw files & assets (a brand id from list_brands). |
folderId | uuid | null | A folder of the destination (or, alone, of the file's current project / brand). null or omitted with projectId / brandId = the top level. |
idrequireduuidThe raw file's UUID - files[].id from get_project_header or get_brand_header.projectIduuidMove it into this project's Raw files & assets. Never together with brandId.brandIduuidMove it into this brand header's Raw files & assets (a brand id from list_brands).folderIduuid | nullA folder of the destination (or, alone, of the file's current project / brand). null or omitted with projectId / brandId = the top level.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:files
refresh_upload_part_urlsRefresh upload part URLsWriteRe-presign PUT URLs for parts that have not been sent yet (up to 100 per call). Part URLs expire 6 hours after start_file_upload; a large file on a slow link needs fresh ones. The fresh URLs point at the same Cloudflare R2 host (*.r2.cloudflarestorage.com), not at timeliner.io - re-presigning does not help a caller whose network blocks that host.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The upload id returned by start_file_upload (also the File id). |
partNumbersrequired | integer[] |
idrequireduuidThe upload id returned by start_file_upload (also the File id).partNumbersrequiredinteger[]{
"id": "00000000-0000-0000-0000-000000000000",
"partNumbers": [
1
]
}Key scope: write:files
start_file_transcriptionStart a raw file's transcriptionWriteStart the transcription of a RAW video or audio file, under the same plan and credit rules as the app's Transcribe button (it spends the workspace's transcription minutes). Answers status pending / extracting with started: true; a file that already has a transcript (or one being made) starts nothing and answers its status with started: false - so calling it twice is safe. Then poll get_file_transcript every ~20s until status is ready. Only start the files you actually need to read, one at a time.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The raw file's UUID - files[].id from list_project_raw_files or list_brand_raw_files. |
idrequireduuidThe raw file's UUID - files[].id from list_project_raw_files or list_brand_raw_files.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:files
start_file_uploadStart a file uploadWriteBegin a multipart upload for a file the agent can read LOCALLY (a render on this machine, a file in a watched folder) - the counterpart to upload_file_from_url for bytes that are not on a public URL. Pass projectId for a raw project file or taskId for a task's new version (the project is derived). taskId must be a regular task or a variant, never a parent: a task whose kind is "parent" (get_task and list_tasks return kind) is a container with no file of its own - each of its variants carries its own. To upload to one, call list_tasks with that projectId and kind=variant and use a variant whose parentTaskId is the parent's id (none yet → create_task with kind=variant and that parentTaskId). A parent's id is refused with 400. Returns presigned PUT URLs (valid 6h): PUT each part's bytes to its url (every part except the last must be at least 5 MiB; use recommendedPartBytes), record the ETag response header of each PUT, then call complete_file_upload with those {partNumber, etag} pairs. NETWORK REQUIREMENT: those part URLs point at Cloudflare R2 (https://<bucket>.<account>.r2.cloudflarestorage.com), NOT at timeliner.io - the app never proxies bytes - so that host must be reachable from wherever you run. If you are sandboxed behind an outbound allowlist, get *.r2.cloudflarestorage.com allowed BEFORE calling this, or this call and complete_file_upload will both succeed while every PUT fails with your proxy's error; if that happens, call abort_file_upload and ask the user to allow the host or to upload the file in the app. Omit parts to let the server pick a count from size. The file counts toward the workspace's storage plan like any upload.
| Name | Type | Details |
|---|---|---|
fileNamerequired | string | File name with extension, e.g. hero_cut_v3.mp4. |
sizerequired | integer | Exact file size in bytes. |
mimeType | string | MIME type; inferred from the name when omitted. |
parts | integer | How many parts you will PUT (1-10000). Omit to use the server's recommendation. |
projectId | uuid | Destination project for a raw project file. |
taskId | uuid | Destination task when the file is its new version. The TASK's own id (from list_tasks or create_task) - never a project, brand or folder id. A regular task or a variant - never a task with kind=parent, which holds no file (use one of its variants: list_tasks kind=variant, matching parentTaskId). Never an HQ item id (attach a file to an HQ item with upload_hq_item_file_from_url) or a file id. |
fileNamerequiredstringFile name with extension, e.g. hero_cut_v3.mp4.sizerequiredintegerExact file size in bytes.mimeTypestringMIME type; inferred from the name when omitted.partsintegerHow many parts you will PUT (1-10000). Omit to use the server's recommendation.projectIduuidDestination project for a raw project file.taskIduuidDestination task when the file is its new version. The TASK's own id (from list_tasks or create_task) - never a project, brand or folder id. A regular task or a variant - never a task with kind=parent, which holds no file (use one of its variants: list_tasks kind=variant, matching parentTaskId). Never an HQ item id (attach a file to an HQ item with upload_hq_item_file_from_url) or a file id.{
"fileName": "example",
"size": 1
}Key scope: write:files
upload_file_from_urlUpload a file from a URLWritePut a file INTO Timeliner by giving us a publicly reachable http(s) URL - we download it server-side and attach it. Two destinations: pass taskId to attach it as the task's NEW VERSION (the deliverable - same as a person dragging the file onto the task: it becomes the current version, fires version automations, notifies reviewers, and lands in the Activity Log), or pass projectId alone to save it as a project raw file (optionally inside a folder with folderId). taskId must be a regular task or a variant, never a parent: a task whose kind is "parent" (get_task and list_tasks return kind) is a container with no file of its own - each of its variants carries its own. To upload to one, call list_tasks with that projectId and kind=variant and use a variant whose parentTaskId is the parent's id (none yet → create_task with kind=variant and that parentTaskId). A parent's id is refused with 400. To set a post's cover image: upload the image with projectId, then pass the returned id to update_post as thumbnailFileId. The file must be reachable without authentication and at most 100 MB - bigger deliverables (full 4K exports) still go through the app's own uploader. Private, internal and localhost addresses are refused. fileName overrides the name we infer from the URL; a name already used in the project gets a " (1)" suffix.
| Name | Type | Details |
|---|---|---|
urlrequired | url | Public http(s) URL of the file to pull in. No auth, max 100 MB. |
taskId | uuid | Attach as this task's new version (the deliverable). Omit for a project file. The TASK's own id (from list_tasks or create_task) - never a project, brand or folder id. A regular task or a variant - never a task with kind=parent, which holds no file (use one of its variants: list_tasks kind=variant, matching parentTaskId). Never an HQ item id (attach a file to an HQ item with upload_hq_item_file_from_url) or a file id. |
projectId | uuid | Save as a raw file in this project. Required when taskId is omitted. |
folderId | uuid | Optional project folder for the file. Project-file uploads only. |
fileName | string | Override the stored filename (include the extension). |
urlrequiredurlPublic http(s) URL of the file to pull in. No auth, max 100 MB.taskIduuidAttach as this task's new version (the deliverable). Omit for a project file. The TASK's own id (from list_tasks or create_task) - never a project, brand or folder id. A regular task or a variant - never a task with kind=parent, which holds no file (use one of its variants: list_tasks kind=variant, matching parentTaskId). Never an HQ item id (attach a file to an HQ item with upload_hq_item_file_from_url) or a file id.projectIduuidSave as a raw file in this project. Required when taskId is omitted.folderIduuidOptional project folder for the file. Project-file uploads only.fileNamestringOverride the stored filename (include the extension).{
"url": "https://files.example.com/exports/launch-reel-03-v2.mp4",
"taskId": "9b3e7a41-5c2d-4f8e-a6b0-1d7c3e9f2a64",
"fileName": "launch-reel-03-v2.mp4"
}Key scope: write:files
generate_post_captionGenerate AI caption, YouTube title, or tagsWriteSocial postingHave Timeliner's AI write a post's copy from the task's own material: the video's transcript (or its images for static/carousel posts) plus the brand's previously published posts for voice-matching and any dated corrections the team gave on earlier drafts. Set `field` to choose what to write - 'caption' (default, any network), 'title' (the YouTube video title), or 'tags' (the YouTube tag list); title and tags require platform:'youtube'. Captions are platform-tuned (short for TikTok/Reels, longer for IG feed, written in the source material's language, no emojis/hashtags by design. If the draft already has text in that field, the call REWRITES that text instead (same subject and facts, better wording) and reads nothing from the video, title or brief - no transcription, no transcribing status. Each call costs one AI generation - for a batch, loop task by task. If the video isn't transcribed yet, the call auto-starts transcription and returns {status:"transcribing", retryAfterSeconds} - retry the SAME call after that many seconds until it flips to {status:"ok"} (usually under a minute or two). {status:"failed"} is terminal for the task's current material (reason: no_speech, transcript_failed, transcripts_unavailable, no_visuals, insufficient_content). reason:transcript_failed means an EARLIER transcription of this video failed; its message says why. Calling again unchanged will always fail the same way - pass retryTranscription:true ONCE to re-run the transcription (spends minutes) and get {status:"transcribing"} back, then poll as above. If it fails again with the same reason the video cannot be transcribed as it stands: stop calling, tell the user, and either replace the file or write the caption without a transcript. reason:ai_unavailable (HTTP 503) means Timeliner's AI is temporarily out of capacity: every call fails the same way until it recovers, so stop calling, tell the user, and try again later. Read the result from `text` (`caption` is a deprecated alias kept for older clients). apply:true saves the result into the post draft (the shared caption, `platform`'s override, or the YouTube options for title/tags) so the team sees it in the composer - same side effect as update_post: a caption change resets the client's caption approval. apply defaults to false (the copy is only returned). Never schedules or publishes anything. Fails 400 for title/tags without platform:'youtube' and 409 when apply:true targets an already-published post. On a workspace whose plan does not include social posting the call is refused before it runs and returns {"available": false, "reason": "planDoesNotIncludeSocialPosting"} - a final answer, not data and not a retryable error: relay it and stop calling this tool.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The post's task UUID (a post is keyed by its task - same id list_posts filters by). |
platform | enum | Write a caption tuned for this one network (saved as its override when apply:true). Omit for the shared caption used by every network.One of: facebook, instagram, linkedin, linkedinCompany, tiktok, youtube |
field | enum | What to write. 'caption' (default) is the post body for any network. 'title' is the YouTube video title (written for search + click-through, max 100 chars). 'tags' is the YouTube tag list (comma-separated keywords). 'title' and 'tags' are YouTube-only - pass platform:'youtube' with them or the call fails 400. With apply:true they are saved into the draft's YouTube options, not into a caption.One of: caption, title, tags |
instructions | string | Optional steering: tone, angle, something to mention. |
apply | boolean | true = save the generated copy into the post draft. Default false (return only). |
retryTranscription | boolean | true = re-run a transcription that previously FAILED, instead of returning reason:transcript_failed (spends transcription minutes). Default false. Use it once after a transcript_failed answer; if the retry fails the same way, the video is not transcribable - stop rather than looping. |
idrequireduuidThe post's task UUID (a post is keyed by its task - same id list_posts filters by).platformenumWrite a caption tuned for this one network (saved as its override when apply:true). Omit for the shared caption used by every network.One of: facebook, instagram, linkedin, linkedinCompany, tiktok, youtubefieldenumWhat to write. 'caption' (default) is the post body for any network. 'title' is the YouTube video title (written for search + click-through, max 100 chars). 'tags' is the YouTube tag list (comma-separated keywords). 'title' and 'tags' are YouTube-only - pass platform:'youtube' with them or the call fails 400. With apply:true they are saved into the draft's YouTube options, not into a caption.One of: caption, title, tagsinstructionsstringOptional steering: tone, angle, something to mention.applybooleantrue = save the generated copy into the post draft. Default false (return only).retryTranscriptionbooleantrue = re-run a transcription that previously FAILED, instead of returning reason:transcript_failed (spends transcription minutes). Default false. Use it once after a transcript_failed answer; if the retry fails the same way, the video is not transcribable - stop rather than looping.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:posts
get_content_insightsGet content insights (what worked)ReadSocial postingLearn WHAT WORKED on social: the published posts that beat and trailed their platform's typical engagement rate, each joined with its hook (first caption line + what the video says in its first 8 seconds, from the transcript), a transcript excerpt (≤600 chars), duration, format and metrics - plus pattern findings (hook length, question hooks, duration, format, caption length, best weekday/hour vs the median), each with sampleSize and lowConfidence. engagementRate = (likes+comments)/views; relativeToMedian = that rate ÷ the platform's median (1.8 = 80% above typical). Only posts published ≥24 h ago with views are scored, and a platform needs ≥3 such posts. Pattern buckets: hookLength short|medium|long (≤8 / 9-15 / >15 words); questionHook question|statement; duration under15|15to30|30to60|60to180|over180 (seconds); captionLength short|medium|long (<100 / 100-300 / >300 chars); weekday 0-6 = Mon-Sun; hour 0-23 in timeZone. Say 'low confidence' whenever lowConfidence is true; never overstate a pattern. Defaults to the last 90 days; same filters as get_social_analytics. Posts imported from outside Timeliner (source:'external', taskId null) are ranked too - their hook is the caption and they never carry a transcript. On a workspace whose plan does not include social posting the call is refused before it runs and returns {"available": false, "reason": "planDoesNotIncludeSocialPosting"} - a final answer: relay it and stop.
| Name | Type | Details |
|---|---|---|
from | string | ISO window start (default: 90 days before `to`). |
to | string | ISO window end (default: now). |
brandId | uuid | Restrict to one brand/client. |
projectId | uuid | Restrict to one project. |
accountId | uuid | Restrict to one connected social account. |
platforms | enum[] | Restrict to these platforms.One of: facebook, instagram, linkedin, linkedinCompany, tiktok, youtube |
timeZone | string | IANA zone for the weekday/hour patterns (default UTC - prefer the workspace's zone). |
fromstringISO window start (default: 90 days before `to`).tostringISO window end (default: now).brandIduuidRestrict to one brand/client.projectIduuidRestrict to one project.accountIduuidRestrict to one connected social account.platformsenum[]Restrict to these platforms.One of: facebook, instagram, linkedin, linkedinCompany, tiktok, youtubetimeZonestringIANA zone for the weekday/hour patterns (default UTC - prefer the workspace's zone).{}Key scope: read:posts
get_platform_guidanceGet official platform guidanceReadOfficial statements from the social platforms themselves (Instagram, Facebook, TikTok, YouTube, LinkedIn, X, Threads) on third-party scheduling & reach, original vs reposted content, trial reels, collaborators, audio, posting limits, required disclosures and API deprecations - newest first. Every item carries title, summary, sourceUrl + sourceTitle (always an official platform domain), publishedAt (the source's own date, may be null) and fetchedAt (when it was read). DEFAULT scope 'workspace' answers 'any platform changes I should know about?' for THIS workspace: only its connected networks (connectedPlatforms), each item's affects.accounts (handle, brandName) and affects.posts (its upcoming + last-30-days posts; postCount = all), developer / API notices left out (developerNoticesOmitted - Timeliner handles them), and accountsNeedingAttention = accounts whose connection expired or was revoked (the user must reconnect them). Answer per item with the account handles and affected post titles, or say none of their posts is affected; when `note` is set (nothing connected / nothing new for their networks) say exactly that - never a generic list. scope 'all' = every network's guidance (a network they have not connected, or the rules in general). ALWAYS cite title + URL + date when you use an item. If no item covers the question, say plainly that no official source settles it - never fill the gap with blog folklore. source:'seed' = the built-in verified set (no newer collection yet).
| Name | Type | Details |
|---|---|---|
scope | enum | workspace (default) = this workspace's connected networks with the accounts and posts each item affects; all = every network's guidance.One of: workspace, all |
platform | enum | Restrict to one platform.One of: instagram, facebook, tiktok, youtube, linkedin, x, threads, general |
topic | enum | Restrict to one topic.One of: thirdPartyScheduling, originality, trialReels, collaborators, audio, postingLimits, apiDeprecations, disclosures, other |
limit | integer | Items to return, 1-50 (default 20). |
scopeenumworkspace (default) = this workspace's connected networks with the accounts and posts each item affects; all = every network's guidance.One of: workspace, allplatformenumRestrict to one platform.One of: instagram, facebook, tiktok, youtube, linkedin, x, threads, generaltopicenumRestrict to one topic.One of: thirdPartyScheduling, originality, trialReels, collaborators, audio, postingLimits, apiDeprecations, disclosures, otherlimitintegerItems to return, 1-50 (default 20).{}Key scope: read:posts
list_channelsList social channelsReadThe connected-vs-manual channel map, per brand - use it BEFORE scheduling to tell the user which platforms will auto-post and which they'll have to post manually. Every platform is listed for every brand with: connected (a live connected account exists - its posts auto-publish), autoPublishSupported (false = always manual), and account (handle/displayName/followerCount; also present with status 'expired'/'revoked' when a reconnect is needed - say so). Optional brandId narrows to one brand. Channels connect in-app (brand dialog → Connected accounts, or a client connect link) - you cannot connect accounts via the API.
| Name | Type | Details |
|---|---|---|
brandId | uuid | Restrict to one brand/client. |
brandIduuidRestrict to one brand/client.{}Key scope: read:posts
list_postsList scheduled postsReadRead the social-posting calendar: the workspace's scheduled/published posts, newest first. A post is a task with a scheduling status. Each post carries brandId + projectId; assigneeIds (the team-member ids assigned to the post's task - resolve names via list_members; answers 'which editor made this post' without a get_task per post, [] when unassigned); the deliverable approval stage (statusGroup - inRevision = sent back/rejected, approved/posted = signed off); overallStatus (the publish rollup: draft|pending|manual|published|failed|missed); scheduledAt; a review object (guest per-element sign-off: caption/thumbnail/schedule approved-at timestamps, null = not approved); media + customThumbnail (presigned download/thumbnail/preview URLs you can open to SEE the media); and a networks array - one entry per social network with its caption, status, scheduledAt/publishedAt, permalink (the live post URL once posted), the connected account it publishes to (account: handle/displayName/avatar/platform), per-network media override, and platformOptions (e.g. YouTube title/tags/visibility, IG format). Times are UTC instants - interpret them in the response's top-level timezone (the workspace IANA zone, also on get_workspace). Filter by taskId (a post is keyed by its task - there is no get_post), projectId, brandId, overall status, or a calendar window with scheduledFrom/scheduledTo. Returns {data, nextBefore}; when nextBefore is non-null, call again with before=nextBefore for the next page.
| Name | Type | Details |
|---|---|---|
limit | integer | Items per page, 1-100. Defaults to 50. |
before | string | Opaque pagination cursor - pass the previous response's nextBefore value verbatim. |
taskId | uuid | Return only the single post for this task (empty list if none). |
projectId | uuid | |
brandId | uuid | |
status | enum | Filter by the post's overall scheduling status.One of: draft, pending, manual, published, failed, missed |
scheduledFrom | string | Calendar window start (inclusive) - ISO-8601 date/time; keep only posts scheduled at or after it. |
scheduledTo | string | Calendar window end (inclusive) - ISO-8601 date/time; keep only posts scheduled at or before it. |
includeMetrics | boolean | true = each network also carries metrics: its latest snapshot (views, reach, impressions, likes, comments, shares, saves, engagementRate, watch-time fields, capturedAt), null when none was fetched (report 'no data', never zeros). Default off. |
limitintegerItems per page, 1-100. Defaults to 50.beforestringOpaque pagination cursor - pass the previous response's nextBefore value verbatim.taskIduuidReturn only the single post for this task (empty list if none).projectIduuidbrandIduuidstatusenumFilter by the post's overall scheduling status.One of: draft, pending, manual, published, failed, missedscheduledFromstringCalendar window start (inclusive) - ISO-8601 date/time; keep only posts scheduled at or after it.scheduledTostringCalendar window end (inclusive) - ISO-8601 date/time; keep only posts scheduled at or before it.includeMetricsbooleantrue = each network also carries metrics: its latest snapshot (views, reach, impressions, likes, comments, shares, saves, engagementRate, watch-time fields, capturedAt), null when none was fetched (report 'no data', never zeros). Default off.{}Key scope: read:posts
schedule_postSchedule postWriteSocial postingSchedule a task as a social post - a media task, or a regular DOCUMENT task (a text-first post: the document is the post text, and the task's one file, if it has one, is the attachment): set the slot (scheduledAt, must be in the future) + target platforms. Full-replace semantics (also reschedules and re-arms a 'missed' post); draft:true places a calendar draft that never enters the publish queue. The response tells you what will ACTUALLY happen - always relay it: `channels` lists each leg as auto (a connected account publishes it) or manual (not connected - so the team posts it themselves), and `approvalGate` says whether the post will auto-publish at the slot. IMPORTANT: when the workspace requires approval before posting (the default) and the task is not 'approved'/'posted', the schedule still succeeds but the post will NOT fire - at the slot it is marked 'missed' and held (it never auto-publishes late; it must be rescheduled). Relay the approvalGate.warning and ASK the user whether to move the task to 'approved' now - if yes, call update_task with statusGroup:'approved' (a deliberate, notifying, hard-to-undo step; never do it silently). Routing through 'clientApproval' instead does NOT publish - the client must approve (reaching 'approved') before the slot. Compose content first (update_post / generate_post_caption) and check list_channels to pick platforms. Fires the same post-plan notification as an in-app schedule (guests only see it once the task is at clientApproval or later) and the partner post.status_changed webhook. Fails 409 on an already-published post and 400 for past slots, for a parent or document-variant task, and for a text-only document post (no file attached) aimed at a network that needs media - only LinkedIn and Facebook take text-only posts; attach media first with upload_file_from_url and the same taskId. A document post that was approved and then changed is held at its slot until it is approved again. On a workspace whose plan does not include social posting the call is refused before it runs and returns {"available": false, "reason": "planDoesNotIncludeSocialPosting"} - a final answer, not data and not a retryable error: relay it and stop calling this tool.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The post's task UUID (a post is keyed by its task - same id list_posts filters by). |
scheduledAtrequired | string | ISO-8601 instant the post should go out at (UTC or offset). Must be in the future. |
platformsrequired | enum[] | Target networks (full replace of the post's platform set).One of: facebook, instagram, linkedin, linkedinCompany, tiktok, youtube |
draft | boolean | true = save as a calendar draft (never auto-publishes). Default false. |
idrequireduuidThe post's task UUID (a post is keyed by its task - same id list_posts filters by).scheduledAtrequiredstringISO-8601 instant the post should go out at (UTC or offset). Must be in the future.platformsrequiredenum[]Target networks (full replace of the post's platform set).One of: facebook, instagram, linkedin, linkedinCompany, tiktok, youtubedraftbooleantrue = save as a calendar draft (never auto-publishes). Default false.{
"id": "9b3e7a41-5c2d-4f8e-a6b0-1d7c3e9f2a64",
"scheduledAt": "2026-11-02T15:00:00.000Z",
"platforms": [
"instagram",
"tiktok"
]
}Key scope: write:posts
unschedule_postUnschedule postWriteDeletesSocial postingRemove a post from the publish queue: clears the slot and the scheduling status (the platform selection is kept so re-scheduling doesn't re-pick networks). Use it to cancel a scheduled/draft/manual/missed post before it goes out. Fails 409 when the post isn't scheduled or is already published (a live post can't be unscheduled - deleting it from the network stays in-app). On a workspace whose plan does not include social posting the call is refused before it runs and returns {"available": false, "reason": "planDoesNotIncludeSocialPosting"} - a final answer, not data and not a retryable error: relay it and stop calling this tool. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The post's task UUID (a post is keyed by its task - same id list_posts filters by). |
idrequireduuidThe post's task UUID (a post is keyed by its task - same id list_posts filters by).{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:posts
update_postUpdate post contentWriteSocial postingWrite or rewrite a post's content - the shared caption, per-network caption overrides, the custom cover (thumbnailFileId - a file already uploaded to the post's project; changing it resets the client's thumbnail approval), and/or the per-network settings via platformOptions: YouTube metadata (title, tags, visibility, isShorts), Instagram (format feed/reel/story, collaborator - up to 3 handles, really invited at publish), first comment), Facebook (first comment; format only accepts 'post' for now), LinkedIn (postAs personal/company - also covers Company Page legs, targetAudience [stored but not applied at publish yet], first comment), and TikTok (privacyLevel, allowComments/Duet/Stitch, commercial-content toggles). Manual steps: collaborators on LinkedIn / YouTube (videos and Shorts) / TikTok / Facebook and an in-app audioNote (Instagram, TikTok, Facebook Reels, YouTube Shorts) make that network's leg a manual hand-off - the team is reminded at posting time instead of it auto-publishing; Instagram trialReel is refused (422) until enabled for the deployment. This is the content-crafting tool: read the post (list_posts with taskId) or the task's media (get_task) for context, draft the copy, and save it here. Read the brand's voice profile first (get_brand_voice) and write in that style. A post is keyed by its task; writing content for a media task that isn't planned on the calendar yet is allowed and simply pre-fills the composer for the team. On a DOCUMENT post (a regular doc task) the shared caption IS the document: do not send `caption` here (409 captionFromDoc) - change the text with update_task_doc, and the post follows it; platformCaptions, platformOptions (e.g. LinkedIn firstComment) and thumbnailFileId work as usual. This tool NEVER schedules, moves, or publishes anything - use schedule_post for the slot + networks - and it fires no notifications. platformCaptions merges per key: a string sets that network's override, null (or "") removes it so the network falls back to the shared caption; omitted networks are untouched. platformOptions deep-merges per platform - unpatched fields (thumbnails, covers, A/B state - not writable via the API) survive. Captions are capped at 5000 characters (stricter per-network limits are enforced by the composer); YouTube titles at 100; first comments at 2200. TikTok commercial-content legality combos are clamped at publish against the creator's live options. Editing the caption of an already-client-approved post resets its caption approval (the client re-reviews), exactly like an in-app edit. Fails with 409 on published posts (frozen). Send at least one of caption, platformCaptions, platformOptions or thumbnailFileId - a call with only the id (or only argument names this tool does not define, which are dropped) is refused before it runs and saves nothing. On a workspace whose plan does not include social posting the call is refused before it runs and returns {"available": false, "reason": "planDoesNotIncludeSocialPosting"} - a final answer, not data and not a retryable error: relay it and stop calling this tool.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The post's task UUID (a post is keyed by its task - same id list_posts filters by). |
caption | string | Shared caption for every network ("" clears it). |
platformCaptions | object | Per-network caption overrides, merged key-by-key: string sets, null removes (falls back to the shared caption), omitted networks keep their current override. |
platformOptions | object | Per-network composer options patch. Writable: youtube, instagram, facebook, linkedin (incl. Company Page legs via postAs), tiktok (incl. coverTimestampMs). Deep-merged per platform - unpatched fields and the in-browser image/thumbnail settings survive. Name at least one network with at least one field (omit platformOptions to leave every network as it is); any other key is refused. |
thumbnailFileId | uuid | null | Custom cover for the post: the id of a file ALREADY UPLOADED to this post's project (400 otherwise). null clears it. Picking a video FRAME isn't possible here - frames are captured in the browser composer; upload the cover image to the project and pass its file id. Changing the cover resets the client's thumbnail approval (they re-review), exactly like an in-app change. |
idrequireduuidThe post's task UUID (a post is keyed by its task - same id list_posts filters by).captionstringShared caption for every network ("" clears it).platformCaptionsobjectPer-network caption overrides, merged key-by-key: string sets, null removes (falls back to the shared caption), omitted networks keep their current override.platformOptionsobjectPer-network composer options patch. Writable: youtube, instagram, facebook, linkedin (incl. Company Page legs via postAs), tiktok (incl. coverTimestampMs). Deep-merged per platform - unpatched fields and the in-browser image/thumbnail settings survive. Name at least one network with at least one field (omit platformOptions to leave every network as it is); any other key is refused.thumbnailFileIduuid | nullCustom cover for the post: the id of a file ALREADY UPLOADED to this post's project (400 otherwise). null clears it. Picking a video FRAME isn't possible here - frames are captured in the browser composer; upload the cover image to the project and pass its file id. Changing the cover resets the client's thumbnail approval (they re-review), exactly like an in-app change.{
"id": "9b3e7a41-5c2d-4f8e-a6b0-1d7c3e9f2a64",
"caption": "Our new collection is live. Link in bio."
}Key scope: write:posts
create_chat_messagePost a chat messageWritePost a message into a chat channel, authored by the API key's creator. Silent - nothing beyond the channel's own unread badge fires. Find the channel with list_chat_channels first. NOT for social publishing.
| Name | Type | Details |
|---|---|---|
scopeTyperequired | enum | Channel kind. 'global' is the workspace-wide channel.One of: global, brand, project, dm, custom |
textrequired | string | The message text. |
scopeId | uuid | Brand / project / conversation / standalone-channel id. Omit for global. |
scopeTyperequiredenumChannel kind. 'global' is the workspace-wide channel.One of: global, brand, project, dm, customtextrequiredstringThe message text.scopeIduuidBrand / project / conversation / standalone-channel id. Omit for global.{
"scopeType": "global",
"text": "example"
}Key scope: write:chat
create_task_chat_messagePost in a task's chatWritePost a message in a task's chat, authored by the API key's creator. Silent - no one is notified. Use create_task_comment instead for timecoded review feedback on a cut.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
textrequired | string | The message text. |
idrequireduuidThe task's UUID - from list_tasks.textrequiredstringThe message text.{
"id": "00000000-0000-0000-0000-000000000000",
"text": "example"
}Key scope: write:chat
list_chat_channelsList chat channelsReadList the CHAT channels the API key's creator can see in Discussions: the workspace-wide channel and standalone channels like #marketing. Each: scopeType, scopeId, name, visibility (private/team/client, standalone only), archived. Private channels the key's creator isn't a member of are omitted. NOT social posting accounts - that's list_channels.
No inputs.
{}Key scope: read:chat
list_chat_messagesRead a chat channelReadRead recent messages in one chat channel, oldest-first. scopeType is 'global' (no scopeId), or 'brand' / 'project' / 'dm' / 'custom' with the matching id - get standalone channel ids from list_chat_channels. Each message: id, text, author, isSystem, teamOnly, createdAt.
| Name | Type | Details |
|---|---|---|
scopeTyperequired | enum | Channel kind. 'global' is the workspace-wide channel.One of: global, brand, project, dm, custom |
scopeId | uuid | Brand / project / conversation / standalone-channel id. Omit for global. |
limit | integer | How many messages (max 100). |
scopeTyperequiredenumChannel kind. 'global' is the workspace-wide channel.One of: global, brand, project, dm, customscopeIduuidBrand / project / conversation / standalone-channel id. Omit for global.limitintegerHow many messages (max 100).{
"scopeType": "global"
}Key scope: read:chat
list_task_chatRead a task's chatReadRead a task's CHAT - the conversation about the task (updates, questions, decisions). Returns the NEWEST posts (default 30, max 100 via `limit`), presented oldest-first. This is NOT the timecoded review thread: use list_task_comments for feedback on a specific frame. Pass a task id from list_tasks.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID - from list_tasks. |
limit | integer | How many of the newest posts (max 100). |
idrequireduuidThe task's UUID - from list_tasks.limitintegerHow many of the newest posts (max 100).{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:chat
get_member_profitabilityGet member profitabilityReadPer-member profitability for ANY pay type - the 'was this hire/freelancer worth it?' dataset: total cost (per-task pay + fixed project fees + prorated salary), what's been paid out, BOOKED client revenue attributed to them (billing-mode aware: task prices split across a task's editors; fixed/legacy project prices allocated by delivered share), delivered + in-progress counts, cost per video, a strong/thin/underwater ROI verdict, and - over the same window - viewsAttributed + engagementAttributed (the video views and engagement their published videos earned, even-split per editor; null when the workspace has no social-distribution data). Answers 'what did this editor cost me and how many views did their videos get?' in one call. `payType` reports the member's model over the window: perTask | perProject | monthly | mixed (both task pay and project fees). Optional from/to (ISO date) filter by task creation date; optional memberId narrows to one member; includeProjects=true appends members[].projects[] - the per-(member, project) breakdown (pay model there, fee, per-task buckets, tagged/delivered counts, and the days span from first task to last delivery).
| Name | Type | Details |
|---|---|---|
from | string | ISO date/datetime - include tasks created on/after this. |
to | string | ISO date/datetime - include tasks created on/before this. |
memberId | string | Limit to one member id (from list_members). |
includeProjects | boolean | true = append the per-(member, project) breakdown under members[].projects[]. |
fromstringISO date/datetime - include tasks created on/after this.tostringISO date/datetime - include tasks created on/before this.memberIdstringLimit to one member id (from list_members).includeProjectsbooleantrue = append the per-(member, project) breakdown under members[].projects[].{}Key scope: read:finance
get_pricing_payablesGet team payablesReadWhat the agency owes its team (editors) - split approved vs pending and paid vs unpaid, per editor, plus workspace totals. Members on a monthly salary are reported under `salaries` instead of the per-task buckets: each row carries windowCost (prorated salary), delivered, activeCount (still in progress/revisions), costPerVideo, and attributedRevenue (booked client money credited to their work). To judge whether a hire is worth it, compare attributedRevenue vs windowCost (≥2x = healthy, <1x = losing money) and check costPerVideo against what the client pays per deliverable. Optional from/to (ISO date) filter by task creation date. The numbers match the in-app Payments Dashboard and the analytics Cost tab.
| Name | Type | Details |
|---|---|---|
from | string | ISO date/datetime - include tasks created on/after this. |
to | string | ISO date/datetime - include tasks created on/before this. |
fromstringISO date/datetime - include tasks created on/after this.tostringISO date/datetime - include tasks created on/before this.{}Key scope: read:finance
get_pricing_receivablesGet client receivablesReadWhat each brand/client owes the agency - split approved vs pending and paid vs outstanding, per brand, plus workspace totals and net + projected margin. Includes project-level prices (retainer/package deals) folded into the totals and itemized under `projects`. Optional from/to (ISO date) filter by task creation date. The numbers match the in-app Analytics Financial Summary.
| Name | Type | Details |
|---|---|---|
from | string | ISO date/datetime - include tasks created on/after this. |
to | string | ISO date/datetime - include tasks created on/before this. |
fromstringISO date/datetime - include tasks created on/after this.tostringISO date/datetime - include tasks created on/before this.{}Key scope: read:finance
get_project_pnlGet project P&LReadOne project's profit & loss - billing-mode-aware revenue (a fixed project price IS the revenue; otherwise task prices bill), team cost (per-task pay or fixed member fees, plus salary allocated by delivered share), realized margin and margin %. `unpricedTasks` > 0 means the margin is an 'at least' figure. Optional from/to (ISO date) filter by task creation date. Matches the in-app Project Profitability card.
| Name | Type | Details |
|---|---|---|
idrequired | string | Project id. |
from | string | ISO date/datetime - include tasks created on/after this. |
to | string | ISO date/datetime - include tasks created on/before this. |
idrequiredstringProject id.fromstringISO date/datetime - include tasks created on/after this.tostringISO date/datetime - include tasks created on/before this.{
"id": "example"
}Key scope: read:finance
list_paymentsList paymentsReadThe append-only payment ledger - what money ACTUALLY moved, with real business dates (including backdates). Each row carries `voided` (true when a reversal cancelled it) and reversal rows carry `reverses`. Filter by direction ('out' = paid to members, 'in' = received from brands), counterparty (editor/node), project, or a paid_at date window. Matches the in-app Payment history blocks.
| Name | Type | Details |
|---|---|---|
direction | enum | 'out' = payouts to members; 'in' = client money received.One of: in, out |
editor | string | Member profile id (from list_members). |
node | string | Brand node id (from list_brands). |
project | string | Project id (from list_projects). |
from | string | ISO date - payments on/after this business date. |
to | string | ISO date - payments on/before this business date. |
limit | integer | Max rows (default 100). |
directionenum'out' = payouts to members; 'in' = client money received.One of: in, outeditorstringMember profile id (from list_members).nodestringBrand node id (from list_brands).projectstringProject id (from list_projects).fromstringISO date - payments on/after this business date.tostringISO date - payments on/before this business date.limitintegerMax rows (default 100).{}Key scope: read:finance
list_pricing_templatesList pricing templatesReadList the workspace's reusable pricing models/templates - name, pricingMode (flat/rules/profitShare/hourly), default client price + editor pay, and badge. Use these to understand how deliverables are priced.
No inputs.
{}Key scope: read:finance
record_paymentRecord paymentWriteRecord ONE payment in the append-only ledger - money paid OUT to a team member (memberId required) or received IN from a brand/client (nodeId required). Partial amounts are fine; backdate with paidAt when the money moved earlier. Does NOT mark owed tasks/prices as settled. Pass idempotencyKey to make retries safe (a replay returns the original row with duplicate=true). Corrections go through void_payment, never edits.
| Name | Type | Details |
|---|---|---|
directionrequired | enum | 'out' = paid to a member; 'in' = received from a brand.One of: in, out |
amountrequired | number | The amount actually paid/received. |
memberId | uuid | The member paid (required for 'out') - from list_members. |
nodeId | uuid | The paying brand (required for 'in') - from list_brands. |
projectId | uuid | Optional project this payment belongs to. |
paidAt | string | Optional ISO business date to backdate. |
method | string | Optional method - e.g. PayPal, wire. |
note | string | Optional note for the accountant. |
idempotencyKey | string | Client-chosen replay-safety key. |
directionrequiredenum'out' = paid to a member; 'in' = received from a brand.One of: in, outamountrequirednumberThe amount actually paid/received.memberIduuidThe member paid (required for 'out') - from list_members.nodeIduuidThe paying brand (required for 'in') - from list_brands.projectIduuidOptional project this payment belongs to.paidAtstringOptional ISO business date to backdate.methodstringOptional method - e.g. PayPal, wire.notestringOptional note for the accountant.idempotencyKeystringClient-chosen replay-safety key.{
"direction": "in",
"amount": 1
}Key scope: write:finance
set_client_paidMark client money receivedWriteSettle client money: mark specific tasks' client prices (taskIds) OR one project's project-level price (projectId - retainers / fixed billing) as received or outstanding. Exactly one of taskIds/projectId. Marking received writes dated 'in' ledger entries with the brand counterparty (backdate with paidAt); marking outstanding voids them by reversal. Same engine as the in-app brand tab.
| Name | Type | Details |
|---|---|---|
paidrequired | boolean | true = received, false = back to outstanding (voids the payment). |
taskIds | uuid[] | Task ids whose client price is being marked. |
projectId | uuid | A project whose PROJECT-LEVEL price is being marked. |
paidAt | string | Optional ISO date to backdate (ignored when paid=false). |
paidrequiredbooleantrue = received, false = back to outstanding (voids the payment).taskIdsuuid[]Task ids whose client price is being marked.projectIduuidA project whose PROJECT-LEVEL price is being marked.paidAtstringOptional ISO date to backdate (ignored when paid=false).{
"paid": true
}Key scope: write:finance
set_payouts_paidMark team payouts paidWriteSettle team money: mark per-task pay rows (editorPayIds - TaskEditorPay ids from get_pricing_payables) and/or per-member project fees (memberFeeIds) as paid or unpaid. Marking paid writes dated 'out' entries in the append-only ledger (backdate with paidAt when the money went out earlier); marking unpaid voids them by reversal. Idempotent on retry. Same engine as the in-app checkboxes.
| Name | Type | Details |
|---|---|---|
paidrequired | boolean | true = mark paid, false = mark unpaid (voids the payments). |
editorPayIds | uuid[] | TaskEditorPay row ids to flip. |
memberFeeIds | uuid[] | ProjectMemberPay fee row ids to flip. |
paidAt | string | Optional ISO date to backdate (ignored when paid=false). |
paidrequiredbooleantrue = mark paid, false = mark unpaid (voids the payments).editorPayIdsuuid[]TaskEditorPay row ids to flip.memberFeeIdsuuid[]ProjectMemberPay fee row ids to flip.paidAtstringOptional ISO date to backdate (ignored when paid=false).{
"paid": true
}Key scope: write:finance
set_project_billingSet project billingWriteConfigure a project's two independent billing axes - client side: 'perTask' (task prices bill) or 'fixedPrice' (one project price IS the revenue; amount required, optional label); team side: 'perTask' (per-task pay rows) or 'fixedProject' (one fee per member; memberFees replaces the fee list - absent unpaid rows are removed, paid rows survive). For SINGLE-member fee changes ('pay Alex $1,500 for this project') use team.feeUpdates instead - a merge that upserts each listed member's fee (amount null = remove), valid on ANY team mode (the fee becomes that member's pay of record in this project; their per-task pay there stops counting). Any combination is valid. Existing task prices/pay rows are KEPT but stop counting per the mode - switching back restores them; nothing is destroyed. Paid fees are payment records and refuse edits (409).
| Name | Type | Details |
|---|---|---|
idrequired | string | Project id. |
client | object | Client-billing axis; omit to leave untouched. |
team | object | Team-pay axis; omit to leave untouched. |
idrequiredstringProject id.clientobjectClient-billing axis; omit to leave untouched.teamobjectTeam-pay axis; omit to leave untouched.{
"id": "example"
}Key scope: write:finance
set_task_pricingSet task pricingWriteApply a pricing template to ONE task and/or set which template split role each assigned editor fills - e.g. two editors on a task but only the one tagged 'Online Editor' is paid: pass editorRoles [{memberId: <online editor>, role: 'Online Editor'}, {memberId: <other>, role: null}]. role null is 'No role (not paid)' - an opt-out that zeroes that editor's pay, NOT a clear; to return an editor to untagged send {memberId, role: null, untag: true}. To undo a change, re-send only the editors you changed: their previous role, or untag: true where the result's roleSource was null - never role null for them. A role one editor holds is never auto-given to another, and editors sharing one role split its share. Prices are recomputed by the in-app engine and the result lists every editor's role, roleSource and amount. Template ids come from list_pricing_templates, member ids from list_members / get_task. Refused (409) once money has moved on the task (a payout marked paid, the client price received, or a recorded payment).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The task's UUID. |
templateId | uuid | Pricing template to apply. Omit to keep the task's current template. |
editorRoles | object[] | Per-editor role tags. Roles must be ones the template defines (case-insensitive). |
idrequireduuidThe task's UUID.templateIduuidPricing template to apply. Omit to keep the task's current template.editorRolesobject[]Per-editor role tags. Roles must be ones the template defines (case-insensitive).{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:finance
void_paymentVoid paymentWriteDeletesCancel one ledger payment by recording its reversal (the ledger is append-only - nothing is deleted; the history keeps both entries). Voiding an already-voided payment returns a 409 conflict; reversal rows themselves cannot be voided.
| Name | Type | Details |
|---|---|---|
idrequired | string | The payment id to void (from list_payments). |
idrequiredstringThe payment id to void (from list_payments).{
"id": "example"
}Key scope: write:finance
create_automation_ruleCreate automation ruleWriteCreate an Automations Center rule - e.g. the backward-scheduling recipe: trigger publishDateScheduled + setDateColumn/setDeadline/setStartDate actions with NEGATIVE offsetDays counting back from the publish date (-21 = 21 days before publish). The same recipe works from a client's intake answer: trigger intakeApproved with anchorQuestionId set to a DATE question on the request form, and every offset counts back from the date the client asked for. Under status/stage triggers, offsets count from the moment the rule fires instead. Max 25 rules per SCOPE (50 on Scale and Enterprise) - pack up to 20 actions into one rule rather than one rule per action. Pass brandId to scope the rule to one brand (from list_brands) instead of the whole workspace; a brand-scoped rule runs only on that brand's tasks AND only if that brand is set to use its own automations, so tell the user to check the brand's Automations settings. Pass hqBoardId INSTEAD to scope the rule to an HQ board (from list_hq_boards): the trigger and every action must then come from the hq* vocabulary, and the rule runs on that board's items only - brandId and hqBoardId are mutually exclusive. Two recipes worth knowing on a board: hqDueDateReached with a NEGATIVE offsetDays nudges before a card is due (reminders are armed when a due date is set or changed, so already-overdue cards are never flushed), and createHqItem on a DELIVERABLE rule drops a card on a board when a task lands - one card per (board, task), so it is safe on a status a task reaches repeatedly. Read list_automation_rules first; if the response's automationsCenterEnabled is false, warn the user the rule is saved but dormant until Settings → Automations is switched on. Requires an upgraded plan. NOT update_automations (review/workflow timeout scalars).
| Name | Type | Details |
|---|---|---|
triggerrequired | object | When the rule fires. Column/option ids come from list_custom_columns. |
actionsrequired | object[] | 1-20 actions, run in order. |
enabled | boolean | Default true. |
brandId | uuid | Scope the rule to this brand (id from list_brands). Omit for a workspace-global rule. |
hqBoardId | uuid | Scope the rule to this HQ board (id from list_hq_boards). The trigger and actions must then be the hq* ones. Mutually exclusive with brandId. |
triggerrequiredobjectWhen the rule fires. Column/option ids come from list_custom_columns.actionsrequiredobject[]1-20 actions, run in order.enabledbooleanDefault true.brandIduuidScope the rule to this brand (id from list_brands). Omit for a workspace-global rule.hqBoardIduuidScope the rule to this HQ board (id from list_hq_boards). The trigger and actions must then be the hq* ones. Mutually exclusive with brandId.{
"trigger": {
"type": "hqItemCreated"
},
"actions": [
{
"type": "hqMoveToStage",
"target": "next"
}
]
}Key scope: write:automations
create_intake_formCreate an intake formWriteCreate a new named intake (deliverable-request) form. Give a name; the authoring language can be any language Timeliner supports and defaults to 'en'. The first form (or setAsDefault:true) becomes the workspace default. The questions + catalog are then built in the app. Returns the created form. (Plan-gated feature; supervisor+.)
| Name | Type | Details |
|---|---|---|
namerequired | string | The form name. |
language | enum | Authoring language - any language Timeliner supports. Defaults to 'en'.One of: en, es, pt, de, nl, fr, hi, ru, zh, he, ar |
setAsDefault | boolean | Make this the workspace default form. |
namerequiredstringThe form name.languageenumAuthoring language - any language Timeliner supports. Defaults to 'en'.One of: en, es, pt, de, nl, fr, hi, ru, zh, he, arsetAsDefaultbooleanMake this the workspace default form.{
"name": "example"
}Key scope: write:automations
delete_automation_ruleDelete automation ruleWriteDeletesPermanently delete an Automations Center rule. Its run history is kept, but the rule cannot be restored - confirm with the user which rule dies before calling. To pause a rule instead, use update_automation_rule with enabled:false. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The rule id, from list_automation_rules. |
idrequireduuidThe rule id, from list_automation_rules.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:automations
get_automationsGet workspace automationsReadRead the workspace's automations: the review/workflow timeouts (auto-advance variant review, auto-revert-after-comment, auto-submit-for-review, the guest revert windows) AND the read-only notification automations (quiet hours, anti-spam batching, the scheduled catch-up digest).
No inputs.
{}Key scope: read:automations
get_intake_formGet an intake formReadRead one intake form in full: its sections (goal / central message / deadline / catalog / …), the question builder, the catalog items (deliverables with prices/badges), and the post-submission settings. Pass a form id from list_intake_forms.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The intake form's UUID - from list_intake_forms. |
idrequireduuidThe intake form's UUID - from list_intake_forms.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:automations
list_automation_rulesList workspace automation rulesReadRead the workspace's Automations Center rules - the 'when X happens → do Y' rules. TWO VOCABULARIES, never mixed inside one rule. DELIVERABLE rules (hqBoardId null): triggers status/stage change, posting time scheduled, a date column set, a version uploaded with optional media conditions, a client request approved, a client request coming in; actions move stage, set status, assign/unassign, set deadlines/dates/posting time/project deadline, send to brand, notify, apply a pricing template, add a card to an HQ board (createHqItem). HQ BOARD rules (hqBoardId set): triggers hqItemCreated, hqStageChanged, hqItemAssigned, hqDueDateSet, hqDueDateReached, hqFieldChanged, hqDocUpdated, hqFileAttached; actions hqMoveToStage, hqSetField, hqAssign, hqUnassign, hqSetDueDate, hqNotify, and hqSetSourceTaskStatus (moves the deliverable a card was spawned from). Use this to answer 'what automations do we have?' - and ALWAYS before create/update/delete_automation_rule. The response's automationsCenterEnabled is the master toggle: false means every rule is dormant. Each rule's brandId says which brand it is scoped to and hqBoardId which HQ board; both null means workspace-global. Pass brandId to list one brand's rules ('none' = only the workspace-global ones), or hqBoardId to list one board's ('none' = only the deliverable rules). Requires an upgraded plan. Not to be confused with get_automations (review/workflow timeouts).
| Name | Type | Details |
|---|---|---|
brandId | string | A brand id (from list_brands) for that brand's rules, or 'none' for only the workspace-global rules. Omit for every scope. |
hqBoardId | string | An HQ board id (from list_hq_boards) for that board's rules, or 'none' for only the deliverable rules. Omit for every scope. |
brandIdstringA brand id (from list_brands) for that brand's rules, or 'none' for only the workspace-global rules. Omit for every scope.hqBoardIdstringAn HQ board id (from list_hq_boards) for that board's rules, or 'none' for only the deliverable rules. Omit for every scope.{}Key scope: read:automations
list_intake_formsList intake formsReadList the workspace's intake (deliverable-request) forms - the Typeform-style request forms clients fill in to request work. Returns each form's id, name, language, and whether it's the workspace default. (Plan-gated feature.)
No inputs.
{}Key scope: read:automations
update_automation_ruleUpdate automation ruleWriteEdit an Automations Center rule: change its recipe (pass BOTH trigger and actions - they must come together) and/or flip enabled. Get rule ids from list_automation_rules. A rule's SCOPE is fixed - a board rule stays on its board and its replacement recipe must still use the hq* vocabulary; moving a rule between scopes means creating a new one. Enabled-only changes work on any plan; recipe edits need an upgraded plan. NOT update_automations (review/workflow timeout scalars).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The rule id, from list_automation_rules. |
trigger | object | |
actions | object[] | |
enabled | boolean |
idrequireduuidThe rule id, from list_automation_rules.triggerobjectactionsobject[]enabledboolean{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:automations
update_automationsUpdate workspace automationsWriteChange one or more REVIEW/WORKFLOW automations. Minutes: 0 = off. Guest windows: 0 = off, -1 = forever, else minutes. The notification automations (quiet/batch/catch-up) are read-only here - change those in the app. Returns the full updated automations.
| Name | Type | Details |
|---|---|---|
autoAdvanceVariantReview | boolean | |
reviewRevertTimeout | integer | Minutes; 0 = off. |
autoSubmitForReviewTimeout | integer | Minutes; 0 = off. |
guestRevertWindow | integer | Minutes; 0 = off, -1 = forever. |
guestApprovalRevertWindow | integer | Minutes; 0 = off, -1 = forever. |
autoAdvanceVariantReviewbooleanreviewRevertTimeoutintegerMinutes; 0 = off.autoSubmitForReviewTimeoutintegerMinutes; 0 = off.guestRevertWindowintegerMinutes; 0 = off, -1 = forever.guestApprovalRevertWindowintegerMinutes; 0 = off, -1 = forever.{}Key scope: write:automations
create_workload_blockCreate workload blockWriteCreate a workload block for a team member: a shoot day / location scout ('production'), a vacation or public holiday ('timeOff' - reduces their capacity), or 'other'. Applies to every calendar day in the range, weekends included (a Saturday shoot is the point). minutesPerDay is the daily amount (480 = 8h).
| Name | Type | Details |
|---|---|---|
memberIdrequired | uuid | Team member id (from list_members). |
titlerequired | string | e.g. 'Client shoot day', 'Vacation'. |
startDaterequired | string | YYYY-MM-DD, inclusive. |
endDaterequired | string | YYYY-MM-DD, inclusive, on/after startDate. |
minutesPerDayrequired | integer | Minutes per day (480 = 8h). |
kind | enum | Defaults to production.One of: production, timeOff, other |
taskId | uuid | null | Optional task this block serves (the deliverable a shoot day is for). |
projectId | uuid | null | Optional related project. |
memberIdrequireduuidTeam member id (from list_members).titlerequiredstringe.g. 'Client shoot day', 'Vacation'.startDaterequiredstringYYYY-MM-DD, inclusive.endDaterequiredstringYYYY-MM-DD, inclusive, on/after startDate.minutesPerDayrequiredintegerMinutes per day (480 = 8h).kindenumDefaults to production.One of: production, timeOff, othertaskIduuid | nullOptional task this block serves (the deliverable a shoot day is for).projectIduuid | nullOptional related project.{
"memberId": "00000000-0000-0000-0000-000000000000",
"title": "example",
"startDate": "example",
"endDate": "example",
"minutesPerDay": 1
}Key scope: write:workspace
delete_workload_blockDelete workload blockWriteDeletesDelete a workload block (e.g. a cancelled shoot or removed time off). Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The block's UUID. |
idrequireduuidThe block's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:workspace
get_analyticsGet analyticsReadRead the workspace's analytics in one call: task status counts, monthly financial trend, per-editor performance (approved / in-progress / on-time% / avg revision rounds), revision stats (internal + external, per editor + per brand + monthly trend), production momentum, top editors/clients by revisions, teamWorkload (per-member open task counts by status), and timeTracking (per-member time from the Time Tracking column - totalSeconds tracked + the number of tasks tracked; use this to sum the team's tracked time). Optional from/to (ISO date; on task creation date, except timeTracking which filters on when the time was logged), brandId, memberId filters. Numbers match the in-app Analytics view.
| Name | Type | Details |
|---|---|---|
from | string | ISO date/datetime - tasks created on/after this. |
to | string | ISO date/datetime - tasks created on/before this. |
brandId | uuid | Restrict to one brand/client. |
memberId | uuid | Restrict to tasks involving one team member. |
fromstringISO date/datetime - tasks created on/after this.tostringISO date/datetime - tasks created on/before this.brandIduuidRestrict to one brand/client.memberIduuidRestrict to tasks involving one team member.{}Key scope: read:workspace
get_workloadGet team workloadReadThe capacity-planning grid - USE THIS to answer 'who is available', 'who can take a new project', 'is the team overloaded next week'. Per active team member: per-day and per-week scheduledMinutes vs capacityMinutes (task time estimates spread across the workspace's working days, plus production/time-off blocks), the task spans (startDate → dueDate with each member's share), block spans, and unscheduledTaskIds (assigned but missing dates) + overdueTaskIds. A member has room when scheduledMinutes < capacityMinutes over the weeks in question. Default range: this week → +8 weeks. Elite/Agency/Scale plans only (403 otherwise).
| Name | Type | Details |
|---|---|---|
from | string | Range start, YYYY-MM-DD. Default: this week's start. |
to | string | Range end (inclusive), YYYY-MM-DD. Default: +8 weeks. |
fromstringRange start, YYYY-MM-DD. Default: this week's start.tostringRange end (inclusive), YYYY-MM-DD. Default: +8 weeks.{}Key scope: read:workspace
get_workspaceGet workspaceReadGet the workspace this API key belongs to (id, name, picture, defaultCurrency, template, timezone). timezone is the workspace's IANA zone (null = browser-detected) - use it to interpret the UTC post times from list_posts. All other tools are automatically scoped to this workspace - never ask for a workspace id.
No inputs.
{}Key scope: read:workspace
get_workspace_settingsGet workspace settingsReadRead the workspace's settings (Settings → Workspace + Advanced): permissions (editor/supervisor), workflow (dual deadlines, revision caps), media-review, guest portal, and read-only context like the download policy. Effective values with defaults applied.
No inputs.
{}Key scope: read:workspace
list_membersList team membersReadList the workspace's team members so you can answer 'who is on the team', 'who is available', or 'what is each person's role'. Each member has a role (admin, supervisor, or editor - admin and supervisor manage work; editors do the editing), an optional free-form subRole label (e.g. 'Rough Cut Editor'), and deactivated/pending flags. Use a member's id to recognise task assignees. By default returns only active team members - set includeGuests=true to also include external guest reviewers, includeDeactivated=true to include removed members. Not paginated.
| Name | Type | Details |
|---|---|---|
includeGuests | boolean | Include external guest reviewers (role 'guest'). Defaults to false. |
includeDeactivated | boolean | Include deactivated / removed members. Defaults to false. |
includeGuestsbooleanInclude external guest reviewers (role 'guest'). Defaults to false.includeDeactivatedbooleanInclude deactivated / removed members. Defaults to false.{}Key scope: read:workspace
list_workload_blocksList workload blocksReadList the workspace's workload blocks - member-level date ranges for shoot days, location scouts and time off. 'timeOff' blocks reduce the member's capacity for those days; 'production'/'other' blocks add scheduled minutes (weekends included). Optional from/to narrows to blocks overlapping the range. Returns {data, nextBefore}; when nextBefore is non-null, call again with before=nextBefore for the next page.
| Name | Type | Details |
|---|---|---|
from | string | Only blocks ending on/after this date (YYYY-MM-DD). |
to | string | Only blocks starting on/before this date (YYYY-MM-DD). |
limit | integer | Items per page, 1-100. Defaults to 50. |
before | string | Opaque pagination cursor - pass the previous response's nextBefore value verbatim. |
fromstringOnly blocks ending on/after this date (YYYY-MM-DD).tostringOnly blocks starting on/before this date (YYYY-MM-DD).limitintegerItems per page, 1-100. Defaults to 50.beforestringOpaque pagination cursor - pass the previous response's nextBefore value verbatim.{}Key scope: read:workspace
update_memberUpdate memberWriteUpdate a team member's per-member settings: weeklyCapacityMinutes - their weekly workload capacity in minutes (2400 = 40h; null = follow the workspace default defaultWeeklyCapacityMinutes) - and/or payModel - how they're paid: 'perTask' (per-deliverable pay rows), 'perProject' (a fixed fee per project, set per project via set_project_billing team.feeUpdates), or 'monthly' (salary; monthlySalary required). Pay-model changes are effective-dated (history keeps counting; the new model applies from today). Member ids come from list_members.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The member's UUID. |
weeklyCapacityMinutes | integer | null | Weekly capacity in minutes (2400 = 40h), or null to follow the workspace default. |
payModel | enum | The member's pay model; 'monthly' requires monthlySalary.One of: perTask, perProject, monthly |
monthlySalary | number | Monthly salary amount - required when payModel is 'monthly'. |
idrequireduuidThe member's UUID.weeklyCapacityMinutesinteger | nullWeekly capacity in minutes (2400 = 40h), or null to follow the workspace default.payModelenumThe member's pay model; 'monthly' requires monthlySalary.One of: perTask, perProject, monthlymonthlySalarynumberMonthly salary amount - required when payModel is 'monthly'.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:workspace
update_workload_blockUpdate workload blockWriteUpdate a workload block's title, kind, dates or minutesPerDay (block ids come from list_workload_blocks or get_workload). Provide at least one field.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The block's UUID. |
title | string | |
kind | enum | One of: production, timeOff, other |
startDate | string | YYYY-MM-DD. |
endDate | string | YYYY-MM-DD, on/after startDate. |
minutesPerDay | integer | |
taskId | uuid | null | Link (or null to unlink) the task this block serves. |
idrequireduuidThe block's UUID.titlestringkindenumOne of: production, timeOff, otherstartDatestringYYYY-MM-DD.endDatestringYYYY-MM-DD, on/after startDate.minutesPerDayintegertaskIduuid | nullLink (or null to unlink) the task this block serves.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:workspace
update_workspace_settingsUpdate workspace settingsWriteChange one or more writable workspace settings. Send only the keys you want to change. Plan-gated keys (dualDeadlineMode, defaultRevisionLimit, enableTranscripts, guestAllStagesVisible, guestEarlyStageDocAccess, autoTranscribeUploads - the last one on a narrower gate than the rest) need an upgraded plan to ENABLE - if the call is refused, relay the error message verbatim; it names the exact plan. Never name a plan tier yourself. guestAllStagesVisible ("Show clients every workflow stage", default false) lets clients see tasks in every stage as view-only cards - offer it when a client asks to see planned or in-progress work; guestEarlyStageDocAccess (placeholder | view | comment | edit) sets how far a document in such a stage goes. A brand's own values (update_brand) override both per brand. Read-only settings (downloadPolicy, archiveEnabled, …) are rejected - change those in the app. Returns the full updated settings.
| Name | Type | Details |
|---|---|---|
editorCanCreateTasks | boolean | |
editorCanCreateProjects | boolean | Let editors create projects inside brands they can already access, and delete only the projects they created. Brands and folders stay supervisor-only. Default false. |
editorSeeAllProjectTasks | boolean | |
editorCanReachClientApproval | boolean | |
supervisorCanApprove | boolean | |
supervisorChartAccess | boolean | Supervisors see brand/client financials in charts. |
autoAddSupervisorsToNodes | boolean | |
supervisorCanArchiveBrands | boolean | Let supervisors archive/unarchive the brands they manage. Projects stay admin-only. Default false. |
dualDeadlineMode | boolean | Plan-gated to enable. |
defaultRevisionLimit | integer | null | Plan-gated to set. null = unlimited. |
showFrameNumber | boolean | |
startMuted | boolean | |
autoplayOnOpen | boolean | |
enableTranscripts | boolean | Plan-gated to enable. |
transcriptSidePanel | boolean | Video review layout default: dock the transcript in its own panel left of the player, side by side with the comments. Desktop + team members only. All plans; default false. |
autoTranscribeUploads | boolean | Start the transcript of every NEW uploaded video (task versions and raw files) automatically. On a narrower plan gate than enableTranscripts - enabling it on a plan without it is refused (relay the 403 verbatim), and a stored true does nothing there; only works while enableTranscripts is on; default false. Each transcript uses transcription minutes. |
rawFilesCommentsEnabled | boolean | Task-review-style comments (timecodes, ranges, drawings, unread badges) on raw files in Raw Files & Assets. All plans; default false. |
openTaskOnTitleClick | boolean | What a click on a task's name does for team members: false (default) = rename, true = open the task (rename on double-click). All plans. |
guestCanViewBrief | boolean | Plan-gated to enable (Studio or higher). |
guestCanSchedulePosts | boolean | Let client approvers schedule & publish content they already approved. Posting-plan gated; default false. |
guestAllStagesVisible | boolean | "Show clients every workflow stage": clients see tasks in every stage (to-do, in progress, internal approvals) as view-only cards. Early-stage media is not playable, commentable or approvable - except for a guest the task reaches through an assignment, who keeps its pre-setting rights. Plan-gated to enable; default false. |
guestEarlyStageDocAccess | enum | How far a DOCUMENT in an early stage goes while every stage is visible: placeholder (default) | view | comment | edit. Plan-gated past placeholder.One of: placeholder, view, comment, edit |
editorCanCreateTasksbooleaneditorCanCreateProjectsbooleanLet editors create projects inside brands they can already access, and delete only the projects they created. Brands and folders stay supervisor-only. Default false.editorSeeAllProjectTasksbooleaneditorCanReachClientApprovalbooleansupervisorCanApprovebooleansupervisorChartAccessbooleanSupervisors see brand/client financials in charts.autoAddSupervisorsToNodesbooleansupervisorCanArchiveBrandsbooleanLet supervisors archive/unarchive the brands they manage. Projects stay admin-only. Default false.dualDeadlineModebooleanPlan-gated to enable.defaultRevisionLimitinteger | nullPlan-gated to set. null = unlimited.showFrameNumberbooleanstartMutedbooleanautoplayOnOpenbooleanenableTranscriptsbooleanPlan-gated to enable.transcriptSidePanelbooleanVideo review layout default: dock the transcript in its own panel left of the player, side by side with the comments. Desktop + team members only. All plans; default false.autoTranscribeUploadsbooleanStart the transcript of every NEW uploaded video (task versions and raw files) automatically. On a narrower plan gate than enableTranscripts - enabling it on a plan without it is refused (relay the 403 verbatim), and a stored true does nothing there; only works while enableTranscripts is on; default false. Each transcript uses transcription minutes.rawFilesCommentsEnabledbooleanTask-review-style comments (timecodes, ranges, drawings, unread badges) on raw files in Raw Files & Assets. All plans; default false.openTaskOnTitleClickbooleanWhat a click on a task's name does for team members: false (default) = rename, true = open the task (rename on double-click). All plans.guestCanViewBriefbooleanPlan-gated to enable (Studio or higher).guestCanSchedulePostsbooleanLet client approvers schedule & publish content they already approved. Posting-plan gated; default false.guestAllStagesVisibleboolean"Show clients every workflow stage": clients see tasks in every stage (to-do, in progress, internal approvals) as view-only cards. Early-stage media is not playable, commentable or approvable - except for a guest the task reaches through an assignment, who keeps its pre-setting rights. Plan-gated to enable; default false.guestEarlyStageDocAccessenumHow far a DOCUMENT in an early stage goes while every stage is visible: placeholder (default) | view | comment | edit. Plan-gated past placeholder.One of: placeholder, view, comment, edit{}Key scope: write:workspace
create_hq_boardCreate HQ boardWriteCreate an internal HQ board, optionally seeded from a template preset (companyOps, salesPipeline, sops, assetLibrary, onboarding, blank). A template seeds its stages and custom fields with ENGLISH names (plain user-editable strings afterward); without a template the board gets a single 'To do' stage. Active boards are capped per plan (409 at the cap; archived boards don't count). Returns the full board with its stages/fields - use those stage ids with create_hq_item.
| Name | Type | Details |
|---|---|---|
namerequired | string | |
description | string | null | |
icon | string | null | Emoji (e.g. 💰) or a curated icon name (e.g. "DollarSign"). Defaults to the template's icon. |
color | string | null | Palette token (e.g. blue, emerald). Invalid tokens are dropped. |
kind | enum | Card-styling flavor. Defaults to the template's kind, else tasks.One of: tasks, docs, crm, assets, checklist |
access | enum | open = every team member; restricted = explicit members + admins. Defaults to open.One of: open, restricted |
template | enum | Template preset to seed stages/fields from (English names).One of: companyOps, salesPipeline, sops, assetLibrary, onboarding, blank |
namerequiredstringdescriptionstring | nulliconstring | nullEmoji (e.g. 💰) or a curated icon name (e.g. "DollarSign"). Defaults to the template's icon.colorstring | nullPalette token (e.g. blue, emerald). Invalid tokens are dropped.kindenumCard-styling flavor. Defaults to the template's kind, else tasks.One of: tasks, docs, crm, assets, checklistaccessenumopen = every team member; restricted = explicit members + admins. Defaults to open.One of: open, restrictedtemplateenumTemplate preset to seed stages/fields from (English names).One of: companyOps, salesPipeline, sops, assetLibrary, onboarding, blank{
"name": "example"
}Key scope: write:hq
create_hq_fieldCreate HQ fieldWriteAdd a custom field to an HQ board - it becomes a list-view column and an item-page detail. Select fields may seed their options; currency fields take an ISO 4217 currency. Capped per board (409). Set values afterward with set_hq_item_field_values on this board's items (the new field id belongs to this board only). Returns the field (with option ids).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
kindrequired | enum | One of: text, number, currency, date, select, link, email, phone, user, timeTracker |
namerequired | string | |
currency | string | Currency fields only, e.g. USD. |
options | object[] | Select fields only - the options in order. |
idrequireduuidThe HQ board's UUID.kindrequiredenumOne of: text, number, currency, date, select, link, email, phone, user, timeTrackernamerequiredstringcurrencystringCurrency fields only, e.g. USD.optionsobject[]Select fields only - the options in order.{
"id": "00000000-0000-0000-0000-000000000000",
"kind": "text",
"name": "example"
}Key scope: write:hq
create_hq_field_optionAdd HQ select optionWriteAdd an option to an HQ select field (appended last). Capped per field (409). Returns the option.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
fieldIdrequired | uuid | A select-kind field of the board. |
labelrequired | string | |
color | string | null | Palette token (e.g. blue, emerald, rose). Invalid tokens are dropped; null clears. |
idrequireduuidThe HQ board's UUID.fieldIdrequireduuidA select-kind field of the board.labelrequiredstringcolorstring | nullPalette token (e.g. blue, emerald, rose). Invalid tokens are dropped; null clears.{
"id": "00000000-0000-0000-0000-000000000000",
"fieldId": "00000000-0000-0000-0000-000000000000",
"label": "example"
}Key scope: write:hq
create_hq_itemCreate HQ itemWriteCreate an item (card) in one of an HQ board's stages - it is appended at the bottom of that column. Get the stage ids from get_hq_board first. Pass contentHtml to write the item's document in the SAME call (no follow-up write_hq_doc needed). Assignees, due date, and field values are set afterward with update_hq_item / set_hq_item_field_values.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
titlerequired | string | |
stageIdrequired | uuid | The stage (column) to create the item in - from get_hq_board. |
icon | string | null | Emoji shown on the card. |
contentHtml | string | Optional document body (HTML) to write on the new item in the same call. The response then carries docSaved - false means the item was created but the document was not written; retry the body with write_hq_doc rather than creating a second item. |
idrequireduuidThe HQ board's UUID.titlerequiredstringstageIdrequireduuidThe stage (column) to create the item in - from get_hq_board.iconstring | nullEmoji shown on the card.contentHtmlstringOptional document body (HTML) to write on the new item in the same call. The response then carries docSaved - false means the item was created but the document was not written; retry the body with write_hq_doc rather than creating a second item.{
"id": "00000000-0000-0000-0000-000000000000",
"title": "example",
"stageId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
create_hq_item_commentComment on an HQ itemWritePost a comment on an HQ item's thread, authored by the API key's creator. Silent - no one is notified. Team-internal only. Returns the created comment.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
textrequired | string | The comment text. |
idrequireduuidThe HQ item's UUID.textrequiredstringThe comment text.{
"id": "00000000-0000-0000-0000-000000000000",
"text": "example"
}Key scope: write:hq
create_hq_stageCreate HQ stageWriteAdd a stage (a kanban column / list group) at the end of an HQ board. outcome marks Done/Won/Lost columns (cards there stop reading as late). Capped per board (409 at the cap). Returns the stage.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
namerequired | string | |
color | string | null | Palette token (e.g. blue, emerald, rose). Invalid tokens are dropped; null clears. |
outcome | enum | Defaults to none.One of: none, done, won, lost |
idrequireduuidThe HQ board's UUID.namerequiredstringcolorstring | nullPalette token (e.g. blue, emerald, rose). Invalid tokens are dropped; null clears.outcomeenumDefaults to none.One of: none, done, won, lost{
"id": "00000000-0000-0000-0000-000000000000",
"name": "example"
}Key scope: write:hq
delete_hq_boardDelete HQ boardWriteDeletesPERMANENTLY delete an HQ board and everything on it - items, stages, fields, values, comments, doc versions. This cannot be undone (prefer update_hq_board archived=true for reversible removal). Refused with 409 while the board still holds files. CONFIRM WITH THE USER FOR EACH BOARD, naming it; NEVER loop this tool over a list of boards. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
idrequireduuidThe HQ board's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
delete_hq_fieldDelete HQ fieldWriteDeletesDelete a custom field from an HQ board. Every item's value in that field is deleted with it - this cannot be undone. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
fieldIdrequired | uuid |
idrequireduuidThe HQ board's UUID.fieldIdrequireduuid{
"id": "00000000-0000-0000-0000-000000000000",
"fieldId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
delete_hq_field_optionDelete HQ select optionWriteDeletesDelete an option of an HQ select field. Items that held it are left empty in that field. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
fieldIdrequired | uuid | |
optionIdrequired | uuid |
idrequireduuidThe HQ board's UUID.fieldIdrequireduuidoptionIdrequireduuid{
"id": "00000000-0000-0000-0000-000000000000",
"fieldId": "00000000-0000-0000-0000-000000000000",
"optionId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
delete_hq_fileDelete an HQ fileWriteDeletesRemove an HQ file (an item attachment or a board-shelf file) - it moves to the board's trash, the same as deleting it in the app. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ file's UUID - from list_hq_item_files. |
idrequireduuidThe HQ file's UUID - from list_hq_item_files.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
delete_hq_itemDelete HQ itemWriteDeletesPERMANENTLY delete an HQ item - its doc body, field values, and comments go with it. There is no HQ trash; this cannot be undone (prefer update_hq_item archived=true for reversible removal). CONFIRM WITH THE USER FOR EACH ITEM, naming it; NEVER loop this tool over a list of items - to clear many, archive them with update_hq_item archived=true, or delete their whole board with delete_hq_board. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
idrequireduuidThe HQ item's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
delete_hq_item_commentDelete an HQ item commentWriteDeletesDelete a comment (and its replies) from an HQ item's thread. Cannot be undone. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
commentIdrequired | uuid |
idrequireduuidThe HQ item's UUID.commentIdrequireduuid{
"id": "00000000-0000-0000-0000-000000000000",
"commentId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
delete_hq_stageDelete HQ stageWriteDeletesDelete an HQ board's stage. If it still holds items, targetStageId (another stage of the same board) is REQUIRED and they move there. A board's last stage can never be deleted (409). Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
stageIdrequired | uuid | |
targetStageId | uuid | Where the stage's items move. |
idrequireduuidThe HQ board's UUID.stageIdrequireduuidtargetStageIduuidWhere the stage's items move.{
"id": "00000000-0000-0000-0000-000000000000",
"stageId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
edit_hq_item_docEdit part of an HQ item's documentWriteChange PART of an HQ item's document body (the Document tab - SOPs, notes) without resending the whole thing - the right tool for any small change. Each edit names where it goes: append / prepend (end / start of the document); replaceSection (a section = a top-level heading plus everything up to the next heading of the same or a higher level - html replaces the heading TOO, so start it with the heading to keep it, or send "" to delete the section); appendToSection (adds html at the end of that section - e.g. a new paragraph under the "Editing steps" heading); replaceText (find = an exact piece of the stored HTML as get_hq_item returns it in contentText, which must occur exactly once; html "" deletes it). Headings match on their visible text, case and spacing ignored. Edits apply in order and all or nothing, to whatever the document holds when the call lands - so a teammate editing another part of the document in the app meanwhile does not make this fail and is not overwritten. Everything outside your edits, including comment highlights, is kept exactly as it is. A 422 means an anchor was not found or matched more than once (code anchorNotFound / anchorAmbiguous, index = which edit) and nothing was written: read contentText with get_hq_item and pick an exact anchor. The body before the edit is saved as a version in the item's version history. Use write_hq_item_doc only to rewrite the whole document. Silent - no one is notified.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's full UUID - all 36 characters, exactly as list_hq_items, get_hq_item or create_hq_item returned it. Never shorten, abbreviate or pad it: a shortened id is a 404, and nothing is written. |
editsrequired | object[] | 1–50 edits, applied in order. |
baseRev | integer | null | Optional and rarely needed. Only apply if the document is still at this docRev (returned by a previous write_hq_item_doc / edit_hq_item_doc) - a 409 otherwise. Omit it to apply the edits to the current content. |
idrequireduuidThe HQ item's full UUID - all 36 characters, exactly as list_hq_items, get_hq_item or create_hq_item returned it. Never shorten, abbreviate or pad it: a shortened id is a 404, and nothing is written.editsrequiredobject[]1–50 edits, applied in order.baseRevinteger | nullOptional and rarely needed. Only apply if the document is still at this docRev (returned by a previous write_hq_item_doc / edit_hq_item_doc) - a 409 otherwise. Omit it to apply the edits to the current content.{
"id": "00000000-0000-0000-0000-000000000000",
"edits": [
{
"op": "append",
"html": "example"
}
]
}Key scope: write:hq
get_hq_boardGet HQ boardReadGet one HQ board with its stages (the kanban columns - use their ids for create_hq_item / update_hq_item), its custom field definitions (+select options - use field/option ids with set_hq_item_field_values on THIS board's items only; every board has its own field ids, and an option id is valid only for the field it is listed under), and its members.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
idrequireduuidThe HQ board's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:hq
get_hq_itemGet HQ itemReadGet one HQ item: boardId, title, stage, assignees, due date, its custom-field values, AND contentText - the item's doc body as HTML (SOPs / notes live there; null when the item has no doc). boardId is the board whose field ids this item accepts - pass it to get_hq_board before set_hq_item_field_values.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's full UUID - all 36 characters, exactly as list_hq_items, get_hq_item or create_hq_item returned it. Never shorten, abbreviate or pad it: a shortened id is a 404, and nothing is written. |
idrequireduuidThe HQ item's full UUID - all 36 characters, exactly as list_hq_items, get_hq_item or create_hq_item returned it. Never shorten, abbreviate or pad it: a shortened id is a 404, and nothing is written.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:hq
list_hq_boardsList HQ boardsReadList the workspace's HQ boards - the team's INTERNAL boards (ops tasks, SOP docs, sales pipeline, asset shelves); clients and guests never get a seat. Each board: id, name, description, icon, color, kind (tasks|docs|crm|assets|checklist), access (open|restricted - this workspace-level key sees restricted boards too), archivedAt, itemCount, memberCount. Not paginated (boards are capped per plan). Use get_hq_board to learn a board's stages before creating or moving items.
No inputs.
{}Key scope: read:hq
list_hq_item_commentsList HQ item commentsReadRead an HQ item's comment thread, oldest first. Each comment carries its author { id, name }. HQ comments are team-internal - clients and guests can never see them. Not paginated.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
idrequireduuidThe HQ item's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:hq
list_hq_item_filesList HQ item filesReadList an HQ item's attached files (its Files tab), newest first - id, name, size, mimeType, uploadedBy, createdAt, and a downloadUrl valid for 7 days.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
idrequireduuidThe HQ item's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:hq
list_hq_itemsList HQ board itemsReadList a board's active (non-archived) items, newest first, each with its custom-field values. Filter by stageId (a stage of the board) or assigneeId (a member id from list_members). The doc body is not included - read one item with get_hq_item for that. Returns {data, nextBefore}; when nextBefore is non-null, call again with before=nextBefore for the next page.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
limit | integer | Items per page, 1-100. Defaults to 50. |
before | string | Opaque pagination cursor - pass the previous response's nextBefore value verbatim. |
stageId | uuid | Restrict to one stage (column) of the board. |
assigneeId | uuid | Restrict to items assigned to this team member. |
idrequireduuidThe HQ board's UUID.limitintegerItems per page, 1-100. Defaults to 50.beforestringOpaque pagination cursor - pass the previous response's nextBefore value verbatim.stageIduuidRestrict to one stage (column) of the board.assigneeIduuidRestrict to items assigned to this team member.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:hq
reorder_hq_boardsReorder HQ boardsWriteSet the order HQ boards are listed in (the HQ home and the in-board boards panel). Pass EVERY non-trashed board of the workspace exactly once (archived included) - get them from list_hq_boards.
| Name | Type | Details |
|---|---|---|
boardIdsrequired | uuid[] | All board ids, in the new order. |
boardIdsrequireduuid[]All board ids, in the new order.{
"boardIds": [
"00000000-0000-0000-0000-000000000000"
]
}Key scope: write:hq
reorder_hq_field_optionsReorder HQ select optionsWriteSet the order of an HQ select field's options (pickers and column sorts follow it). Pass EVERY option id of the field exactly once. Returns the field.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
fieldIdrequired | uuid | |
optionIdsrequired | uuid[] |
idrequireduuidThe HQ board's UUID.fieldIdrequireduuidoptionIdsrequireduuid[]{
"id": "00000000-0000-0000-0000-000000000000",
"fieldId": "00000000-0000-0000-0000-000000000000",
"optionIds": [
"00000000-0000-0000-0000-000000000000"
]
}Key scope: write:hq
reorder_hq_stagesReorder HQ stagesWriteSet the order of an HQ board's stages (kanban columns). Pass EVERY stage id of the board exactly once, in the new order. Returns the board.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
stageIdsrequired | uuid[] | All stage ids, in the new order. |
idrequireduuidThe HQ board's UUID.stageIdsrequireduuid[]All stage ids, in the new order.{
"id": "00000000-0000-0000-0000-000000000000",
"stageIds": [
"00000000-0000-0000-0000-000000000000"
]
}Key scope: write:hq
set_hq_item_field_valuesSet HQ item field valuesWriteBulk-set custom-field values on an HQ item. Get the board's field definitions (ids + kinds + select options) from get_hq_board; each entry's kind must match its field's kind. Field ids belong to ONE board: take them from get_hq_board for the item's OWN boardId (get_hq_item and list_hq_items return boardId on every item) right before you write. A field with the same name on another board - two boards from the same template, a duplicated board, a board the item was moved off - has a DIFFERENT id and is refused with 400. A fieldId is never a stage id or a select option id. Value by kind: text/email/phone → a string; number/currency → a number; date → 'YYYY-MM-DD'; select → the `id` of one of THAT field's own `options` in get_hq_board (never another field's option - not even a same-named option of another field or board - and never the option's label; a wrong one is a 400 that lists the field's valid label = optionId pairs - retry with one of those); link → an http(s) URL; user → an ACTIVE team-member id; timeTracker → the TOTAL logged seconds (a non-negative number; the in-app play/pause timer state is untouched). Pass value null to clear a cell. All entries are validated before anything is written. Returns the item's full value set.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
valuesrequired | object[] | The cells to set - one entry per field. |
idrequireduuidThe HQ item's UUID.valuesrequiredobject[]The cells to set - one entry per field.{
"id": "00000000-0000-0000-0000-000000000000",
"values": [
{
"fieldId": "00000000-0000-0000-0000-000000000000",
"kind": "text",
"value": "example"
}
]
}Key scope: write:hq
update_hq_boardUpdate HQ boardWriteUpdate an HQ board's allow-listed fields - name, description, icon, color, access, archived, columnOrder, viewOrder. Provide at least one. archived=true archives the board (reversible; archived boards stop counting toward the plan cap); archived=false restores it. columnOrder reorders the board's properties (list-view columns AND the item page's details) - built-ins stage, assignees, dueDate plus field ids; keys you omit fall in behind. viewOrder reorders the Board|List|Files toggle - its FIRST view is what the board opens in for everyone. Returns the full updated board (its current columnOrder and viewOrder included).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
name | string | |
description | string | null | Pass null to clear. |
icon | string | null | |
color | string | null | |
access | enum | One of: open, restricted |
archived | boolean | |
columnOrder | string[] | Property order: 'stage', 'assignees', 'dueDate' and this board's field ids, each once. |
viewOrder | enum[] | Board|List|Files toggle order - the first entry is the board's default view.One of: board, list, files |
idrequireduuidThe HQ board's UUID.namestringdescriptionstring | nullPass null to clear.iconstring | nullcolorstring | nullaccessenumOne of: open, restrictedarchivedbooleancolumnOrderstring[]Property order: 'stage', 'assignees', 'dueDate' and this board's field ids, each once.viewOrderenum[]Board|List|Files toggle order - the first entry is the board's default view.One of: board, list, files{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
update_hq_fieldUpdate HQ fieldWriteRename an HQ board's custom field (its column title) and/or change a currency field's currency.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
fieldIdrequired | uuid | |
name | string | |
currency | string |
idrequireduuidThe HQ board's UUID.fieldIdrequireduuidnamestringcurrencystring{
"id": "00000000-0000-0000-0000-000000000000",
"fieldId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
update_hq_field_optionUpdate HQ select optionWriteRename or recolor an option of an HQ select field.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
fieldIdrequired | uuid | |
optionIdrequired | uuid | |
label | string | |
color | string | null | Palette token (e.g. blue, emerald, rose). Invalid tokens are dropped; null clears. |
idrequireduuidThe HQ board's UUID.fieldIdrequireduuidoptionIdrequireduuidlabelstringcolorstring | nullPalette token (e.g. blue, emerald, rose). Invalid tokens are dropped; null clears.{
"id": "00000000-0000-0000-0000-000000000000",
"fieldId": "00000000-0000-0000-0000-000000000000",
"optionId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
update_hq_itemUpdate HQ itemWriteUpdate an HQ item's allow-listed fields - title, icon, stageId (move it to another stage of the SAME board), assigneeIds, dueDate (YYYY-MM-DD or null), sortOrder, archived. Provide at least one. assigneeIds is a FULL REPLACE: send the complete list you want, [] to clear; every id must be an ACTIVE team member (member ids from list_members - guests can never hold HQ work).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's full UUID - all 36 characters, exactly as list_hq_items, get_hq_item or create_hq_item returned it. Never shorten, abbreviate or pad it: a shortened id is a 404, and nothing is written. |
title | string | |
icon | string | null | |
stageId | uuid | A stage of the item's board - from get_hq_board. |
assigneeIds | uuid[] | FULL REPLACE of the item's assignees - active team-member ids; [] clears all. |
dueDate | string | null | YYYY-MM-DD or null to clear. |
sortOrder | number | Manual position within the stage column. |
archived | boolean |
idrequireduuidThe HQ item's full UUID - all 36 characters, exactly as list_hq_items, get_hq_item or create_hq_item returned it. Never shorten, abbreviate or pad it: a shortened id is a 404, and nothing is written.titlestringiconstring | nullstageIduuidA stage of the item's board - from get_hq_board.assigneeIdsuuid[]FULL REPLACE of the item's assignees - active team-member ids; [] clears all.dueDatestring | nullYYYY-MM-DD or null to clear.sortOrdernumberManual position within the stage column.archivedboolean{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
update_hq_item_commentEdit an HQ item commentWriteEdit the text of a comment on an HQ item. Only comments authored by the API key's creator can be edited (403 otherwise). Returns the comment.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
commentIdrequired | uuid | |
textrequired | string |
idrequireduuidThe HQ item's UUID.commentIdrequireduuidtextrequiredstring{
"id": "00000000-0000-0000-0000-000000000000",
"commentId": "00000000-0000-0000-0000-000000000000",
"text": "example"
}Key scope: write:hq
update_hq_stageUpdate HQ stageWriteRename, recolor, or change the outcome (none|done|won|lost) of an HQ board's stage.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ board's UUID. |
stageIdrequired | uuid | The stage's UUID - from get_hq_board. |
name | string | |
color | string | null | Palette token (e.g. blue, emerald, rose). Invalid tokens are dropped; null clears. |
outcome | enum | One of: none, done, won, lost |
idrequireduuidThe HQ board's UUID.stageIdrequireduuidThe stage's UUID - from get_hq_board.namestringcolorstring | nullPalette token (e.g. blue, emerald, rose). Invalid tokens are dropped; null clears.outcomeenumOne of: none, done, won, lost{
"id": "00000000-0000-0000-0000-000000000000",
"stageId": "00000000-0000-0000-0000-000000000000"
}Key scope: write:hq
upload_hq_item_file_from_urlAttach a file to an HQ itemWriteAttach a file to an HQ item by URL: the server downloads a PUBLIC http(s) URL and stores it on the item's Files tab, like a file dropped there in the app (the item is marked changed; 'file attached' board automations run). Counts toward workspace storage. Returns the file with a downloadUrl.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
urlrequired | url | A public http(s) URL to the file. |
fileName | string | Name to store it under; inferred when omitted. |
idrequireduuidThe HQ item's UUID.urlrequiredurlA public http(s) URL to the file.fileNamestringName to store it under; inferred when omitted.{
"id": "00000000-0000-0000-0000-000000000000",
"url": "https://example.com/file.mp4"
}Key scope: write:hq
write_hq_item_docWrite an HQ item's documentWriteREPLACE an HQ item's document body (the Notion-style Document tab) with new HTML. The previous body is saved as a doc version first, so it stays recoverable from the item's version history. Full replace - read the item first (get_hq_item returns contentText) and carry over anything that should stay. To change only PART of the document (add a paragraph under a heading, fix one passage, add a step at the end) use edit_hq_item_doc instead: it sends just the change and leaves the rest of the document, and a teammate's live edits, untouched. To put an image or a file in the document, call upload_doc_attachment_from_url first and paste the embedHtml it returns into contentHtml. Silent - no one is notified.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The HQ item's UUID. |
contentHtmlrequired | string | The FULL new document body as HTML (<p>, <h1-h3>, <ul>/<ol>, <strong>, <em>, <a>, plus any embedHtml returned by upload_doc_attachment_from_url for images and files). |
idrequireduuidThe HQ item's UUID.contentHtmlrequiredstringThe FULL new document body as HTML (<p>, <h1-h3>, <ul>/<ol>, <strong>, <em>, <a>, plus any embedHtml returned by upload_doc_attachment_from_url for images and files).{
"id": "00000000-0000-0000-0000-000000000000",
"contentHtml": "example"
}Key scope: write:hq
create_webhookCreate webhookWriteRegister a public https:// endpoint to receive events (task.completed, task.created, task.updated, task.trashed, project.trashed, custom.column_changed, post.status_changed). The response includes the plaintext signing secret ONCE - relay it to the user immediately; it is never shown again. Max 20 subscriptions per workspace (409 beyond that).
| Name | Type | Details |
|---|---|---|
urlrequired | string | Public https:// endpoint; loopback and private ranges are rejected. |
eventsrequired | enum[] | Event types to subscribe to.One of: task.completed, post.status_changed, task.created, task.updated, task.trashed, project.trashed, custom.column_changed, payment.recorded, payment.voided, task.status_changed, comment.created, version.uploaded, file.uploaded, project.created, post.published, post.failed, intake.submitted, share.opened, member.invited, member.joined, automation.triggered |
urlrequiredstringPublic https:// endpoint; loopback and private ranges are rejected.eventsrequiredenum[]Event types to subscribe to.One of: task.completed, post.status_changed, task.created, task.updated, task.trashed, project.trashed, custom.column_changed, payment.recorded, payment.voided, task.status_changed, comment.created, version.uploaded, file.uploaded, project.created, post.published, post.failed, intake.submitted, share.opened, member.invited, member.joined, automation.triggered{
"url": "https://hooks.example.com/timeliner",
"events": [
"task.completed"
]
}Key scope: write:webhooks
delete_webhookDelete webhookWriteDeletesPermanently delete a webhook subscription. Deliveries stop immediately; this cannot be undone. Deletes share one budget across the whole API: about 20 per minute per key, and daily ceilings per key and per workspace - over budget returns 429 with a Retry-After header. A 429 means STOP and tell the user; never wait-and-retry your way through a batch.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The webhook subscription's UUID. |
idrequireduuidThe webhook subscription's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:webhooks
get_webhookGet webhookReadGet one webhook subscription (without its secret).
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The webhook subscription's UUID. |
idrequireduuidThe webhook subscription's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:webhooks
list_webhook_deliveriesList webhook deliveriesReadThe most recent deliveries attempted for a webhook subscription, newest first: event type, status (pending / delivered / failed / dead), attempt count, last HTTP status and error, next retry time. The first place to look when a partner says an event never arrived.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The webhook subscription's UUID. |
limit | integer | Rows to return, 1-200 (default 50). |
idrequireduuidThe webhook subscription's UUID.limitintegerRows to return, 1-200 (default 50).{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: read:webhooks
list_webhooksList webhooksReadList every webhook subscription in the workspace (not paginated). Signing secrets are never returned - only secretPrefix.
No inputs.
{}Key scope: read:webhooks
rotate_webhook_secretRotate webhook secretWriteDeletesGenerate a new signing secret for the subscription and return it ONCE. The previous secret stops verifying immediately - make sure the receiving endpoint is updated. Relay the new secret to the user right away; it is never shown again.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The webhook subscription's UUID. |
idrequireduuidThe webhook subscription's UUID.{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:webhooks
send_test_webhookSend a test webhook eventWritePOST one signed sample delivery to a webhook subscription right now and report the endpoint's answer (HTTP status, latency, error). The envelope carries test: true and a representative sample payload. eventType must be one the subscription is subscribed to; omitted = its first event. Not retried and never counts toward auto-disable - use it to verify a partner endpoint before real events flow.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The webhook subscription's UUID. |
eventType | enum | Which subscribed event to sample.One of: task.completed, post.status_changed, task.created, task.updated, task.trashed, project.trashed, custom.column_changed, payment.recorded, payment.voided, task.status_changed, comment.created, version.uploaded, file.uploaded, project.created, post.published, post.failed, intake.submitted, share.opened, member.invited, member.joined, automation.triggered |
idrequireduuidThe webhook subscription's UUID.eventTypeenumWhich subscribed event to sample.One of: task.completed, post.status_changed, task.created, task.updated, task.trashed, project.trashed, custom.column_changed, payment.recorded, payment.voided, task.status_changed, comment.created, version.uploaded, file.uploaded, project.created, post.published, post.failed, intake.submitted, share.opened, member.invited, member.joined, automation.triggered{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:webhooks
update_webhookUpdate webhookWriteChange a webhook subscription's url, events, or active flag - provide at least one. Set active=false to pause deliveries without deleting the subscription.
| Name | Type | Details |
|---|---|---|
idrequired | uuid | The webhook subscription's UUID. |
url | string | New public https:// endpoint. |
events | enum[] | One of: task.completed, post.status_changed, task.created, task.updated, task.trashed, project.trashed, custom.column_changed, payment.recorded, payment.voided, task.status_changed, comment.created, version.uploaded, file.uploaded, project.created, post.published, post.failed, intake.submitted, share.opened, member.invited, member.joined, automation.triggered |
active | boolean |
idrequireduuidThe webhook subscription's UUID.urlstringNew public https:// endpoint.eventsenum[]One of: task.completed, post.status_changed, task.created, task.updated, task.trashed, project.trashed, custom.column_changed, payment.recorded, payment.voided, task.status_changed, comment.created, version.uploaded, file.uploaded, project.created, post.published, post.failed, intake.submitted, share.opened, member.invited, member.joined, automation.triggeredactiveboolean{
"id": "00000000-0000-0000-0000-000000000000"
}Key scope: write:webhooks
get_api_key_infoGet this API key's permissionsReadIntrospect the key this session uses: its name, the scopes it holds (read:<resource> / write:<resource> over tasks, projects, files, posts, chat, finance, automations, workspace, hq, webhooks), its expiry, and its per-minute rate limit. Call this when a tool returned 403 insufficient_scope, to tell the user exactly which scope the key is missing before retrying.
No inputs.
{}Key scope: any key