Skip to content

campaigns — full tool reference

Post calendar, human review queue, account-level post performance analytics and the Zernio account to BU/owner mapping.

Endpointhttps://campaigns.mcp.devfellowship.com/mcp
Packagepackages/dfl-mcp-campaigns
Tools34
ToolDescription
list_post_business_unitsThe business units the post calendar can schedule for — strategy.business_units, archived ones excluded. Call this FIRST: every other tool takes a business_unit_id (a uuid), never a name like “itera”.
list_zernio_profilesWho publishes where: each Zernio profile (a person or the company) with its connected accounts per network, plus the accounts that belong to no profile. Call this FIRST when drafting a post: draft_post_for_review requires zernio_profile_id — the profile of the person or brand the post goes out as (“Criador” in the dfl-campaigns UI). A post is NOT limited to that profile’s own accounts — the app allows channels across profiles (e.g. the brand DevFellowship plus a team member’s personal accounts); the profile just names who the post is mainly for.
list_zernio_accountsThe social accounts connected in Zernio, each with the id a channel needs to actually publish. Call this BEFORE draft_post_for_review whenever the post has channels: Zernio publishes per ACCOUNT, and there is more than one account on the same platform (a company profile and a personal one), so “instagram” alone does not say where the post goes out. A channel drafted without its zernio_account_id is dropped at dispatch, and the review queue cannot add the account afterwards — that post has to be redone.
list_postsRead the post calendar, ordered by scheduled_for. Without business_unit_id it is the consolidated view across every BU. Pass status: “awaiting_review” to read the human review queue — everything the AI wrote that is still waiting on a person. assignee_id and zernio_profile_id narrow it to one person or one profile; while those columns do not exist yet the filter is skipped and notes says so. Each post carries assignment; pass include_metrics: true to attach latest_metrics per platform and account. Each post carries archetype and media_group (its content-format tag; null when unset).
list_core_daily_topicsWhat each person said in the last N days, merged per person from the Discord channel #core-daily-updates (primary) and the Core Daily meeting transcripts (when there was one). Each line has source (“channel” with a Discord url, or “meeting”); lines under 8 words and repeats are removed, at most 30 per person, newest first. sources says which side answered. Read-only. Use it to propose one draft per person: each line is raw speech, so rewrite it into a hook before calling draft_post_for_review, and pass assignee_id = user_id when it is present (user_id is only set on an exact name match with a member).
list_suggested_topicsRead the suggested topics on the Curadoria board of /posts/pautas in dfl-campaigns. A suggested topic (a curated content topic, “pauta” in Portuguese) is an idea for content that a person or an agent put on the board and that later turns into one or many posts. Each topic has a title, briefing, status (idea, todo, awaiting_content, in_production, awaiting_approval, posted, standby), optional due_date, formats, channels, reference_links, business_unit_id, assignee_id and a url to its card. Active topics are returned by default; pass archived=true for the archived ones. Filters are applied after the read. Read-only. Call it before create_suggested_topic so you do not add a duplicate. If the answer says the board is not live on this deployment, do not retry.
create_suggested_topicAdd one suggested topic to the Curadoria board of /posts/pautas in dfl-campaigns. A suggested topic (a curated content topic, “pauta” in Portuguese) is an idea for content that a person or an agent proposes and that later turns into one or many posts. Creating one posts NOTHING and drafts no post: it only puts a card on the board, marked as created by an agent (origin “mcp”), for a person to pick up. To draft a post use draft_post_for_review. The status defaults to idea. Run list_suggested_topics first to avoid duplicates. The answer carries the card url. A refusal (not available on this deployment, a rule, a missing BU or user) says do not retry; only a temporary failure may be retried.
update_suggested_topicEdit the content of one suggested topic (“pauta”) on the Curadoria board of /posts/pautas in dfl-campaigns: title, briefing, due date, formats, channels, reference links, BU or assignee. Any member can edit any topic. Send only the fields to change; an omitted field keeps its value, and null clears briefing, due_date, business_unit_id or assignee_id. Arrays (formats, channels, reference_links) replace the whole list. It does NOT change the column or the order: use move_suggested_topic for that, and archive_suggested_topic to archive. It refuses an empty change, an unknown field and an unknown id. Get the id from list_suggested_topics. A refusal says do not retry; only a temporary failure may be retried.
move_suggested_topicMove one suggested topic (“pauta”) to a column of the Curadoria board of /posts/pautas in dfl-campaigns and, optionally, to a place inside it. status is the target column (it may be the current one, to only reorder). Without before_id/after_id the card goes to the end of the column; with before_id it lands right above that card, with after_id right below it (send one, never both). The neighbour must be an active topic already in the target column. Any member can move any topic. It does not edit content (update_suggested_topic) and does not touch posts. A refusal says do not retry; only a temporary failure may be retried.
archive_suggested_topicArchive one suggested topic (“pauta”) of the Curadoria board of /posts/pautas in dfl-campaigns, or bring an archived one back with restore=true. Archiving hides the card from the board but keeps it, and it is reversible: this is the safe way to take a topic off the board. Any member can archive or restore any topic. To remove one for good use delete_suggested_topic. Archived topics are read with list_suggested_topics archived=true. A refusal (unknown id, not available on this deployment) says do not retry; only a temporary failure may be retried.
delete_suggested_topicPermanently delete one suggested topic (“pauta”) from the Curadoria board of /posts/pautas in dfl-campaigns. It cannot be undone. Only the creator of the topic or an admin may delete it: the server decides that, from the caller session, and refuses everybody else with “do not retry” (ask the creator or an admin instead). To take a topic off the board without losing it, use archive_suggested_topic. Pass confirm_title equal to the topic’s current title, exactly as list_suggested_topics shows it; a different title, or an id that is not on the board, deletes nothing. Posts already drafted from the topic are not deleted.
get_postOne post with its channels, its lifecycle state and its approval trail — who approved it and when, and whether it was already dispatched to Zernio. Also returns assignment (zernio_profile_id, assignee_id; null while those columns do not exist) and latest_metrics: the latest views/likes/comments per platform AND Zernio account, never summed across platforms. The post always carries archetype and media_group (its content-format tag; null when unset or while the columns do not exist). live is { published_at, published_url } once dfl-campaigns read the post LIVE in Zernio (status published): the link to the post on the network. null while it is not known live. Use it to answer “how did this post do” and “where is it”.
draft_post_for_reviewWrite one post into the calendar. It lands in awaiting_review and NOTHING leaves this server until a person opens the queue in dfl-campaigns, reads it and approves it — you cannot clear it yourself; approve_post only records a decision a person already gave you. Schedule the time you want it to go out; a channel only reaches its network if it carries the connected zernio_account_id. An Instagram Reel cover is optional: pass cover_media_id (the COVER_MEDIA_ID from the thumbify-reel-cover skill) or cover_url (a Thumbify render URL), or Instagram uses frame 0 of the video. A cover that is not a Thumbify cover is refused at approval. The Lesson Studio cover slide is NOT carried over. A carousel (Instagram, or a TikTok photo carousel) is either media_ids (existing public.media ids) or media_urls (rendered images, e.g. from render_composition_images; dfl-campaigns registers them as your media). A single image is media_id; TikTok accepts it as a photo post (JPEG, PNG or WebP, up to 20 MB each).
revise_post_in_reviewRewrite a post that is still in the queue — typically after a reviewer left review_notes, or when a rejected post is being redone. Only draft, awaiting_review and rejected posts can be revised: once a person has approved it, the text they read is the text that goes out. Status is never changed here.
approve_postRecord that a PERSON cleared this post for publishing, when they said so to you instead of clicking in the dfl-campaigns UI — the case the queue had no answer for (Tainan asked for a Reel over Telegram, 2026-09-11). It does not approve on your behalf: it WRITES DOWN a decision a human already took, and approval_reference is where you point at the message they took it in. Never call this off your own judgement and never invent a reference to satisfy the field — no server can tell a made-up pointer from a real one, which is precisely why the queue exists. If nobody told you to publish, leave the post in awaiting_review and say it is waiting on a person. Restricted to super admins; everybody else is refused and has to approve in the UI. Approving IS publishing: the same call sends the post to Zernio, scheduled for its scheduled_for (a time that already passed goes out now), and Zernio publishes it then. If Zernio refuses, this call fails and the post stays approved but NOT scheduled — fix what the error names and retry with dispatch_post. If the error says it cannot tell whether Zernio received the post, do NOT retry: tell the person to check Zernio.
dispatch_postApproving a post already sends it to Zernio — you do not need this after approve_post or after a person approves in the UI. Use it only to RETRY a post that is approved but not scheduled, because Zernio refused it at approval time (the approval error said so). Fix what that error named first, or the retry fails the same way. The server refuses anything that is not approved with a recorded approver, so this can never publish something nobody cleared. A post whose send outcome is unknown stays dispatched without a Zernio id and is refused here on purpose, so it is never published twice — tell the person to check Zernio. Zernio publishes at scheduled_for; a time that already passed goes out now, and the calendar is updated to match.
unschedule_postStop a post that Zernio is holding from publishing: the server cancels it in Zernio FIRST and only then rewrites the row, which comes back as awaiting_review with the approval trail cleared. It is NOT a rejection and NOT a shortcut around the queue — it only moves a post BACKWARDS, into human review, and someone has to approve it again for it to be scheduled at all. Judging the post is still not something this server does. A new scheduled_for is required because it is what the post holds while it waits (and it has to be in the future). This only works while Zernio still HOLDS the post: once it published — which includes the last few minutes before scheduled_for, when Zernio may already be sending — nothing here takes it back, and the call is refused saying so rather than reporting a cancel that did not happen. Deleting what is already on the network is done in the network itself, by a person.
reject_postMove a post that YOU created from awaiting_review to rejected — for example a smoke or test post, or a post the person told you to drop. It refuses any post created by somebody else: rejecting another person’s post is a judgement on content, and a person does that in the dfl-campaigns UI. It also refuses a draft (delete it with delete_post), an approved post, and a scheduled or published post (unschedule_post takes a scheduled post back to the queue). The reason is stored as the post’s review_notes. A rejected post never publishes; delete_post then removes it.
delete_postPermanently delete a post that YOU created, while it is a draft or rejected — for example a smoke or test post. The post and its channels go; this cannot be undone. It refuses a post created by somebody else, a post in the review queue (reject it with reject_post first), and every approved, scheduled or published post.
archive_postHide an approved or PUBLISHED post from dfl-campaigns — the calendar, the profile pages (recent posts, top posts, cadence, counts) and analytics — while KEEPING its metric snapshots in the database. Use it for a published post that should not be in the lists, for example a test post that already went live. It is a soft delete that records who archived the post and why; it does not delete anything and it does not touch Zernio or the social network (the post stays live there, or has already expired). There is no un-archive tool. Restricted to global admins (IAM level 80 or higher): everybody else gets 403. It refuses a draft or rejected post (use delete_post), a post in the review queue (reject_post, then delete_post), and a post still scheduled in Zernio (unschedule_post first). Only call it when a person asked for the post to go, and put where they asked in the reason.
set_post_keywordsReplace the SEO keywords linked to a post, in any status — including posts already dispatched, so older content can be mapped to keywords. It never changes the post itself and never touches its approval. The first keyword is the primary one.
set_post_content_tagWrite media_group + archetype on ONE post, in any status — published posts included. It writes those two fields and nothing else: title, text, media, schedule and approval stay as they are. The creator of a post may tag it; any other post needs a global admin (IAM level >= 80). The pair must match the content-visual taxonomy (e.g. vertical-short + talking-head; archetype null is a media-group-only tag). Pass dry_run: true to see the before/after and the authority check without writing.
draft_story_sequence_for_reviewWrite an ordered Instagram Story sequence: one post per video part, published in index order a few minutes apart. Get media_ids from the studio tool split_export_for_stories, and pass them IN THAT ORDER — the order of the list is the order on Instagram. A single Story (one video of 3 to 60 s) is a sequence of 1: pass one media id. Instagram only, exactly one account. Every part lands in awaiting_review and NOTHING is published until a person approves the sequence (in the dfl-campaigns UI, or through approve_story_sequence with the reference of the message where the person said so). Optional user_tags: [{username, x?, y?}], up to 3 (our limit). It tags other Instagram accounts on the Stories. One list for the whole sequence: it is stored on every part and sent to Zernio as platformSpecificData.userTags, on Stories only. A leading ”@” is stripped and names are lowercased; x and y (0.0 to 1.0) come together or not at all. Zernio (docs.zernio.com/platforms/instagram): “Images require x/y (0.0 to 1.0); Reels and videos ignore coordinates; Stories take them optionally.” The tagged account must be a public Business or Creator account. WHAT INSTAGRAM RENDERS ON A STORY WAS NOT VERIFIED: it may be a plain tag and not the interactive mention sticker. Do not promise the person a clickable mention. The tags are fixed at draft time and are shown on the review page and in get_story_sequence (sequence.user_tags, parts[].user_tags). Until the dfl-schema migration that adds campaigns.posts.story_user_tags is applied, a call with user_tags answers 503 story_sequence_schema_missing and creates nothing; a call without user_tags works as before. Returns the sequence and review_url: send that link to the person who reviews it.
get_story_sequenceOne Story sequence with every part in index order: status, scheduled_for, updated_at, the approval trail and the Zernio id, and the user tags (sequence.user_tags and parts[].user_tags; absent when the dfl-schema column story_user_tags is not applied yet). Read it before approve_story_sequence, and show the person what they approve.
approve_story_sequenceRecord that a PERSON cleared this whole Story sequence for publishing (show them its user_tags first: the tags go to Instagram with the Stories, and what Instagram renders was not verified), when they said so to you instead of clicking in the dfl-campaigns UI. It does not approve on your behalf: it WRITES DOWN a decision a human already took, and approval_reference is where you point at the message they took it in. Call it ONLY with an explicit human approval and the reference of that message. Never call this off your own judgement and never invent a reference to satisfy the field — no server can tell a made-up pointer from a real one. If nobody told you to publish, leave the sequence in awaiting_review and say it is waiting on a person. Restricted to super admins; everybody else is refused and has to approve in the UI. Approving IS publishing: the same call sends the parts to Zernio as Stories, in index order. If one part fails, the later parts are not sent (the order is kept); the answer says which part and what to do. If it says nobody can tell whether Zernio received a part, do NOT retry: tell the person to check Zernio.
check_story_sequence_orderRead from Zernio when each part of the sequence was published, and compare the order. check.ok is true only when every checked part went out after the part before it. check.violations names the parts out of order; check.incomplete names the parts that are not published yet (check again later). parts[].outcome: ok = live (publishedUrl is the live link), pending = Zernio holds it, not_sent = never sent (post_status says approved or awaiting_review; it is not a refusal), rejected = Zernio refused it or reports it failed, unknown = check Zernio. It sends nothing. It records each part that Zernio reports live as published, with its live URL.
retry_story_sequenceUse it only after approve_story_sequence (or a later retry) stopped at a part that Zernio rejected or refused. It sends the parts that are approved but not scheduled, in index order. It never approves anything: the server refuses a part that no person cleared. action “none” means there was nothing to send. A 409 retry_stopped means the server will not retry — for example a part whose send outcome is unknown. Then tell the person to check Zernio.
get_business_unit_voiceREAD the brand VOICE of a business unit before you write any post copy — the writing patterns that say how that BU sounds. Source is strategy.writing_patterns, the single canonical store, read through the strategy MCP tool list_writing_patterns with your own JWT. Call it after list_post_business_units and BEFORE draft_post_for_review: a Reel written without it is written in a voice you guessed. Each row is one “slot” (e.g. “book”, “youtube”); the free-form pattern jsonb carries description, toneAxes, vocabularyDo / vocabularyAvoid and examplePairs. Optionally narrow to one slot. This tool is READ-ONLY: authoring a voice happens in BM Canvas or on the strategy MCP.
list_campaign_accountsList each platform and Zernio account combination that has stored post metric snapshots. Accounts stay separate even when they use the same platform. An RLS-safe database RPC excludes soft-deleted posts before it computes the summaries.
rank_account_postsRank one Zernio account’s campaigns posts. lifetime uses the latest absolute counter. period_gain subtracts the baseline from the latest observation inside the requested period and can be negative. The RLS-safe database RPC computes and bounds the ranking.
get_post_metric_historyReturn stored daily absolute counters for one campaigns post. Account and platform targets are required so separate publication targets are never combined. Reads fail explicitly above 10,000 visible snapshots; use a smaller date range.
get_account_analyticsThe last 30 São Paulo days of ONE Zernio account on ONE platform, the same numbers as the Analytics page of dfl-campaigns. daily: for each day, the sum over this account’s posts of each post’s latest cumulative views on or before that day (a post counts from its first snapshot in the window). top: the 5 posts with the most latest views. median_views: median latest views of posts dispatched with scheduled_for inside the window. Never add two accounts or platforms together; call once per account. truncated=true means the page cap was hit and totals may be low.
list_social_accountsList campaigns.social_accounts: each Zernio account with its business unit (name, logo) and its owner (name, avatar). All filters combine with AND. unmapped_only returns the accounts with no business_unit_id. Write a mapping with set_social_account_mapping.
set_social_account_mappingCreate or update the campaigns.social_accounts row of one Zernio account (idempotent UPSERT on zernio_account_id). It maps the account to a business unit (strategy.business_units) and to the person who owns it (public.profiles). An omitted optional field keeps its stored value; an explicit null clears business_unit_id or owner_user_id. Take zernio_account_id, zernio_profile_id and platform from list_zernio_accounts. The BU must exist: find it with list_business_units on the strategy MCP, or create a creator BU there with create_business_unit — this server does not create BUs. Runs with your JWT; RLS allows the write only to global admins.

