Content board
The content board carries one card per video, through seven columns:
IDEA → SCRIPT → VALIDATED → TO_SHOOT → TO_EDIT → TO_SCHEDULE → PUBLISHED
The card does not disappear when the video goes out: tied to the posts
scheduled from it, it moves to PUBLISHED by itself when the first of them
goes live, then carries their numbers. These endpoints read and write the same cards as the
board in the app, so a card created from a voice note, a Notion page or a DM
shows up there straight away.
These endpoints do not exist in the original project.
Endpoints
Section titled “Endpoints”GET /contentsGET /contents/performanceGET /contents/{id}POST /contentsPOST /contents/upsertPUT /contents/{id}PUT /contents/{id}/movePUT /contents/{id}/postDELETE /contents/{id}A card of another organisation, or one that was deleted, answers 404.
Create a card
Section titled “Create a card”POST /contents
| Field | Type | Required | Description |
|---|---|---|---|
title |
string |
Yes | Short title of the video |
externalId |
string |
No | Your own id for the card (a script file path, a build id). Unique per organisation: a second card with the same one answers 409 |
stage |
string |
No | The column to start in. Default IDEA |
hook |
string |
No | The spoken hook, the first seconds that stop the scroll. Markdown |
hookText |
string |
No | The text shown on screen over the first seconds |
script |
string |
No | Markdown: the board renders it (tables, bold, lists) |
notes |
string |
No | |
tags |
string[] |
No | Free labels, e.g. a series |
resourceStatus |
string |
No | The resource the video gives away (a link, a guide, a masterclass): READY, or TO_CREATE. Omitted: no resource. See The resource of a video |
resourceUrl |
string |
No | Where the resource lives, or will live |
resourceBrief |
string |
No | What the resource holds, or the prompt to make it. Markdown |
integrationIds |
string[] |
No | The channels the video is meant for |
plannedAt |
string |
No | ISO date the video should go out |
parentId |
string |
No | The card this one is repurposed from |
The card lands at the bottom of its column. The answer is 201 with the card.
{ "title": "Three mistakes when moving to Malta", "hook": "Nobody told me about the third one.", "integrationIds": ["cm4ean69r0003w8w1cdomox9n"]}Create or update by your own id
Section titled “Create or update by your own id”POST /contents/upsert takes the same fields as creation, with externalId
required. The card holding that externalId is updated with the fields sent;
without one, a card is created. Re-running an import therefore never
duplicates, and you do not need to keep a table of your ids against ours.
On an existing card, stage is ignored: someone may have moved the card
since, and the import should not move it back. Move it with
PUT /contents/{id}/move.
The answer is { "created": true | false, "content": { … } }.
A deleted card lets go of its externalId: the next upsert creates a new card.
Update a card
Section titled “Update a card”PUT /contents/{id} takes the same fields as creation except stage and
parentId, plus videoMediaId and videoPath (the final video, from the
media library). Only the fields sent
change, and null clears one.
Move a card
Section titled “Move a card”PUT /contents/{id}/move
| Field | Type | Required | Description |
|---|---|---|---|
stage |
string |
Yes | The target column |
position |
number |
No | Rank inside the column (fractional, ascending). Omitted, the card goes to the bottom |
The resource of a video
Section titled “The resource of a video”A video that promises a link (the resources it mentions, a guide, a
masterclass) must not go out before the link leads somewhere. A card whose
resourceStatus is TO_CREATE therefore cannot move to TO_SCHEDULE or
PUBLISHED: the move, or a creation straight into one of these columns,
answers 409. Set resourceStatus to READY once the resource exists.
The card still moves by itself when one of its posts goes live (see below): that is a fact, not a choice.
Tie a card to its posts
Section titled “Tie a card to its posts”PUT /contents/{id}/post with { "postGroup": "…" }: the group of a post
created with POST /posts. A card holds
several posts, for example a TikTok draft sent now and an Instagram post
scheduled for tomorrow: each call adds one to the card’s postGroups, and
sending the same group twice changes nothing. A card that is still behind
TO_SCHEDULE jumps there, and it moves to PUBLISHED when the first channel
of any of its posts goes live (at once, if one already has).
A TikTok post sent to the app inbox (SENT_UNCONFIRMED) does not count as
live: the video still waits for someone to publish it in TikTok.
List the cards
Section titled “List the cards”GET /contents returns every card, in board order. On top of its fields, each
card has childrenCount (the cards repurposed from it) and posts: one entry
per channel of each post scheduled from it, with its state, publishDate and
releaseURL. GET /contents/{id} returns a single card, in the same shape.
Query parameters, all optional and combined:
| Parameter | Description |
|---|---|
stage |
Only this column |
tag |
Only the cards carrying this tag |
externalId |
The card holding this id |
fields |
Comma-separated fields to return, e.g. id,title,stage. The id always comes back; an unknown field answers 400. Omitted, every field comes back, scripts included |
GET /contents?stage=SCRIPT&fields=id,title,externalIdThe numbers
Section titled “The numbers”GET /contents/performance returns an object keyed by card id, for the
published cards tied to a post:
| Field | Description |
|---|---|
views |
Sum across channels. null when no channel is collected, never a 0 nobody measured |
engagementRate |
Interactions / views, in % |
skipRate |
Instagram only, weighted by views |
followersGained |
What the accounts gained in the 48 hours after publication |
leads, emails |
People who went through an automation posed on the post, and the addresses collected |
channels |
The same numbers per channel |
The counters come from the collected history, read every 30 minutes: a channel whose collection is off has nulls.