A post from start to finish
The link is a Model Context Protocol server. Your assistant signs in as you, once, on the Spica Post login page; there are no keys to copy. Every post then follows the same six calls.
Find the accounts
Each connected account has an id. Drafts are written for these ids, and they stay the same until an account is disconnected.
accounts_list {}
Upload the picture
Skip this for a text post. The answer carries the media id to attach.
media_upload {
"name": "saturday-hours.jpg",
"mimeType": "image/jpeg",
"base64": "/9j/4AAQSk…",
"alt": "The shop front with the new Saturday hours on the door"
}
Write the draft
One entry per account, each with its own words. Nothing is sent yet. The answer carries the draft id and its version.
drafts_create {
"title": "Saturday hours",
"variants": [
{
"accountId": "acc_instagram",
"text": "We’re open Saturdays now, 9 to 2. Come say hi.",
"mediaIds": [
"med_1"
]
},
{
"accountId": "acc_facebook",
"text": "New: we’re open Saturdays from 9 to 2, starting this week.",
"mediaIds": [
"med_1"
]
}
]
}
Check it
Answers whether every network will take it and, if not, what to fix in plain words.
drafts_validate {
"id": "draft_1",
"version": 1
}
Show it, then schedule it
Only after the person said yes to this post and this time. The answer is one delivery per account, waiting in line.
schedule {
"id": "draft_1",
"version": 1,
"idempotencyKey": "0b0f6a7e-3a52-4d0c-9a3e-6f1f0c2d9b11",
"scheduledAt": "2026-11-06T14:00:00Z"
}
Read the outcome
Once the time has passed, the delivery says whether it was posted and gives the link to the live post.
jobs_get {
"id": "job_1"
}
Rules that keep posting safe
- Nothing goes out without a yes. The assistant shows each draft and waits for approval before it publishes or schedules.
- A post is never sent twice. Every publish or schedule call carries a key the assistant makes up. Repeating the call with the same key answers the same deliveries instead of posting again.
- What was approved is what is posted. A call names the draft's version. If the draft changed since it was read, the call is refused.
- Queued is not posted. A delivery starts as
queued. The outcome and the link to the live post come from jobs_get. - An unknown outcome is read, not retried. When a network does not confirm, the delivery is
unknown and settles on a later read.
What each network takes
These are the limits the service checks before anything is queued. Some networks also limit how files combine, such as several photos or one video; post_check answers for any mix without needing a draft.
An assistant uploads PNG, JPEG, WebP or MP4 files of up to 10 MB each, or adds one that is already online by its address. A larger video is added in the app.
Accounts
The social accounts connected to this workspace: each one’s id, network, name and status. Drafts are written for these ids. An account whose status is disconnected has to be connected again on the user’s account page before anything can be posted to it.
Links the user opens to connect their social accounts, one per network, with what is already connected on each. A link opens their account page on that network: they press Connect and sign in on the network’s own page, so you never see a password. Give them the link for each network they use, then call accounts_list to see what arrived. A network that cannot be connected yet answers ready: false and no link; say so instead of sending the user to it. Use it for a first connection and for one that needs reconnecting. Changes nothing.
networktext, optional- One network by its key in capabilities, such as "instagram". Leave it out for every network that can be connected.
What each network takes here: the longest caption and how it is counted, which file types, how many files (and whether a post needs one), and whether it has stories. Read it before writing for a network.
What one connected account has posted lately, read from the network itself: each post’s words, when it went out, what kind it is and its link, with likes and comments where the network gives them. Read it during setup to hear how the business already writes, and before a new set of posts so nothing is said twice. Works for Bluesky, Instagram, Facebook Pages and Threads; for other networks read the account’s public page yourself. Changes nothing.
accountIdtext- An account id from accounts_list.
limitnumber, optional- How many posts to read, newest first, from 1 to 25. Ten when left out.
The boards of one connected Pinterest account, read from Pinterest now: each board’s id and name, and which one its pins go to (chosen). To pin a post somewhere else, name a board in the draft’s options.board. Changes nothing.
accountIdtext- A Pinterest account id from accounts_list.
Changes which board a connected Pinterest account pins to when a post names none. The board is checked against the account’s boards on Pinterest first. Ask the user before changing it. Answers the account, whose name now ends in the board’s name.
accountIdtext- A Pinterest account id from accounts_list.
boardIdtext- The board’s id, from boards_list.
Disconnect one account. Its stored connection is deleted and its queued posts are cancelled, so nothing more is sent to it; posts already made stay in the history. The account stays in the list as disconnected until the user connects it again on their account page. Ask the user first.
idtext- An account id from accounts_list.
Add a practice account that posts nowhere, to try the flow. Its deliveries are simulated and marked as such. Refused where practice accounts are switched off.
labeltext- A name for the practice account.
mode"success" or "expired" or "ambiguous" or "retry", optional- How its deliveries end: success (the default), expired (fails as a lapsed connection), ambiguous (outcome unknown) or retry (rejected twice, then succeeds).
Upload one picture or video to attach to drafts: PNG, JPEG, WebP or MP4, up to 10 MB. Answers its media id. A file that no draft uses is removed 24 hours after upload.
nametext- The file name, for the user’s own list.
mimeType"image/png" or "image/jpeg" or "image/webp" or "video/mp4"- The file’s real format. A file whose bytes do not match is refused.
base64text- The file’s bytes in standard base64, with no data: prefix. Up to 10 MB of file.
alttext, optional- What the picture shows, for people who cannot see it. Kept with the file.
Add one picture or video by its public web address instead of sending the file: PNG, JPEG, WebP or MP4, up to 10 MB. The service fetches it once and stores it; answers its media id, as media_upload does. Use it when the user points at a file that is already online. Refused when the address is not a public https one, cannot be fetched, is too large, or leads to a page instead of a file.
urltext- The https address of the file itself, one that opens the picture or video in a browser without signing in. Not the address of a page that shows it.
nametext, optional- A file name for the user’s own list. Taken from the address when left out.
alttext, optional- What the picture shows, for people who cannot see it. Kept with the file.
Every uploaded file: id, name, type, size and alt text. A file with purgedAt has been removed from storage and can no longer be attached.
Look at one stored picture of this workspace: the answer is the picture itself. Use it on every picture templates_render made, before showing it to the user: check that no line is cut off, that the words fit their space and that the photo sits well, and render again if not. A video answers with its still picture. Changes nothing.
idtext- A media id from media_upload or media_list.
Remove one uploaded file from storage now. Refused with 409 while a draft uses it, while it is part of the saved brand, or while a post that uses it is still queued or not yet confirmed.
idtext- A media id from media_upload or media_list.
Drafts
Create a draft: a title and one variant per account, each with its own text and media. Nothing is sent. Answers the draft with its id and version. Check it with drafts_validate and show it to the user before publish or schedule.
titletext- A name for the draft in the user’s own list. It is never posted.
variantslist of objects- One entry per account the post goes to, each with its own text and media.
accountIdtext- An account id from accounts_list.
texttext- The caption for this account, within that network’s limit (see capabilities). Empty for a story. On YouTube the first line is the video title.
mediaIdslist of text- Media ids from media_upload, media_list or templates_render, in the order they should appear. Empty for a text post.
kind"post" or "story", optional- Leave out for an ordinary post. "story" sends exactly one photo or video as an Instagram or Facebook story, with empty text.
optionsobject, optional- What a post carries besides its text and media, on the four networks that take it; leave out everywhere else. YouTube: visibility. Pinterest: board and link. X: thread and firstReply. Google Business Profile: a button, or the details of an offer or an event (every field but visibility, board, link, thread and firstReply).
visibility"public" or "unlisted" or "private", optional- YouTube only: who can see this video. Ask the user; left out, the video is public.
boardtext, optional- Pinterest only: the board this pin goes to, by its name as shown on Pinterest or its id (boards_list lists them). Left out, the pin goes to the account’s chosen board.
linktext, optional- Pinterest only: the https address the pin leads to, such as a page of the business’s website. Left out, the first https address in the description is used.
threadtrue or false, optional- X only: true posts the pictures as a thread, the first on the post with its text and each next one on a reply of its own, up to 10. X crops several pictures on one post into small tiles, so a carousel goes to X this way, drawn in the landscape cut (templates_render).
firstReplytext, optional- X only: the text of a reply posted right under the post, up to 280 weighted characters. The place for a link: the post itself reads better without one. With thread, it rides on the first reply with the second picture.
type"update" or "offer" or "event", optional- "update" (the default), "offer" or "event".
languagetext, optional- The language the post is written in, as a code such as en or es.
buttonobject, optional- One button on an update or an event. An offer has none.
action"book" or "order" or "shop" or "learn_more" or "sign_up" or "call"- What the button says. "call" rings the phone number on the profile.
urltext, optional- Where the button leads, starting with https://. Leave out for "call".
titletext, optional- The name of the offer or event, up to 58 characters. Needed for both.
starttext, optional- The first day of the offer or event, as YYYY-MM-DD, or with a time as YYYY-MM-DDTHH:MM, in the business’s own local time. Needed for both.
endtext, optional- The last day, written the same way as start. Give a time for both or for neither.
offerobject, optional- An offer only: what the customer needs to redeem it.
codetext, optional- The code a customer gives, up to 58 characters.
urltext, optional- Where the offer is redeemed online, starting with https://.
termstext, optional- The terms of the offer.
Replace a draft’s title and variants. Send the whole draft, not only what changed, with the version you last read; the answer carries the next version. Refused with 409 when the draft changed in the meantime: read it again with drafts_get. Posts already queued keep the content they were queued with.
idtext- The draft id.
versionnumber- The draft’s version as you last read it. A different number is refused, so nothing goes out with content the user did not see.
titletext- A name for the draft in the user’s own list. It is never posted.
variantslist of objects- One entry per account the post goes to, each with its own text and media.
accountIdtext- An account id from accounts_list.
texttext- The caption for this account, within that network’s limit (see capabilities). Empty for a story. On YouTube the first line is the video title.
mediaIdslist of text- Media ids from media_upload, media_list or templates_render, in the order they should appear. Empty for a text post.
kind"post" or "story", optional- Leave out for an ordinary post. "story" sends exactly one photo or video as an Instagram or Facebook story, with empty text.
optionsobject, optional- What a post carries besides its text and media, on the four networks that take it; leave out everywhere else. YouTube: visibility. Pinterest: board and link. X: thread and firstReply. Google Business Profile: a button, or the details of an offer or an event (every field but visibility, board, link, thread and firstReply).
visibility"public" or "unlisted" or "private", optional- YouTube only: who can see this video. Ask the user; left out, the video is public.
boardtext, optional- Pinterest only: the board this pin goes to, by its name as shown on Pinterest or its id (boards_list lists them). Left out, the pin goes to the account’s chosen board.
linktext, optional- Pinterest only: the https address the pin leads to, such as a page of the business’s website. Left out, the first https address in the description is used.
threadtrue or false, optional- X only: true posts the pictures as a thread, the first on the post with its text and each next one on a reply of its own, up to 10. X crops several pictures on one post into small tiles, so a carousel goes to X this way, drawn in the landscape cut (templates_render).
firstReplytext, optional- X only: the text of a reply posted right under the post, up to 280 weighted characters. The place for a link: the post itself reads better without one. With thread, it rides on the first reply with the second picture.
type"update" or "offer" or "event", optional- "update" (the default), "offer" or "event".
languagetext, optional- The language the post is written in, as a code such as en or es.
buttonobject, optional- One button on an update or an event. An offer has none.
action"book" or "order" or "shop" or "learn_more" or "sign_up" or "call"- What the button says. "call" rings the phone number on the profile.
urltext, optional- Where the button leads, starting with https://. Leave out for "call".
titletext, optional- The name of the offer or event, up to 58 characters. Needed for both.
starttext, optional- The first day of the offer or event, as YYYY-MM-DD, or with a time as YYYY-MM-DDTHH:MM, in the business’s own local time. Needed for both.
endtext, optional- The last day, written the same way as start. Give a time for both or for neither.
offerobject, optional- An offer only: what the customer needs to redeem it.
codetext, optional- The code a customer gives, up to 58 characters.
urltext, optional- Where the offer is redeemed online, starting with https://.
termstext, optional- The terms of the offer.
Check a draft against the rules of every network it goes to. Answers valid, and errors as plain sentences that say what to fix. Changes nothing. publish and schedule run the same check and refuse a draft that fails it.
idtext- The draft id.
versionnumber- The draft’s version as you last read it. A different number is refused, so nothing goes out with content the user did not see.
Would each network take this post? Give the text, the networks and how many photos and videos; answers for each network whether it is ready, what is wrong, and the length used against the limit. Needs no draft and changes nothing.
texttext- The caption to check.
networkslist of text- Network keys to check against: x, linkedin, instagram, facebook, threads, bluesky, tiktok, youtube, google (Google Business Profile), pinterest.
imagesnumber, optional- How many photos would be attached.
videosnumber, optional- How many videos would be attached.
storytrue or false, optional- True to check it as a story instead of a post.
One draft: its title, its version and the text and media for each account. mediaRemovalAt, when present, is when its files will be removed if the draft is left unposted.
Every draft in the workspace, with its current version and the link that opens it in the user’s composer.
Delete a draft, and with it each of its files that no other draft uses. Refused with 409 while one of its posts is queued or not yet confirmed. Posts already made stay in the history.
Keep a post for a network the user has not connected yet, with its pictures, so it is not lost with the conversation. It shows on their account page next to that network’s Connect button, and becomes a draft by itself, for the new account, when they connect it. Use it only while accounts_list has no connected account for that network; otherwise write a draft with drafts_create. Up to 10 posts can wait. Answers the kept post and every post now waiting.
networktext- The network it is for, by its key in capabilities, such as "facebook". It must have no connected account yet.
texttext- The post, up to 20000 characters and within what that network takes (post_check says).
titletext, optional- A short name for it, up to 200 characters. Left out, its first line is used.
topicIdtext, optional- The topic it was written for, from topics_add. The draft is tied to that topic when the post becomes one.
mediaIdslist of text, optional- The pictures or videos that go with it, by their ids (from templates_render, media_upload or media_from_url), within what that network takes. They are kept while the post waits and go into the draft with it.
idtext, optional- A waiting post from an earlier drafts_hold, to replace its words. Leave it out to keep a new post.
Remove a post that was waiting for a network to be connected, when the user no longer wants it. Nothing else is touched. Ask the user first.
idtext- A waiting post, from drafts_hold or setup_start.
Publishing
Post a draft now to every account in it. Call it only after the user said yes to this post. Answers one delivery per account in state queued, which means accepted, not posted: read jobs_get for the outcome and the link to the live post. One call counts as one post on the plan.
idtext- The draft id.
versionnumber- The draft’s version as you last read it. A different number is refused, so nothing goes out with content the user did not see.
idempotencyKeytext- A key you make up for this one request, 8 to 200 characters (a UUID works). Repeating the request with the same key answers the same deliveries and never posts twice, so reuse it after a timeout. A key already used for another draft, version or time is refused.
Queue a draft to post at a later time, to every account in it. Call it only after the user said yes to this post and this time. Answers one delivery per account in state queued; a warning on a delivery means that account’s connection will lapse before then and the user should reconnect it. Refused when the time is further ahead than the plan schedules (usage_get says how far).
idtext- The draft id.
versionnumber- The draft’s version as you last read it. A different number is refused, so nothing goes out with content the user did not see.
idempotencyKeytext- A key you make up for this one request, 8 to 200 characters (a UUID works). Repeating the request with the same key answers the same deliveries and never posts twice, so reuse it after a timeout. A key already used for another draft, version or time is refused.
scheduledAttext- When to post, in UTC and ending in Z, for example 2026-11-06T14:00:00Z. Work it out from the user’s local time (usage_get names the workspace time zone). It must be in the future.
Queue a draft for the next free posting time, to every account in it, without choosing a date. Call it only after the user said yes to this post; tell them the time it was given. The time is the next of the saved posting times at which none of the draft’s accounts already has a post. Answers one delivery per account in state queued, with its scheduledAt. Refused when no posting times are saved yet (queue_set) or when every one the plan can schedule for is taken.
idtext- The draft id.
versionnumber- The draft’s version as you last read it. A different number is refused, so nothing goes out with content the user did not see.
idempotencyKeytext- A key you make up for this one request, 8 to 200 characters (a UUID works). Repeating the request with the same key answers the same deliveries and never posts twice, so reuse it after a timeout. A key already used for another draft, version or time is refused.
The saved posting times: each day’s own times (week), the time zone they are read in, and the next five. times and days say the same the short way, and uniform is true when every posting day has the same times. set is false until the user has chosen them.
Save the posting times the queue uses, replacing the ones saved before: either the same times on some days (times and days), or each day’s own (week). To change one day, read queue_get, change that day in week and send the whole week back. Ask the user which days and times they want first. Posts that are already scheduled keep their times.
timeslist of text, optional- The short way, for the same times on several days: one to twelve times of day on the 24-hour clock with two digits each, such as "09:00" or "17:30". They are read in the workspace time zone. Leave out when sending week.
dayslist of text, optional- With times: the days to post on, from mon, tue, wed, thu, fri, sat, sun. Every day when left out.
weekobject, optional- Each day’s own times, for days that differ, such as { "mon": ["17:30"], "tue": ["09:00", "17:30"] }. A day left out has none. Send this or times and days, not both.
One delivery and its outcome. state is queued, provider_accepted (the network has it and is still processing), published (receipt.url is the live post), failed (error says why), unknown (the outcome could not be confirmed: do not send it again, read it again later) or cancelled.
idtext- A delivery id from publish, schedule or jobs_list.
Every delivery, one per post per account: queued, posted, failed or cancelled, with its time, state, error and receipt.
How the posts of the last 7, 30 or 90 days did, the same numbers as the Results page: how many went out, their likes, comments and shares, views and reach where a network reports them, the same for the period before, each network’s share, the five posts that did best with their links, the times of the week the posts did best at (bestTimes, from the last 90 days; times is empty until enough posts have results), and what a post got on average with each hashtag (hashtags). Use it when the user asks how their posts are doing or what to post more of. notReported names networks posted to that gave no numbers. A post is read 1 hour, 1 day, 3 days and 7 days after it goes out, so a new post may have nothing yet. Where a Google Business Profile is connected, google holds what the profile itself got, by Google’s own count, over the same number of days ending three days ago (period): how many people called (calls), asked for directions (directions) or opened the website from it (websiteClicks), and how often it was seen (views, split into viewsInSearch and viewsInMaps). totals adds up every profile that was read and profiles lists each one; a profile whose state is no_reading gave no numbers just now, so say that and ask again later, and reconnect means the user reconnects it on their account page. google is null when no profile is connected. Part of Studio and Pro. Changes nothing.
daysnumber, optional- How far back to look: 7, 30 or 90 days. 30 when left out.
Move one delivery that is still queued to another time in the future. Refused with 409 once it is being sent or has been sent. Each account of a post is its own delivery.
idtext- A delivery id from publish, schedule or jobs_list.
scheduledAttext- When to post, in UTC and ending in Z, for example 2026-11-06T14:00:00Z. Work it out from the user’s local time (usage_get names the workspace time zone). It must be in the future.
Cancel one delivery that is still queued. Refused with 409 once it is being sent or has been sent. Each account of a post is its own delivery, so cancel each one that should not go out.
idtext- A delivery id from publish, schedule or jobs_list.
Brand
Start here when the user asks to be set up or no brand is saved, and again whenever you have lost your place. Say in can what you can do (open web pages, open the user’s files, look at pictures) and each step is worded for that. Answers where the workspace stands, the short first steps that end in a first post and what to offer after it (each with an id, what to do and whether it is done), which step is next, and the fields of a brand. Changes nothing.
canobject, optional- What you can do, so each step is worded for you. Leave it out and the steps are the general ones.
webtrue or false, optional- True if you can open and search web pages yourself, false if you cannot.
filestrue or false, optional- True if you can open the user’s own files or folders, false if you cannot.
picturestrue or false, optional- True if you can look at a picture and see what is in it, false if you cannot.
The brand study: what the business really looks like, read from its own website. Answers its logo (the file of its own, never the logo of a platform the site is built on or a badge), the colours the logo is drawn in with their share, the brand colours those give, its own photos by address with their alt text, and notes on what could not be read. A website’s style sheets often carry the colours of the tool it was built with, so take the look from this, not from the site’s styles. Call it before drafting the brand, then import the logo and the best photos with media_from_url. Changes nothing.
urltext- The address of the business’s website, such as yourshop.com.
The saved brand of this workspace (business, voice, look, offers, what never to say), or null before setup. Read it before writing any post.
Save the brand you drafted during setup. Send the whole brand each time: it replaces the saved one. Set confirmed to true only after the user agreed to your summary.
brandobject- The whole brand. Send every part each time: what is left out is cleared, except the parts the user set by hand (brand.owned), which are kept.
businessobject- Who the business is and how to reach it.
nametext- The name of the business, up to 80 characters.
tradetext- What kind of business it is, up to 60 characters, such as "auto shop" or "restaurant". setup_start lists the trades the templates know.
descriptiontext, optional- What the business does, up to 600 characters.
locationtext, optional- The town or neighbourhood, up to 80 characters.
serviceAreatext, optional- Where it serves, up to 200 characters.
phonetext, optional- The phone number customers call, up to 40 characters.
websitetext, optional- Its web address, starting with https://, up to 200 characters.
hourstext, optional- Opening hours in words, up to 300 characters.
weekobject, optional- Opening hours day by day, keyed mon to sun: { open, close } as 24-hour times such as "08:30", null for closed, or a list of up to three { open, close } for a day that opens more than once. A day left out is not stated.
speciallist of objects, optional- Days that differ from the usual week, up to 30: a holiday, a closure, a late night. The user sets these on their brand page.
datetext- The date, such as "2026-11-26".
hoursobject, optional- That day’s hours as { open, close } in 24-hour times, null for closed, or a list of up to three { open, close } for a day that opens more than once.
labeltext, optional- What the day is, up to 40 characters.
languageslist of text, optional- The languages it posts in, up to 5, each up to 40 characters.
audiencetext, optional- Who buys, in a sentence or two, up to 600 characters.
goaltext, optional- What the business wants more of from its posts, in one line of up to 200 characters: calls, bookings, walk-ins, orders. A post’s call to action leads there.
offerslist of text, optional- What it offers right now, up to 20 lines of up to 160 characters.
voiceobject, optional- How the business sounds.
tonelist of text, optional- Up to 8 words for how it sounds, each up to 40 characters.
dolist of text, optional- What its writing does, up to 10 lines of up to 200 characters.
avoidlist of text, optional- What its writing avoids, up to 10 lines of up to 200 characters.
sampletext, optional- One passage in the business’s own voice, up to 600 characters.
lookobject, optional- The colours, type and logo that templates are drawn in.
colorsobject, optional- The four colours templates are drawn in. One left out keeps its default.
primarytext, optional- The main brand colour, as #RRGGBB.
accenttext, optional- The colour for emphasis, as #RRGGBB.
inktext, optional- The text colour, as #RRGGBB.
papertext, optional- The background colour, as #RRGGBB.
fontsobject, optional- The type templates are set in, by family name.
displaytext, optional- The Google Fonts family for headings, such as "Archivo".
bodytext, optional- The Google Fonts family for text. Left out, it follows display.
logotext, optional- The media id of the uploaded logo.
topicslist of text, optional- What the business posts about, up to 20 lines of up to 120 characters.
hashtagslist of text, optional- Hashtags it uses, up to 30, each up to 60 characters.
neverlist of text, optional- Things no post may say, up to 20 lines of up to 240 characters: prices that are not public, claims that cannot be shown.
templatesobject, optional- The template chosen for each format, such as { "carousel": "rundown" }, for up to 12 formats. Ids come from templates_list.
sourceslist of objects, optional- Where each part of the brand came from, up to 20, so the user can see it was read and not made up.
wheretext- A web address, or "the customer", up to 200 characters.
whattext- What was taken from it, up to 200 characters.
itemslist of objects, optional- What the business sells, up to 60.
nametext- The product, service or dish, up to 80 characters.
abouttext, optional- One line about it, up to 240 characters.
pricetext, optional- Its price as the user writes it, such as "$89" or "from $45", up to 24 characters. Leave it out unless the user gave it.
librarylist of objects, optional- Photos of the business’s own work, up to 200. A photo is kept in storage for as long as it is listed here.
mediaIdtext- The media id of an uploaded picture.
captiontext, optional- What the photo shows, up to 160 characters.
itemtext, optional- The item it belongs to, by name, up to 80 characters.
cutouttrue or false, optional- True for a photo that stands alone: cut out, or shot on white. The cover page takes these.
confirmedtrue or false, optional- True only once the user has agreed to your summary of the brand.
The saved industry profile (who buys, the questions they ask, what converts, the local angle, sources, guardrails) or null, with what each of its parts is for. Read it before choosing a topic or writing an article.
Save the industry profile you researched during setup. Send the whole profile each time: it replaces the saved one. Needs a saved brand.
profileobject- The whole profile. Send every part each time: it replaces the saved one.
tradeobject- What this trade is in one line, and the kinds of customer or business within it that behave differently.
definitiontext- The trade in one or two sentences, up to 300 characters.
segmentslist of text, optional- The kinds of customer or business within it, up to 8, each up to 160 characters.
buyerslist of objects, optional- Who buys: each kind of buyer and what they want when they come looking. Up to 6.
whotext- A kind of buyer, up to 160 characters.
wantstext- What they want when they come looking, up to 240 characters.
questionsobject, optional- The real questions buyers type, in their own words, grouped by what they are after. Take them from search suggestions, "people also ask", reviews and the customer.
learninglist of text, optional- Questions from people finding out, up to 15, each up to 160 characters.
comparinglist of text, optional- Questions from people weighing options, up to 15, each up to 160 characters.
buyinglist of text, optional- Questions from people ready to book or buy, up to 15, each up to 160 characters.
nearbylist of text, optional- Questions that name a place, up to 15, each up to 160 characters.
convertingTopicslist of objects, optional- 3 to 8 themes that move a reader to call, book or buy, and why each does.
topictext- The theme, up to 120 characters.
whytext- Why it moves a reader to act, up to 240 characters.
tonetext, optional- How writing for this trade should sound to land with its buyers, within brand.voice. Up to 400 characters.
localAnglelist of text, optional- What is local: neighbourhoods, languages, seasons, rules. Empty for a business that sells everywhere. Up to 10, each up to 200 characters.
entitieslist of text, optional- The names a piece should use the same way every time: the business, places, products, tools, brands. Up to 30, each up to 80 characters.
keywordslist of text, optional- The search phrases to be found for. Only ones you saw demand for; say in `landscape.gap` when you could not check. Up to 40, each up to 80 characters.
landscapeobject, optional- What currently ranks or gets quoted for those questions, and the gap this business can fill.
rankslist of text, optional- Who or what ranks today, and for which question, up to 10, each up to 200 characters.
gaptext, optional- The gap this business can fill, up to 500 characters.
sourceslist of objects, optional- The authoritative references a claim in this trade should be backed by. Up to 20.
nametext- The reference, up to 120 characters.
fortext- What it backs, up to 200 characters.
callToActionobject, optional- The one offer that turns a reader into a customer, and where it sits in a piece.
offertext, optional- The offer, up to 200 characters.
wheretext, optional- Where it sits in a piece, up to 200 characters.
guardrailsobject, optional- What makes a piece filler in this trade, and what every piece must contain to be worth reading.
bannedlist of text, optional- What makes a piece filler, up to 10, each up to 200 characters.
requiredlist of text, optional- What every piece must contain, up to 10, each up to 200 characters.
Templates
The post templates this workspace can use, with what each is for.
One template: its page types, every field with its limits, and a filled example for the trade.
idtext- A template id from templates_list.
Fill a template with words. The service draws each page in the saved brand’s colours and fonts, saves it as a picture and answers the media ids, in page order, ready to attach to a draft. Look at each one with media_view before the user sees it. A photo on a page is one the user already uploaded (its media id from media_list) or an https address.
idtext- A template id from templates_list.
seriestext, optional- A short series name shown on the pages, where the template has one.
language"en" or "es" or "pt" or "fr" or "ht", optional- The language of the words the service prints itself, such as the days of the week and "Closed": English (en), Spanish (es), Portuguese (pt), French (fr) or Haitian Creole (ht). English when left out.
cut"portrait" or "landscape" or "pin" or "google", optional- The shape of the pictures, one per network: portrait (1080 by 1350) for Instagram, Facebook, LinkedIn and Threads; landscape (1600 by 900) for X and Bluesky; pin (1080 by 1620) for Pinterest. templates_list says which cuts each template has. The template’s own shape when left out.
pageslist of objects- The pages in order, as many as the template takes. A carousel ends on an action page.
typetext- The page type, one of the template’s own (templates_get lists them). The page’s other fields are that type’s fields, each within its limit of characters and lines.
The templates and their pages
A template is a designed picture or carousel. The assistant writes the words; the service draws each page in your brand's colours and type. Each page type takes these fields, and a field holds only so many characters and lines.
The Rundown rundown
One thing explained end to end in three to five pages: a service, a dish, a product.
carousel · 1080 × 1350 · up to 6 pages
cover- One photo on the paper with the title under it. The photo is cut out or shot on white: use a brand.library photo marked cutout, and no other kind.
image a picture: a media id or an https addresskicker up to 40 characters, 1 linetitle up to 40 characters, 2 linessub up to 120 characters, 2 lines, optional
punch- No photo: one or two words carry the page. Use it when there is no good picture.
kicker up to 40 characters, 1 lineword up to 24 characters, 2 linestitle up to 90 characters, 2 linessub up to 140 characters, 2 lines
number- One range or figure, and the two things that move it.
kicker up to 40 characters, 1 linelow up to 4 characters, 1 linehigh up to 5 characters, 1 lineunit up to 40 characters, 1 linetitle up to 80 characters, 2 linesleft with head (up to 14 characters, 1 line), body (up to 70 characters, 1 line)right with head (up to 14 characters, 1 line), body (up to 70 characters, 1 line)
stats- A title, an optional photo and exactly three figures.
kicker up to 40 characters, 1 linetitle up to 44 characters, 2 linesimage a picture: a media id or an https address, optionalstats exactly 3, each with value (up to 7 characters, 1 line), label (up to 24 characters, 1 line)
cells- A title and two or four short points.
kicker up to 40 characters, 1 linetitle up to 44 characters, 2 linescells 2 or 4, each with title (up to 24 characters, 1 line), body (up to 64 characters, 1 line)
items- What the business sells, printed by the service from the brand’s own list: each item’s name, its line and its price when the customer gave one. You write only the title, and may name which items to show. A price cannot be typed; prices: false leaves them off. When every item shown has a photo of its own in the brand’s library, the photos are printed too; photos: false leaves them off.
kicker up to 40 characters, 1 linetitle up to 44 characters, 2 linesnames 1 to 6 names, each up to 80 characters, optionalprices true or false, optionalphotos true or false, optional
hours- The opening hours, day by day, printed by the service from the brand. You write only the title; the days and times cannot be typed.
kicker up to 40 characters, 1 linetitle up to 44 characters, 2 linesnote up to 110 characters, 2 lines, optionalshow "week" or "special" or "both", optional
action- The last page of a carousel: what to do next and where.
kicker up to 40 characters, 1 linetitle up to 28 characters, 2 linessub up to 110 characters, 1 lineaction up to 26 characters, 1 lineurl up to 48 characters, 1 linehours true or false, optional
Head to Head head-to-head
Two options a customer weighs against each other, ending in a plain verdict.
single image or carousel · 1080 × 1350 · up to 2 pages
compare- Two options side by side, three to five rows, then the verdict.
leftName up to 24 characters, 2 linesleftLabel up to 24 characters, 1 linerightName up to 24 characters, 2 linesrightLabel up to 24 characters, 1 linerows 3, 4 or 5, each with label (up to 14 characters, 1 line), left (up to 22 characters, 1 line), right (up to 22 characters, 1 line)callKicker up to 24 characters, 1 linecall up to 120 characters, 1 line
action- The last page of a carousel: what to do next and where.
kicker up to 40 characters, 1 linetitle up to 28 characters, 2 linessub up to 110 characters, 1 lineaction up to 26 characters, 1 lineurl up to 48 characters, 1 linehours true or false, optional
The Work showcase
The customer’s own photos: a finished job, a dish, a before and after, the shop. Built around pictures from brand.library.
single image or carousel · 1080 × 1350 · up to 6 pages
photo- One of the customer’s own photos filling the whole page, with the words low on it. The opener of a photo post.
image a picture: a media id or an https addresskicker up to 40 characters, 1 linetitle up to 40 characters, 2 linessub up to 110 characters, 2 lines, optionalfocus "top" or "center" or "bottom", optional
card- A photo shown whole at the top, with the words under it on the paper. Use it when the photo must not be covered.
image a picture: a media id or an https addresskicker up to 40 characters, 1 linetitle up to 44 characters, 2 linessub up to 120 characters, 2 lines, optional
grid- Two or four photos side by side under a title: several jobs, dishes or angles at once.
kicker up to 40 characters, 1 linetitle up to 44 characters, 2 linesphotos 2 or 4, each with image (a picture: a media id or an https address), caption (up to 26 characters, 1 line)
before_after- Two photos of the same thing, before and after the work, with a title under them.
before a picture: a media id or an https addressafter a picture: a media id or an https addressbeforeLabel up to 14 characters, 1 lineafterLabel up to 14 characters, 1 linekicker up to 40 characters, 1 linetitle up to 44 characters, 2 linessub up to 120 characters, 2 lines, optional
items- What the business sells, printed by the service from the brand’s own list: each item’s name, its line and its price when the customer gave one. You write only the title, and may name which items to show. A price cannot be typed; prices: false leaves them off. When every item shown has a photo of its own in the brand’s library, the photos are printed too; photos: false leaves them off.
kicker up to 40 characters, 1 linetitle up to 44 characters, 2 linesnames 1 to 6 names, each up to 80 characters, optionalprices true or false, optionalphotos true or false, optional
hours- The opening hours, day by day, printed by the service from the brand. You write only the title; the days and times cannot be typed.
kicker up to 40 characters, 1 linetitle up to 44 characters, 2 linesnote up to 110 characters, 2 lines, optionalshow "week" or "special" or "both", optional
action- The last page of a carousel: what to do next and where.
kicker up to 40 characters, 1 linetitle up to 28 characters, 2 linessub up to 110 characters, 1 lineaction up to 26 characters, 1 lineurl up to 48 characters, 1 linehours true or false, optional
Moments moments
Lifestyle posts for a restaurant, a florist, a salon, any business people come to for how it feels: the customer’s own photos carry the post. A carousel goes moment, close-up, place, then a sign-off that invites. Built from brand.library; needs photos of people and moments, not only products.
single image or carousel · 1080 × 1350 · up to 6 pages
moment- One of the customer’s own photos of a moment, filling the page, with one short line on it in the business’s voice: someone enjoying it, hands at work, the table, the shop. Not a headline and never a price; the caption says the rest.
image a picture: a media id or an https addressline up to 40 characters, 2 linesplace "bottom" or "top", optionalfocus "top" or "center" or "bottom", optional
signoff- The last page of a lifestyle carousel: a photo, one short line that invites (“see you on the patio.”), and under it where the business is and its logo, small.
image a picture: a media id or an https addressline up to 40 characters, 2 linesplace "bottom" or "top", optionalfocus "top" or "center" or "bottom", optional
Google post google-post
This week on the Google Business Profile: something new, an offer, a change of hours.
single image · 1200 × 900 · 1 page
google- A Google Business Profile post, landscape: words on the left, a photo or one figure on the right.
kicker up to 40 characters, 1 linetitle up to 36 characters, 2 linessub up to 120 characters, 3 linesimage a picture: a media id or an https address, optionalfigure up to 4 characters, 1 line, optionalfigureLabel up to 28 characters, 1 line, optional
google_hours- The opening hours as a Google Business Profile post, landscape: your title on the left, the week on the right, printed by the service from the brand. The days and times cannot be typed.
kicker up to 40 characters, 1 linetitle up to 36 characters, 2 linesnote up to 110 characters, 3 lines, optionalshow "week" or "special" or "both", optional
Topics
What is covered and what is not: each topic’s standing per network and language, what it still lacks, how many topics each brand pillar has, and the profile questions no topic has taken yet. Call it to choose what to write next.
The topic ledger: every topic planned or covered, with where it stands on each network and in each language.
Add a topic to the ledger before writing it: its title, the question or search phrase it is meant to win (keyword), the one product or service it is about (item), how it comes at it (angle) and which of the brand’s topics it belongs to (pillar). A keyword already covered is refused, so the same question is never written twice.
titletext- The topic in a few words, up to 160 characters.
keywordtext, optional- The question or search phrase it is meant to win, up to 120 characters. One already covered is refused.
itemtext, optional- The one product or service it is about, up to 120 characters.
angletext, optional- How the piece comes at it, in a word or two of up to 60 characters: guide, comparison, checklist, story.
pillartext, optional- Which of the brand’s topics it belongs to, up to 120 characters.
notetext, optional- Anything to remember when writing it, up to 400 characters.
Tie a draft to its topic, with the language it is written in. Do this for every draft you create, so the ledger knows what was written and posted.
idtext- A topic id from topics_add or topics_list.
draftIdtext- The draft written for it.
languagetext, optional- The language the draft is written in.
Take a planned topic out of the ledger. A topic with a post out cannot be removed.
idtext- A topic id from topics_list.
Writing guides
How to write a post for each network: what one post is, how its caption is built, where a link goes, and the rules every post follows. Read it before writing a set of posts. Without a network it answers for the connected ones.
networktext, optional- One network key, such as instagram. Leave out for every connected network.
How to judge a draft before the user sees it: seven scored dimensions for an article, yes-or-no checks for a social post, and what counts as a pass. Judge every piece and fix it until it passes.
kind"article" or "post", optional- What is being judged.
Articles
Keep an article you wrote for the user, or the next version of one. It reaches readers one of two ways. On the user’s blog here: send blog true with status ready, and it is public at their link page’s address followed by /blog, in their brand (a workspace with one link page gets its blog there; with several, links_save with showBlog chooses the page; with none, make one first). Or on the user’s own site: they paste it in, and you then save its address as url with status published. Either way posts can link to it. An article on the blog can carry a cover picture (image) and pictures in its words, each one of the user’s own stored pictures. The same piece in two languages is two articles: save each in its own language, then name one in the other’s twinId. Send every field again with each version: a field left out is removed. Judge it with grader_get first, tie it to its topic with topicId, and put nothing on the blog without the user’s yes to that article.
idtext, optional- The article to replace. Leave it out for a new article.
versionnumber, optional- With id: the version you read, so an edit made meanwhile is not overwritten.
titletext- The headline, up to 160 characters.
bodytext- The article as Markdown, without its title: ## and ### headings, paragraphs, lists, tables, **bold**, *italic* and [links](https://…). On the blog a numbered list is shown as steps, so use one for things done in order. A picture inside the words is a line of its own, , with a media id from media_upload, media_from_url or media_list, never a web address; up to 12. It is shown on the blog where its line is, and left out of the copy the user pastes into their own site. Up to 60,000 characters.
summarytext, optional- What a search result shows under the title, up to 160 characters.
slugtext, optional- The last part of the page’s address: lower-case letters, numbers and hyphens, up to 80 characters. Made from the title when left out.
keywordtext, optional- The question or search phrase the article is meant to win, up to 120 characters.
languagetext, optional- The language it is written in, up to 40 characters. English when left out.
topicIdtext, optional- The topic in the ledger this article covers, from topics_list.
status"draft" or "ready" or "published", optional- draft while you work on it, ready once it passes the grader and the user has agreed, published once the user has put it on their own site (url is then needed). An article on the blog here is ready or published. draft when left out.
urltext, optional- The https address where the article now lives on the user’s own site, up to 300 characters.
blogtrue or false, optional- true puts the article on the user’s blog here, at the address of their link page followed by /blog. Left out or false, it is not on the blog. It needs status ready or published, and the user’s yes to this article.
publishAttext, optional- For the blog: the moment the article goes out, which is also the date it is shown with, with its time zone, such as 2026-10-13T09:00:00-04:00. A later moment makes it appear by itself then. Left out, it goes out when it is first put on the blog.
faqlist of objects, optional- Up to 8 questions the article answers, each with its answer. On the blog they are shown in full under the article and given to search engines word for word.
qtext- The question as someone would ask it, up to 160 characters.
atext- The answer in one to three sentences that stand on their own, up to 600 characters. Plain words, no Markdown.
sourceslist of objects, optional- Where each number in the article comes from, up to 12. A number with no source does not go in the article.
labeltext- A short name for the source, up to 120 characters.
urltext- The https address of the page the number comes from, up to 300 characters.
tagslist of text, optional- Up to 12 short tags, each up to 40 characters. On the blog, articles that share a tag are offered to the reader next.
imagetext, optional- The cover picture, by its media id (from media_upload, media_from_url or media_list). On the blog it is shown under the headline, small beside the article in the list and on the link page, and it is the picture a share of the article shows. Use the user’s own photo of the thing the article is about. It is kept for as long as the article is.
imageAlttext, optional- What the cover picture shows, in one plain sentence, for a reader who cannot see it. Up to 200 characters. Needs image.
twinIdtext, optional- The id of the same article in another language, from articles_list. It ties the two from either side: on the blog each then names the other to search engines, and the language link at the top of each leads to the other. Left out, the two are untied.
One article in full: its Markdown, the same words as clean HTML ready to paste into the user’s site, the search markup (schema) that goes on the same page, and where it is on the user’s blog here when it is on it.
idtext- An article id from articles_list or articles_save.
The articles kept for this workspace, newest first: title, address, status and length, without the words themselves. One that is on the user’s blog here says where (blogAddress) and whether a visitor gets it right now (blogLive).
Remove an article from this workspace. If it was on the user’s blog here it leaves the blog at once. Nothing on the user’s own site is touched.
idtext- An article id from articles_list.
Locations
The locations of this workspace, each with the connected accounts that belong to it, and the accounts that belong to none. To post to a location, use the ids of its accounts as the destinations of the draft. Part of Pro.
Make a location, or change one: its name, where it is, who its report is for, and which connected accounts belong to it. Use it when the user has several branches, or posts for several clients. An account belongs to one location at a time. Part of Pro; refused on other plans.
idtext, optional- A location id from locations_list, to change that one. Leave out to make a new location.
nametext, optional- What the location is called, for example the branch or the client, up to 80 characters. Needed for a new one.
placetext, optional- Where it is, for example "Hialeah, FL", up to 120 characters.
clienttext, optional- Who its report is for, when the user posts for someone else, up to 120 characters.
accountIdslist of text, optional- The connected accounts that belong to it, as ids from accounts_list. This replaces the list. An account leaves any other location it was in.
What one location did in the last 7, 30 or 90 days: how many posts went out and where, what they got, which did best, what failed and what is scheduled next. `text` is the same thing in plain words, ready for the user to send to the branch or the client: show it to them as it is. Without an id the answer is every location side by side for the same period, with the totals of all of them. Part of Pro; refused on other plans.
idtext, optional- A location id from locations_list. Leave out for every location side by side.
daysnumber, optional- The period, counted back from now: 7, 30 or 90 days. 30 when left out.
Remove a location. Its accounts stay connected and belong to no location; nothing that was posted is touched.
idtext- A location id from locations_list.
Link page
The user’s link pages: for each its public address, name, line, link buttons in order, what it shows and whether it is live. Also how many pages the plan includes, and `kept`: what the service keeps current on every page without being asked (the look and logo from the brand, the open-now line from the brand’s hours, the connected accounts and the latest published posts). And `reserved`: addresses a page of theirs has left, which only they can use until the time given. The page that carries the user’s blog says so (showBlog) and where the blog is (blogAddress).
Make a link page, or change one: a public page at the app’s address followed by /@handle, with the user’s name, one line about them, their connected accounts as icons and their link buttons. Only what you send changes. The page takes its colours, fonts and logo from the saved brand and keeps the latest posts and the open-now line current by itself, so never add those as links. Starter includes 1 page, Studio 10 and Pro 30; one more is refused. A handle someone else has is refused. With showBlog a page also carries the user’s blog: their articles, at the page’s address followed by /blog.
idtext, optional- A page id from links_list, to change that page. Leave out to make a new page.
handletext, optional- The page’s address, after the @: 3 to 30 lowercase letters, digits and hyphens, for example "casa-lola". Needed for a new page. Changing it moves the page: the old address stops working at once. If the page was ever public, the old address stays reserved for the user for 30 days: nobody else can take it, and they can move the page back.
nametext, optional- The name at the top of the page, up to 60 characters. The business name from the brand when left out.
biotext, optional- One line about the business or the person, up to 160 characters. Empty text removes it.
linkslist of objects, optional- Every link button, in the order shown, up to 30. This replaces the list, so send the links from links_list with your change made. To add one link, use addLink.
labeltext- The words on the button, up to 60 characters.
urltext- Where the button leads: a web address starting with https://, a phone number as tel:+13055550100, or an email address as mailto:name@example.com. Nothing else is accepted, and an address runs to 500 characters at most.
showntrue or false, optional- false keeps the link on the page’s list without showing it. Shown when left out.
toptrue or false, optional- true makes this the top button: drawn first and filled. At most one link.
addLinkobject, optional- One link to add without sending the whole list. It goes first, under the top button. Use it for "add my new video to my link page".
labeltext- The words on the button, up to 60 characters.
urltext- Where it leads: an https:// web address, a tel: phone number or a mailto: email address, up to 500 characters.
toptrue or false, optional- true makes it the top button; the link that was the top button stays as an ordinary link.
accountIdslist of text, optional- The connected accounts this page shows as icons, and whose latest posts it shows, as ids from accounts_list, up to 150. Leave out to keep it as it is; null shows every connected account, which is how a new page starts.
showPoststrue or false, optional- Whether the page shows the latest published posts (up to three). On for a new page.
showHourstrue or false, optional- Whether the page says if the business is open now, from the opening hours on the brand. On for a new page; nothing is shown while the brand has no hours.
showBlogtrue or false, optional- true makes this the page that carries the user’s blog: the articles put on the blog are public at this page’s address followed by /blog, and the page names the newest three. A workspace has one blog, so the page that had it gives it up and every article’s address changes. Off for a new page.
publishedtrue or false, optional- true puts the page at its public address; false takes it down. A new page is not published until this is true. Ask the user before publishing.
Remove a link page. Its public address stops working at once. If the page was ever public, the address stays reserved for the user for 30 days, and after that anyone can take it. Nothing that was posted and nothing of the brand is touched.
idtext- A page id from links_list.
Plan and help
What the plan includes and what is used this month: posts, accounts and uploads, the workspace time zone, when the month ends, and the latest time a post can be scheduled for right now. Check it before queuing a batch.
Set the workspace time zone. The plan’s month and its counts follow it; the answer says from when the change applies.
timeZonetext- A time zone name such as America/Chicago or Europe/Madrid.
atSignuptrue or false, optional- Used by the app on a first visit. Leave it out.
Search the help centre. Answers up to five matching articles; read one with help_get. Use it when the user asks how something works or why something was refused.
querytext- The question in a few words.
One help article as plain text, to read out or sum up for the user.
idtext- An article id from help_search.
When a call is refused
A refusal is one plain sentence in error that says what happened and what to do next, and nothing is changed. Where the reason is a plan limit or a pace limit it also carries a code, and for a plan limit the limit that was reached and a help link to the full answer.
{
"error": "“Saturday hours” already covers that keyword. Pick another question, or wait until 90 days after it was last posted and come at it from a new angle.",
"code": "topic_repeat",
"topicId": "topic_1"
}
A refusal without a code is a draft that changed since it was read, a delivery that is already on its way, or a draft that breaks a network's rule. Its sentence says which.
Next: how it works, pricing or the short version for agents.