List post calendar business units

The business units the post calendar can schedule for — strategy.business_units, archived ones excluded. Call this FIRST: every other tool takes a business_unit_id (a uuid), never a name like “itera”.

Takes no parameters.

List Zernio profiles and their accounts

Who publishes where: each Zernio profile (a person or the company) with its connected accounts per network, plus the accounts that belong to no profile. Call this FIRST when drafting a post: draft_post_for_review requires zernio_profile_id — the profile of the person or brand the post goes out as (“Criador” in the dfl-campaigns UI). A post is NOT limited to that profile’s own accounts — the app allows channels across profiles (e.g. the brand DevFellowship plus a team member’s personal accounts); the profile just names who the post is mainly for.

Takes no parameters.

List connected Zernio accounts

The social accounts connected in Zernio, each with the id a channel needs to actually publish. Call this BEFORE draft_post_for_review whenever the post has channels: Zernio publishes per ACCOUNT, and there is more than one account on the same platform (a company profile and a personal one), so “instagram” alone does not say where the post goes out. A channel drafted without its zernio_account_id is dropped at dispatch, and the review queue cannot add the account afterwards — that post has to be redone.

Takes no parameters.

List calendar posts

Read the post calendar, ordered by scheduled_for. Without business_unit_id it is the consolidated view across every BU. Pass status: “awaiting_review” to read the human review queue — everything the AI wrote that is still waiting on a person. assignee_id and zernio_profile_id narrow it to one person or one profile; while those columns do not exist yet the filter is skipped and notes says so. Each post carries assignment; pass include_metrics: true to attach latest_metrics per platform and account. Each post carries archetype and media_group (its content-format tag; null when unset).

ParameterTypeRequiredDescription
business_unit_idstringnoFilter to one BU (from list_post_business_units)
statusenumnoFilter by lifecycle state One of: draft, awaiting_review, approved, rejected, dispatched.
scheduled_fromstringnoOnly posts scheduled at/after this ISO date-time
scheduled_tostringnoOnly posts scheduled at/before this ISO date-time
assignee_idstringnoOnly posts assigned to this user id
zernio_profile_idstringnoOnly posts linked to this Zernio profile
include_metricsbooleannoAttach latest_metrics per platform and account to each post (never summed across platforms) Default: false.
limitnumbernoDefault: 50.

List Core Daily topics per person

What each person said in the last N days, merged per person from the Discord channel #core-daily-updates (primary) and the Core Daily meeting transcripts (when there was one). Each line has source (“channel” with a Discord url, or “meeting”); lines under 8 words and repeats are removed, at most 30 per person, newest first. sources says which side answered. Read-only. Use it to propose one draft per person: each line is raw speech, so rewrite it into a hook before calling draft_post_for_review, and pass assignee_id = user_id when it is present (user_id is only set on an exact name match with a member).

ParameterTypeRequiredDescription
daysnumbernoWindow in days, 1 to 30 Default: 7.
speakerstringnoOnly this speaker (case and accents ignored), as shown in speaker

List suggested topics (Curadoria board)

Read the suggested topics on the Curadoria board of /posts/pautas in dfl-campaigns. A suggested topic (a curated content topic, “pauta” in Portuguese) is an idea for content that a person or an agent put on the board and that later turns into one or many posts. Each topic has a title, briefing, status (idea, todo, awaiting_content, in_production, awaiting_approval, posted, standby), optional due_date, formats, channels, reference_links, business_unit_id, assignee_id and a url to its card. Active topics are returned by default; pass archived=true for the archived ones. Filters are applied after the read. Read-only. Call it before create_suggested_topic so you do not add a duplicate. If the answer says the board is not live on this deployment, do not retry.

ParameterTypeRequiredDescription
statusenumnoOnly topics in this column One of: idea, todo, awaiting_content, in_production, awaiting_approval, posted, standby.
assignee_idstringnoOnly topics assigned to this user
business_unit_idstringnoOnly topics of this BU (from list_post_business_units)
archivedbooleannotrue reads the archived topics instead of the active ones Default: false.

Add a suggested topic to the Curadoria board

Add one suggested topic to the Curadoria board of /posts/pautas in dfl-campaigns. A suggested topic (a curated content topic, “pauta” in Portuguese) is an idea for content that a person or an agent proposes and that later turns into one or many posts. Creating one posts NOTHING and drafts no post: it only puts a card on the board, marked as created by an agent (origin “mcp”), for a person to pick up. To draft a post use draft_post_for_review. The status defaults to idea. Run list_suggested_topics first to avoid duplicates. The answer carries the card url. A refusal (not available on this deployment, a rule, a missing BU or user) says do not retry; only a temporary failure may be retried.

ParameterTypeRequiredDescription
titlestringyesShort name of the topic, as it shows on the card
briefingstringnoWhat the content is about and the angle (up to 5000 characters)
statusenumnoBoard column; omitted means idea One of: idea, todo, awaiting_content, in_production, awaiting_approval, posted, standby.
due_datestringnoDay the content is wanted, YYYY-MM-DD
formatsenum[]noContent formats
channelsenum[]noNetworks the content is for
reference_linksstring[]nohttp(s) links that inspired or back the topic
business_unit_idstringnoWhich BU the topic is for (from list_post_business_units)
assignee_idstringnoThe user expected to produce it

Edit a suggested topic on the Curadoria board

Edit the content of one suggested topic (“pauta”) on the Curadoria board of /posts/pautas in dfl-campaigns: title, briefing, due date, formats, channels, reference links, BU or assignee. Any member can edit any topic. Send only the fields to change; an omitted field keeps its value, and null clears briefing, due_date, business_unit_id or assignee_id. Arrays (formats, channels, reference_links) replace the whole list. It does NOT change the column or the order: use move_suggested_topic for that, and archive_suggested_topic to archive. It refuses an empty change, an unknown field and an unknown id. Get the id from list_suggested_topics. A refusal says do not retry; only a temporary failure may be retried.

ParameterTypeRequiredDescription
idstringyesThe suggested topic id (from list_suggested_topics)
titlestringnoNew title
briefingstringnoNew briefing; null clears it
due_datestringnoDay the content is wanted, YYYY-MM-DD; null clears it
formatsenum[]noReplaces the formats
channelsenum[]noReplaces the channels
reference_linksstring[]noReplaces the http(s) reference links
business_unit_idstringnoBU from list_post_business_units; null clears it
assignee_idstringnoThe user expected to produce it; null clears it

Move a suggested topic to a column or position

Move one suggested topic (“pauta”) to a column of the Curadoria board of /posts/pautas in dfl-campaigns and, optionally, to a place inside it. status is the target column (it may be the current one, to only reorder). Without before_id/after_id the card goes to the end of the column; with before_id it lands right above that card, with after_id right below it (send one, never both). The neighbour must be an active topic already in the target column. Any member can move any topic. It does not edit content (update_suggested_topic) and does not touch posts. A refusal says do not retry; only a temporary failure may be retried.

ParameterTypeRequiredDescription
idstringyesThe suggested topic to move (from list_suggested_topics)
statusenumyesTarget column One of: idea, todo, awaiting_content, in_production, awaiting_approval, posted, standby.
before_idstringnoPlace the card right above this one
after_idstringnoPlace the card right below this one

Archive or restore a suggested topic

Archive one suggested topic (“pauta”) of the Curadoria board of /posts/pautas in dfl-campaigns, or bring an archived one back with restore=true. Archiving hides the card from the board but keeps it, and it is reversible: this is the safe way to take a topic off the board. Any member can archive or restore any topic. To remove one for good use delete_suggested_topic. Archived topics are read with list_suggested_topics archived=true. A refusal (unknown id, not available on this deployment) says do not retry; only a temporary failure may be retried.

ParameterTypeRequiredDescription
idstringyesThe suggested topic id (from list_suggested_topics)
restorebooleannotrue brings an archived topic back to the board; default archives Default: false.

Permanently delete a suggested topic you created

Permanently delete one suggested topic (“pauta”) from the Curadoria board of /posts/pautas in dfl-campaigns. It cannot be undone. Only the creator of the topic or an admin may delete it: the server decides that, from the caller session, and refuses everybody else with “do not retry” (ask the creator or an admin instead). To take a topic off the board without losing it, use archive_suggested_topic. Pass confirm_title equal to the topic’s current title, exactly as list_suggested_topics shows it; a different title, or an id that is not on the board, deletes nothing. Posts already drafted from the topic are not deleted.

ParameterTypeRequiredDescription
idstringyesThe suggested topic id (from list_suggested_topics)
confirm_titlestringyesThe topic’s current title, copied exactly: the guard against deleting the wrong card

Get one calendar post

One post with its channels, its lifecycle state and its approval trail — who approved it and when, and whether it was already dispatched to Zernio. Also returns assignment (zernio_profile_id, assignee_id; null while those columns do not exist) and latest_metrics: the latest views/likes/comments per platform AND Zernio account, never summed across platforms. The post always carries archetype and media_group (its content-format tag; null when unset or while the columns do not exist). live is { published_at, published_url } once dfl-campaigns read the post LIVE in Zernio (status published): the link to the post on the network. null while it is not known live. Use it to answer “how did this post do” and “where is it”.

ParameterTypeRequiredDescription
post_idstringyes—

Draft a post into the human review queue

Write one post into the calendar. It lands in awaiting_review and NOTHING leaves this server until a person opens the queue in dfl-campaigns, reads it and approves it — you cannot clear it yourself; approve_post only records a decision a person already gave you. Schedule the time you want it to go out; a channel only reaches its network if it carries the connected zernio_account_id. An Instagram Reel cover is optional: pass cover_media_id (the COVER_MEDIA_ID from the thumbify-reel-cover skill) or cover_url (a Thumbify render URL), or Instagram uses frame 0 of the video. A cover that is not a Thumbify cover is refused at approval. The Lesson Studio cover slide is NOT carried over. A carousel (Instagram, or a TikTok photo carousel) is either media_ids (existing public.media ids) or media_urls (rendered images, e.g. from render_composition_images; dfl-campaigns registers them as your media). A single image is media_id; TikTok accepts it as a photo post (JPEG, PNG or WebP, up to 20 MB each).

ParameterTypeRequiredDescription
business_unit_idstringyesWhich BU publishes it (from list_post_business_units)
titlestringyesInternal title — how the post shows up in the calendar
bodystringyesThe text that goes out to the network
scheduled_forstringyesISO date-time the post should be published at
youtube_visibilityenumnoHow the post lands on YouTube and TikTok. Omitted means private — the closed default, on purpose: a post that goes out more public than intended cannot be taken back, while a private one is one click away from being opened. On TikTok (video or photo) it sets the privacy level: private = only the creator, unlisted = followers only, public = everyone — so the default private means a TikTok post only the creator can see. Only matters when a channel is youtube or tiktok. One of: private, unlisted, public.
link_urlstringnoINTERNAL note only — this URL is NOT published. The dispatch sends title, body and media to Zernio and nothing else, so a link (and any utm_ it carries) reaches the network ONLY if it is written inside body. The dfl-campaigns form stopped offering this field for that reason; it is kept here for rows written through the API. Put the link in body.
media_idstringnopublic.media id — never a raw bucket URL; media is served by media.devfellowship.com/:id
media_idsstring[]noCarousel (Instagram, or TikTok photo carousel): ordered public.media ids of 2 to 10 images; use instead of media_id. Never a single image: one image is media_id.
media_urlsstring[]noCarousel (Instagram, or TikTok photo carousel) from rendered images: 2 to 10 public devfellowship S3 media/ PNG/JPG URLs (e.g. render_composition_images output) in order; use instead of media_ids. Never together with media_id or media_ids.
cover_media_idstringnoReel cover: another public.media id (JPEG or PNG, 1080x1920) holding a Thumbify cover (reel-cover-v<N>-…) — any other file is refused at approval. Not the video. Omitted means Instagram uses frame 0. Dispatch sends this as instagramThumbnail.
cover_urlstringnoReel cover as a URL, in place of cover_media_id: a public JPEG/PNG of the devfellowship S3 bucket under media/ — typically a Thumbify render output_url (media/renders/<template>/<ts>.png). dfl-campaigns registers it as public.media (owned by you) and stores that id. Other hosts are refused. Never both.
instagram_collaboratorsstring[]noInstagram co-authors: up to 3 usernames, without the @. A co-authored post appears in the feed AND the grid of BOTH accounts, with both handles in the header and the reach added together — it is what makes a post published by a personal profile also work for the company account, without reposting. Naming the @ in the caption does NOT do this; that is only text. ⚠️ Tagging is not publishing: the tagged account gets an INVITE and has to accept it in the Instagram app, and until it does the post does not appear there. Neither Zernio nor Meta can accept for us. Dispatch sends these as collaborators, so they must be on the post BEFORE dispatch — they cannot be added to a post already sent.
channelsobject[]noNetworks this post goes out through. The same content on several networks or accounts — even across profiles and business units (e.g. TikTok of a person plus YouTube Shorts of the brand) — is ONE call with several entries here, never one call per channel: each extra post is one more approval for the reviewer. Default: [].
zernio_profile_idstringyesZernio profile of the person or brand this post goes out as (“Criador” in the dfl-campaigns UI). Required on every post — get it from list_zernio_profiles.
assignee_idstringnoWho is expected to produce the content (“Solicitante” in the UI). null unassigns it.
keyword_idsstring[]noSEO keywords (strategy.keywords ids, from list_keywords on the strategy MCP) this post was written for, same business unit as the post. The first one is the primary keyword: the post’s views count for it. Replaces the whole set; an empty array unlinks all.
archetypestringnoContent-format archetype of the post, as a lowercase slug (e.g. “talking-head-hook”), max 64 characters. Requires media_group. null clears it.
media_groupenumnoMedia group of the post: vertical-short | long-video | micro-clips | ephemeral-daily | swipe-static. Required when archetype is set. null clears it (only when the post has no archetype). One of: vertical-short, long-video, micro-clips, ephemeral-daily, swipe-static.

Revise a post that has not been approved yet

Rewrite a post that is still in the queue — typically after a reviewer left review_notes, or when a rejected post is being redone. Only draft, awaiting_review and rejected posts can be revised: once a person has approved it, the text they read is the text that goes out. Status is never changed here.

ParameterTypeRequiredDescription
post_idstringyes—
titlestringno—
bodystringno—
scheduled_forstringnoNew ISO date-time
link_urlstringnoINTERNAL note only, never published — the dispatch sends title, body and media to Zernio and nothing else. To change the link the network actually sees, edit body. null clears this note.
media_idstringnonull clears the media
cover_media_idstringnoReel cover (JPEG/PNG public.media) holding a Thumbify cover — any other file is refused at approval. null clears it, and Instagram then uses frame 0. Not the video.
cover_urlstringnoReel cover as a URL, in place of cover_media_id: a public JPEG/PNG of the devfellowship S3 bucket under media/ — typically a Thumbify render output_url (media/renders/<template>/<ts>.png). dfl-campaigns registers it as public.media (owned by you) and stores that id. Other hosts are refused. Never both. Replaces the cover.
youtube_visibilityenumnoNew visibility — private, unlisted or public. Also sets TikTok privacy: private = only the creator, unlisted = followers only, public = everyone. One of: private, unlisted, public.
instagram_collaboratorsstring[]noReplaces the co-author list. An empty array clears it. Instagram co-authors: up to 3 usernames, without the @. A co-authored post appears in the feed AND the grid of BOTH accounts, with both handles in the header and the reach added together — it is what makes a post published by a personal profile also work for the company account, without reposting. Naming the @ in the caption does NOT do this; that is only text. ⚠️ Tagging is not publishing: the tagged account gets an INVITE and has to accept it in the Instagram app, and until it does the post does not appear there. Neither Zernio nor Meta can accept for us. Dispatch sends these as collaborators, so they must be on the post BEFORE dispatch — they cannot be added to a post already sent.
zernio_profile_idstringnoZernio profile of the person or brand this post goes out as (“Criador” in the dfl-campaigns UI), from list_zernio_profiles. null unlinks it.
assignee_idstringnoWho is expected to produce the content (“Solicitante” in the UI). null unassigns it.
archetypestringnoContent-format archetype of the post, as a lowercase slug (e.g. “talking-head-hook”), max 64 characters. Requires media_group. null clears it.
media_groupenumnoMedia group of the post: vertical-short | long-video | micro-clips | ephemeral-daily | swipe-static. Required when archetype is set. null clears it (only when the post has no archetype). One of: vertical-short, long-video, micro-clips, ephemeral-daily, swipe-static.

Record a human approval taken outside the review queue

Record that a PERSON cleared this post for publishing, when they said so to you instead of clicking in the dfl-campaigns UI — the case the queue had no answer for (Tainan asked for a Reel over Telegram, 2026-09-11). It does not approve on your behalf: it WRITES DOWN a decision a human already took, and approval_reference is where you point at the message they took it in. Never call this off your own judgement and never invent a reference to satisfy the field — no server can tell a made-up pointer from a real one, which is precisely why the queue exists. If nobody told you to publish, leave the post in awaiting_review and say it is waiting on a person. Restricted to super admins; everybody else is refused and has to approve in the UI. Approving IS publishing: the same call sends the post to Zernio, scheduled for its scheduled_for (a time that already passed goes out now), and Zernio publishes it then. If Zernio refuses, this call fails and the post stays approved but NOT scheduled — fix what the error names and retry with dispatch_post. If the error says it cannot tell whether Zernio received the post, do NOT retry: tell the person to check Zernio.

ParameterTypeRequiredDescription
post_idstringyesThe post the person cleared (from list_posts with status: “awaiting_review”)
approval_referencestringyesWhere the human said “publish it”, in their words or as a locator: “Telegram msg 18508”. The only trace of the person on an approval with no click — it must point at something real that someone auditing this post later can go read.
expected_updated_atstringyesThe post’s updated_at exactly as you read it (get_post / list_posts) when you showed it to the person. If the post was revised since, the server refuses with 409: read it again and get the person’s go-ahead on the new content.
review_notesstringnoWhat they said along with it, if anything — kept on the post as the reviewer note

Retry scheduling an approved post in Zernio

Approving a post already sends it to Zernio — you do not need this after approve_post or after a person approves in the UI. Use it only to RETRY a post that is approved but not scheduled, because Zernio refused it at approval time (the approval error said so). Fix what that error named first, or the retry fails the same way. The server refuses anything that is not approved with a recorded approver, so this can never publish something nobody cleared. A post whose send outcome is unknown stays dispatched without a Zernio id and is refused here on purpose, so it is never published twice — tell the person to check Zernio. Zernio publishes at scheduled_for; a time that already passed goes out now, and the calendar is updated to match.

ParameterTypeRequiredDescription
post_idstringyesThe approved, not yet scheduled post (list_posts with status: “approved”)

Take a scheduled post back out of Zernio

Stop a post that Zernio is holding from publishing: the server cancels it in Zernio FIRST and only then rewrites the row, which comes back as awaiting_review with the approval trail cleared. It is NOT a rejection and NOT a shortcut around the queue — it only moves a post BACKWARDS, into human review, and someone has to approve it again for it to be scheduled at all. Judging the post is still not something this server does. A new scheduled_for is required because it is what the post holds while it waits (and it has to be in the future). This only works while Zernio still HOLDS the post: once it published — which includes the last few minutes before scheduled_for, when Zernio may already be sending — nothing here takes it back, and the call is refused saying so rather than reporting a cancel that did not happen. Deleting what is already on the network is done in the network itself, by a person.

ParameterTypeRequiredDescription
post_idstringyesThe scheduled post (from list_posts with status: “dispatched”)
scheduled_forstringyesNew ISO date-time the post waits for in the queue. Must be in the future — the server refuses a date that already passed.
review_notesstringnoWhy it was pulled, kept on the post as the reviewer note for whoever reads it next

Withdraw one of your own posts from the review queue

Move a post that YOU created from awaiting_review to rejected — for example a smoke or test post, or a post the person told you to drop. It refuses any post created by somebody else: rejecting another person’s post is a judgement on content, and a person does that in the dfl-campaigns UI. It also refuses a draft (delete it with delete_post), an approved post, and a scheduled or published post (unschedule_post takes a scheduled post back to the queue). The reason is stored as the post’s review_notes. A rejected post never publishes; delete_post then removes it.

ParameterTypeRequiredDescription
post_idstringyesYour post, from list_posts with status: “awaiting_review”
reasonstringyesWhy the post leaves the queue, kept as review_notes: “smoke test post, not for publishing”

Delete one of your own draft or rejected posts

Permanently delete a post that YOU created, while it is a draft or rejected — for example a smoke or test post. The post and its channels go; this cannot be undone. It refuses a post created by somebody else, a post in the review queue (reject it with reject_post first), and every approved, scheduled or published post.

ParameterTypeRequiredDescription
post_idstringyesYour draft or rejected post

Archive a published post (admin only) — hide it, keep its metrics

Hide an approved or PUBLISHED post from dfl-campaigns — the calendar, the profile pages (recent posts, top posts, cadence, counts) and analytics — while KEEPING its metric snapshots in the database. Use it for a published post that should not be in the lists, for example a test post that already went live. It is a soft delete that records who archived the post and why; it does not delete anything and it does not touch Zernio or the social network (the post stays live there, or has already expired). There is no un-archive tool. Restricted to global admins (IAM level 80 or higher): everybody else gets 403. It refuses a draft or rejected post (use delete_post), a post in the review queue (reject_post, then delete_post), and a post still scheduled in Zernio (unschedule_post first). Only call it when a person asked for the post to go, and put where they asked in the reason.

ParameterTypeRequiredDescription
post_idstringyesThe approved or published post to hide
reasonstringyesWhy the post leaves the lists, pointing at the decision: “Test Stories, Tainan TG msg 19998/20007”. Stored on the post for whoever audits it later.

Link a post to the SEO keywords it was written for

Replace the SEO keywords linked to a post, in any status — including posts already dispatched, so older content can be mapped to keywords. It never changes the post itself and never touches its approval. The first keyword is the primary one.

ParameterTypeRequiredDescription
post_idstringyes—
keyword_idsstring[]yes—

Set the content-format tag of a post (any status)

Write media_group + archetype on ONE post, in any status — published posts included. It writes those two fields and nothing else: title, text, media, schedule and approval stay as they are. The creator of a post may tag it; any other post needs a global admin (IAM level >= 80). The pair must match the content-visual taxonomy (e.g. vertical-short + talking-head; archetype null is a media-group-only tag). Pass dry_run: true to see the before/after and the authority check without writing.

ParameterTypeRequiredDescription
post_idstringyes—
media_groupenumyesMedia group: vertical-short | long-video | micro-clips | ephemeral-daily | swipe-static One of: vertical-short, long-video, micro-clips, ephemeral-daily, swipe-static.
archetypestringyesArchetype slug of that media group: animated-explainer, talking-head, face-cam-over-screen, myth-vs-fact, quiz-spot-the-bug, long-tutorial, stories-frames, framework-carousel. null = media group only.
dry_runbooleannotrue = check and show the change, write nothing

Draft an Instagram Story sequence into the human review queue

Write an ordered Instagram Story sequence: one post per video part, published in index order a few minutes apart. Get media_ids from the studio tool split_export_for_stories, and pass them IN THAT ORDER — the order of the list is the order on Instagram. A single Story (one video of 3 to 60 s) is a sequence of 1: pass one media id. Instagram only, exactly one account. Every part lands in awaiting_review and NOTHING is published until a person approves the sequence (in the dfl-campaigns UI, or through approve_story_sequence with the reference of the message where the person said so). Optional user_tags: [{username, x?, y?}], up to 3 (our limit). It tags other Instagram accounts on the Stories. One list for the whole sequence: it is stored on every part and sent to Zernio as platformSpecificData.userTags, on Stories only. A leading ”@” is stripped and names are lowercased; x and y (0.0 to 1.0) come together or not at all. Zernio (docs.zernio.com/platforms/instagram): “Images require x/y (0.0 to 1.0); Reels and videos ignore coordinates; Stories take them optionally.” The tagged account must be a public Business or Creator account. WHAT INSTAGRAM RENDERS ON A STORY WAS NOT VERIFIED: it may be a plain tag and not the interactive mention sticker. Do not promise the person a clickable mention. The tags are fixed at draft time and are shown on the review page and in get_story_sequence (sequence.user_tags, parts[].user_tags). Until the dfl-schema migration that adds campaigns.posts.story_user_tags is applied, a call with user_tags answers 503 story_sequence_schema_missing and creates nothing; a call without user_tags works as before. Returns the sequence and review_url: send that link to the person who reviews it.

ParameterTypeRequiredDescription
business_unit_idstringyesWhich BU publishes it (from list_post_business_units)
titlestringyesInternal title. Each part shows up in the calendar as “<title> (i/N)”; a single Story keeps “<title>”.
bodystringyesThe text that goes with the Stories
zernio_account_idstringyesThe connected Instagram Zernio account that publishes (from list_zernio_accounts)
media_idsstring[]yespublic.media ids of the Story videos, 1 to 25, in order — as split_export_for_stories returns them. One id = a single Story.
first_atstringnoISO date-time of part 0. Omitted means now + 5 minutes. The next parts follow at gap_minutes intervals.
gap_minutesnumbernoMinutes between two parts, 1 to 5. Omitted means the server default (2).
user_tagsobject[]noInstagram accounts to tag on every Story of the sequence, up to 3: [{username, x?, y?}]. x and y are 0.0 to 1.0, both or neither. Public Business or Creator accounts only. What Instagram shows on a Story was not verified.

Get one Instagram Story sequence

One Story sequence with every part in index order: status, scheduled_for, updated_at, the approval trail and the Zernio id, and the user tags (sequence.user_tags and parts[].user_tags; absent when the dfl-schema column story_user_tags is not applied yet). Read it before approve_story_sequence, and show the person what they approve.

ParameterTypeRequiredDescription
groupstringyesThe sequence group id (the group that draft_story_sequence_for_review returned)

Record a human approval of a Story sequence taken outside the review queue

Record that a PERSON cleared this whole Story sequence for publishing (show them its user_tags first: the tags go to Instagram with the Stories, and what Instagram renders was not verified), when they said so to you instead of clicking in the dfl-campaigns UI. It does not approve on your behalf: it WRITES DOWN a decision a human already took, and approval_reference is where you point at the message they took it in. Call it ONLY with an explicit human approval and the reference of that message. Never call this off your own judgement and never invent a reference to satisfy the field — no server can tell a made-up pointer from a real one. If nobody told you to publish, leave the sequence in awaiting_review and say it is waiting on a person. Restricted to super admins; everybody else is refused and has to approve in the UI. Approving IS publishing: the same call sends the parts to Zernio as Stories, in index order. If one part fails, the later parts are not sent (the order is kept); the answer says which part and what to do. If it says nobody can tell whether Zernio received a part, do NOT retry: tell the person to check Zernio.

ParameterTypeRequiredDescription
groupstringyesThe sequence group id (the group that draft_story_sequence_for_review returned)
approval_referencestringyesWhere the human said “publish it”, in their words or as a locator: “Telegram msg 18508”. The only trace of the person on an approval with no click — it must point at something real that someone auditing this sequence later can go read.
expected_updated_atobjectnoMap of every part id to its updated_at, exactly as you read it (get_story_sequence) when you showed the sequence to the person. If a part was revised since, the server refuses with 409: read it again and get the go-ahead on the new content. Omitted means the tool reads the sequence now and uses its current values.
review_notesstringnoWhat they said along with it, if anything — kept as the reviewer note
first_atstringnoISO date-time of part 1. A future time is honored. A time earlier than now + 30 s moves to now + 30 s. The answer returns first_at: the time asked for, the time sent, and whether it moved. Omitted means the stored time of part 1.
gap_minutesnumbernoMinutes between two parts, 1 to 5. Omitted means the server default (2).

Check that the Stories of a sequence went out in order

Read from Zernio when each part of the sequence was published, and compare the order. check.ok is true only when every checked part went out after the part before it. check.violations names the parts out of order; check.incomplete names the parts that are not published yet (check again later). parts[].outcome: ok = live (publishedUrl is the live link), pending = Zernio holds it, not_sent = never sent (post_status says approved or awaiting_review; it is not a refusal), rejected = Zernio refused it or reports it failed, unknown = check Zernio. It sends nothing. It records each part that Zernio reports live as published, with its live URL.

ParameterTypeRequiredDescription
groupstringyesThe sequence group id (the group that draft_story_sequence_for_review returned)

Retry sending the rest of an approved Story sequence

Use it only after approve_story_sequence (or a later retry) stopped at a part that Zernio rejected or refused. It sends the parts that are approved but not scheduled, in index order. It never approves anything: the server refuses a part that no person cleared. action “none” means there was nothing to send. A 409 retry_stopped means the server will not retry — for example a part whose send outcome is unknown. Then tell the person to check Zernio.

ParameterTypeRequiredDescription
groupstringyesThe sequence group id (the group that draft_story_sequence_for_review returned)
gap_minutesnumbernoMinutes between two parts, 1 to 5. Omitted means the server default (2).

Get business unit voice

READ the brand VOICE of a business unit before you write any post copy — the writing patterns that say how that BU sounds. Source is strategy.writing_patterns, the single canonical store, read through the strategy MCP tool list_writing_patterns with your own JWT. Call it after list_post_business_units and BEFORE draft_post_for_review: a Reel written without it is written in a voice you guessed. Each row is one “slot” (e.g. “book”, “youtube”); the free-form pattern jsonb carries description, toneAxes, vocabularyDo / vocabularyAvoid and examplePairs. Optionally narrow to one slot. This tool is READ-ONLY: authoring a voice happens in BM Canvas or on the strategy MCP.

ParameterTypeRequiredDescription
business_unit_idstringyesThe BU whose voice to read (from list_post_business_units)
slotstringnoNarrow to a single voice slot, e.g. “book”, “youtube”, “pedagogia_aula”

List campaign analytics accounts

List each platform and Zernio account combination that has stored post metric snapshots. Accounts stay separate even when they use the same platform. An RLS-safe database RPC excludes soft-deleted posts before it computes the summaries.

ParameterTypeRequiredDescription
platformenumnoFilter to one social platform One of: instagram, facebook, linkedin, tiktok, youtube, x, threads, pinterest, reddit, bluesky, telegram, discord, whatsapp, google_business.

Rank posts for one account

Rank one Zernio account’s campaigns posts. lifetime uses the latest absolute counter. period_gain subtracts the baseline from the latest observation inside the requested period and can be negative. The RLS-safe database RPC computes and bounds the ranking.

ParameterTypeRequiredDescription
account_idstringyesExact Zernio account ID from list_campaign_accounts
platformenumyesExact platform from list_campaign_accounts One of: instagram, facebook, linkedin, tiktok, youtube, x, threads, pinterest, reddit, bluesky, telegram, discord, whatsapp, google_business.
metricenumyesOne of: views, likes, comments.
basisenumnoOne of: lifetime, period_gain. Default: "lifetime".
start_datestringnoPeriod start date, required for period_gain
end_datestringnoPeriod end date, required for period_gain
limitnumbernoDefault: 10.

Get post metric history

Return stored daily absolute counters for one campaigns post. Account and platform targets are required so separate publication targets are never combined. Reads fail explicitly above 10,000 visible snapshots; use a smaller date range.

ParameterTypeRequiredDescription
post_idstringyescampaigns.posts ID
account_idstringyesExact Zernio account ID
platformenumyesExact publication platform One of: instagram, facebook, linkedin, tiktok, youtube, x, threads, pinterest, reddit, bluesky, telegram, discord, whatsapp, google_business.
start_datestringno—
end_datestringno—

Get 30-day analytics for one account

The last 30 São Paulo days of ONE Zernio account on ONE platform, the same numbers as the Analytics page of dfl-campaigns. daily: for each day, the sum over this account’s posts of each post’s latest cumulative views on or before that day (a post counts from its first snapshot in the window). top: the 5 posts with the most latest views. median_views: median latest views of posts dispatched with scheduled_for inside the window. Never add two accounts or platforms together; call once per account. truncated=true means the page cap was hit and totals may be low.

ParameterTypeRequiredDescription
account_idstringyesExact Zernio account ID from list_campaign_accounts
platformenumyesExact platform from list_campaign_accounts One of: instagram, facebook, linkedin, tiktok, youtube, x, threads, pinterest, reddit, bluesky, telegram, discord, whatsapp, google_business.

List Zernio account mappings

List campaigns.social_accounts: each Zernio account with its business unit (name, logo) and its owner (name, avatar). All filters combine with AND. unmapped_only returns the accounts with no business_unit_id. Write a mapping with set_social_account_mapping.

ParameterTypeRequiredDescription
business_unit_idstringnoOnly accounts of this BU
owner_user_idstringnoOnly accounts owned by this user
zernio_profile_idstringnoOnly accounts of this Zernio profile
unmapped_onlybooleannoOnly accounts with no business_unit_id

Set the BU and owner of a Zernio account

Create or update the campaigns.social_accounts row of one Zernio account (idempotent UPSERT on zernio_account_id). It maps the account to a business unit (strategy.business_units) and to the person who owns it (public.profiles). An omitted optional field keeps its stored value; an explicit null clears business_unit_id or owner_user_id. Take zernio_account_id, zernio_profile_id and platform from list_zernio_accounts. The BU must exist: find it with list_business_units on the strategy MCP, or create a creator BU there with create_business_unit — this server does not create BUs. Runs with your JWT; RLS allows the write only to global admins.

ParameterTypeRequiredDescription
zernio_account_idstringyesZernio account id (the conflict key)
zernio_profile_idstringyesZernio profile id of the account
platformstringyesZernio platform string, e.g. instagram, youtube. Stored lowercase.
handlestringnoZernio username. Omit to keep the stored value; null clears it.
display_namestringnoZernio displayName. Omit to keep the stored value; null clears it.
avatar_urlstringnoZernio profilePicture URL. Omit to keep the stored value; null clears it.
business_unit_idstringnostrategy.business_units id. Omit to keep; null clears (account not clickable).
owner_user_idstringnoUser id of the person who owns the account (public.profiles id). Omit to keep; null clears.