> ## Documentation Index
> Fetch the complete documentation index at: https://docs.contenthero.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Planner

> Spaces, stages and cards: the content pipeline, and publishing from it.

Spaces, stages and cards: the content pipeline, and publishing from it. 20 tools.

Every tool below is exposed by the hosted MCP server at `https://mcp.contenthero.ai` and by the `@contenthero/mcp` npm package. A <Badge>write</Badge> tool changes your account; a <Badge>read</Badge> tool does not.

### `list_spaces`

<Badge>read</Badge>

List the account's SPACES. A space is the planner's top-level container: Space > Stage > Card > Post. Each space has its own stages, so two spaces can both hold a stage called 'Published'. Call this FIRST to discover which board to work in, then pass a space id to list\_stages, list\_cards or create\_card. Archived spaces are excluded unless archived is set, which lists only them.

<ParamField body name="archived" type="boolean">
  Only archived spaces. Archived spaces are excluded by default, matching the grid in the app.
</ParamField>

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="favorited" type="boolean">
  Only favorited spaces.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

<ParamField body name="order" type="`asc` \| `desc`">
  The sort direction.
</ParamField>

<ParamField body name="search" type="string">
  Search space names.
</ParamField>

<ParamField body name="sort" type="`updatedAt` \| `name` \| `cardCount`">
  The field to sort by.
</ParamField>

### `get_space`

<Badge>read</Badge>

Return one space with its live card count. Use list\_spaces to discover ids. The count excludes archived cards, and is the same number list\_spaces reports for that space.

<ParamField body name="spaceId" type="string" required>
  The space id.
</ParamField>

### `create_space`

<Badge>write</Badge>

Create a space: a new planner board with its own stages. duplicateFrom copies another space's STAGES, never its cards, so the new board arrives with the columns and none of the work. Requires the planner:write scope.

<ParamField body name="name" type="string" required>
  The space name.
</ParamField>

<ParamField body name="coverUrl" type="string">
  A cover image URL for the space tile.
</ParamField>

<ParamField body name="duplicateFrom" type="string">
  Copy this space's stages into the new one. Cards are never copied.
</ParamField>

### `update_space`

<Badge>write</Badge>

Rename a space or change its cover. This is a PATCH: a field you omit is left alone, so renaming does not disturb the cover. Pass coverUrl as an empty string to REMOVE the cover. To favorite or archive a space, use the `favorite` and `archive` tools with assetType 'space' instead. Requires the planner:write scope.

<ParamField body name="spaceId" type="string" required>
  The space id to update.
</ParamField>

<ParamField body name="coverUrl" type="string">
  A new cover image URL. Pass an empty string to remove the cover entirely.
</ParamField>

<ParamField body name="name" type="string">
  A new name.
</ParamField>

### `delete_space`

<Badge>write</Badge>

Delete a space. The server REFUSES a space that still holds cards and names the count, because the delete cascades to every card in it along with their covers, captions, posts and schedules. Archive the space instead if you want it out of the way. Requires the planner:write scope.

<ParamField body name="spaceId" type="string" required>
  The space id to delete.
</ParamField>

### `list_stages`

<Badge>read</Badge>

List one SPACE's stages, in order. ⚠️ STAGES ARE PER-SPACE: without spaceId this is the account's DEFAULT space, and two spaces can each hold a stage named 'Published' with different ids, so a stage name resolved against the wrong space is a different column. Stages are user-customizable (renamed, reordered, added, removed), so call this to discover the real stages before placing a card; pass a stage's id (most stable), slug, or name to create\_card / update\_card.

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

<ParamField body name="spaceId" type="string">
  Which space's stages, from list\_spaces. Omit only when you mean the account's default space.
</ParamField>

### `create_stage`

<Badge>write</Badge>

Create a stage (a column on one board). ⚠️ STAGES ARE PER-SPACE: without spaceId this creates on the account's DEFAULT board, which is rarely what you want once more than one space exists, so call list\_spaces first. The slug is DERIVED from the name and is not settable; a board cannot hold two columns whose names produce the same slug and the server refuses the second rather than renaming it for you. Place the column with afterId, beforeId or position (top is the first column), or name none to put it at the end. Requires the planner:write scope.

<ParamField body name="name" type="string" required>
  The column name, for example "In Review". Must contain a letter or number.
</ParamField>

<ParamField body name="afterId" type="string">
  Place it immediately after this item of the same list.
</ParamField>

<ParamField body name="beforeId" type="string">
  Place it immediately before this item of the same list.
</ParamField>

<ParamField body name="color" type="string">
  A hex color such as "#3B82F6".
</ParamField>

<ParamField body name="position" type="`top` \| `bottom`">
  Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
</ParamField>

<ParamField body name="spaceId" type="string">
  Which board, from list\_spaces. Omit only when you mean the account's default space.
</ParamField>

### `update_stage`

<Badge>write</Badge>

Rename, recolor or move one stage. This is a PATCH: a field you omit is left alone. ⚠️ spaceId is REQUIRED, because a stage id alone does not tell the server which board you mean and guessing the wrong one silently changes nothing. Renaming re-derives the slug, so renaming a column away from 'Published' also stops publishing auto-moving cards into it; a rename that collides with another column on the same board is refused. Moving names NEIGHBORS or an end, never a number: pass afterId, beforeId, or position (top is the first column). Requires the planner:write scope.

<ParamField body name="spaceId" type="string" required>
  The board this stage is on, from list\_stages or list\_spaces.
</ParamField>

<ParamField body name="stageId" type="string" required>
  The stage id to update, from list\_stages.
</ParamField>

<ParamField body name="afterId" type="string">
  Place it immediately after this item of the same list.
</ParamField>

<ParamField body name="beforeId" type="string">
  Place it immediately before this item of the same list.
</ParamField>

<ParamField body name="color" type="string">
  A new hex color such as "#3B82F6".
</ParamField>

<ParamField body name="name" type="string">
  A new name. The slug follows it automatically.
</ParamField>

<ParamField body name="position" type="`top` \| `bottom`">
  Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
</ParamField>

### `delete_stage`

<Badge>write</Badge>

Delete a stage and move its cards to another column. The server REFUSES a column that still holds cards when you name no targetStageId, and tells you how many there are: cards are never destroyed by deleting a column, and the delete and the reassignment happen in one transaction. Returns the board that is left. Requires the planner:write scope.

<ParamField body name="spaceId" type="string" required>
  The board this stage is on.
</ParamField>

<ParamField body name="stageId" type="string" required>
  The stage id to delete, from list\_stages.
</ParamField>

<ParamField body name="targetStageId" type="string">
  Where this column's cards should go. Required unless the column is empty.
</ParamField>

### `list_cards`

<Badge>read</Badge>

List one SPACE's cards (newest-updated first). ⚠️ SCOPED, NOT COMPLETE: without spaceId this lists the account's DEFAULT space only, and cards on any other board are absent with nothing in the response saying so (search misses them too). Call list\_spaces FIRST and pass spaceId unless you specifically mean the default board. Filter by status, platform, stage (id/slug/name), favorite, tag, or a title search. Call get\_card for one card's full detail (posts + assets).

<ParamField body name="archived" type="boolean">
  Only ARCHIVED cards. Archived cards are excluded by default, matching the board.
</ParamField>

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="isFavorite" type="boolean">
  Only favorited cards.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

<ParamField body name="order" type="`asc` \| `desc`">
  The sort direction.
</ParamField>

<ParamField body name="platform" type="`youtube` \| `instagram` \| `tiktok` \| `facebook` \| `linkedin` \| `x` \| `threads` \| `general`">
  Filter by the post platform.
</ParamField>

<ParamField body name="search" type="string">
  Case-insensitive title search, scoped to the chosen space.
</ParamField>

<ParamField body name="sort" type="`updatedAt` \| `createdAt` \| `scheduledAt` \| `title` \| `position`">
  The field to sort by.
</ParamField>

<ParamField body name="spaceId" type="string">
  Which space's board to list, from list\_spaces, or 'all' for every space. Omit ONLY when you mean the account's default space; omitting it does not search every board.
</ParamField>

<ParamField body name="stage" type="string">
  Filter by a stage id, slug, or name. Resolved within the chosen space. Needed for sort position, which reads that stage's own order.
</ParamField>

<ParamField body name="tag" type="string">
  Only cards carrying this tag.
</ParamField>

### `get_card`

<Badge>read</Badge>

Get one CARD in full: its fields (title, description, script, notes, status, stage, schedule), plus its posts (one per platform, which is how it publishes) and its attached assets in carousel order. An \[inspiration] asset is a tracked post attached as a reference; its line already carries the headline metrics (creator, outlier score, views, likes, duration) plus transcript and breakdown availability, and a content id to pass to get\_content for the transcript and the full Break It Down analysis. To attach one, add \{ contentId } to the assets list of update\_card; a linked editor or canvas project is added the same way with \{ projectId }.

<ParamField body name="cardId" type="string" required>
  The card id from list\_cards.
</ParamField>

### `create_card`

<Badge>write</Badge>

Create a card, the container in the content pipeline. A card holds the work (title, notes, cover) and the posts that publish it. Attach posts and media by passing `posts` and `assets` to update\_card, then publish with publish\_post. ⚠️ WITHOUT spaceId THIS LANDS ON THE ACCOUNT'S DEFAULT BOARD, which is rarely what you want once more than one space exists, so call list\_spaces first. `stage` accepts a stage id/slug/name and DECIDES the space when it is an id; a spaceId that disagrees with it is rejected rather than guessed. Requires a key with the planner:write scope.

<ParamField body name="platform" type="`youtube` \| `instagram` \| `tiktok` \| `facebook` \| `linkedin` \| `x` \| `threads` \| `general`" required>
  Primary platform for the post.
</ParamField>

<ParamField body name="title" type="string" required>
  Post title (required).
</ParamField>

<ParamField body name="afterId" type="string">
  Place it immediately after this item of the same list.
</ParamField>

<ParamField body name="beforeId" type="string">
  Place it immediately before this item of the same list.
</ParamField>

<ParamField body name="coverOutputId" type="string">
  A media token (short id, output id or first 8 characters, optionally with "-N") for the cover, resolved to its URL. Use this or coverUrl.
</ParamField>

<ParamField body name="coverUrl" type="string">
  Public URL for the post cover (the card thumbnail).
</ParamField>

<ParamField body name="position" type="`top` \| `bottom`">
  Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
</ParamField>

<ParamField body name="spaceId" type="string">
  Which board to create the card on, from list\_spaces. Omit only when you mean the account's default space.
</ParamField>

<ParamField body name="stage" type="string">
  Stage id, slug, or name. Defaults to the first stage.
</ParamField>

<ParamField body name="tags" type="string[]">
  Tag names to set on the post (must already exist; see list\_tags / create\_tag). Replaces the set.
</ParamField>

### `update_card`

<Badge>write</Badge>

Update a card: its fields (title, notes, platform, cover, stage), its POSTS (one per platform, which is how it publishes), its ASSETS (its files, links, linked projects and inspiration posts, in order), and its SCHEDULE. posts and assets are DECLARATIVE: pass the WHOLE set, because anything you leave out is removed. Posts key on platform. Assets key on id, and THE ARRAY ORDER IS THE carousel ORDER, so reordering is just sending the same ids in a different order; keep an existing asset by id, and add a new one by EXACTLY ONE reference: outputId (media: a generation, an upload or an import), assetUrl (a link), contentId (an inspiration post, shown on the card's Inspiration tab) or projectId (an editor or canvas project); get\_card reads each back under the same field name. Listing the same thing twice is refused. To attach one thing, read the card, append the entry to its assets, and send the whole list back. scheduledAt sets the time on the card AND every post (pass null to clear); give a post its own scheduledAt to override it for that platform. To publish NOW, use publish\_post. Pass spaceId to MOVE the card to another space; without a stage it lands in the target space's stage whose slug matches its current one, or that space's first stage. Pass cardIds to update several cards at once, which crossed with spaceId is how a selection moves in one call; fields that describe ONE card (title, notes, cover) still need exactly one. WRITING notes REQUIRES expectedRevision: read the card first and pass the revision it reported, or the call is refused. Requires the planner:write scope.

<ParamField body name="cardId" type="string" required>
  The card id.
</ParamField>

<ParamField body name="afterId" type="string">
  Place it immediately after this item of the same list.
</ParamField>

<ParamField body name="assets" type="object[]">
  The post's assets IN ORDER, each \{ id } to keep an existing one or ONE of \{ outputId } | \{ assetUrl, assetType? } | \{ contentId } | \{ projectId } to add. REPLACES the list; \[] clears it.
</ParamField>

<ParamField body name="beforeId" type="string">
  Place it immediately before this item of the same list.
</ParamField>

<ParamField body name="cardIds" type="string[]">
  Update several cards at once. Fields that describe ONE card (title, notes, cover) still require exactly one.
</ParamField>

<ParamField body name="coverOutputId" type="string">
  A media token (short id, output id or first 8 characters, optionally with "-N") for the cover, resolved to its URL.
</ParamField>

<ParamField body name="coverUrl" type="string">
  Public URL for the post cover.
</ParamField>

<ParamField body name="expectedRevision" type="integer">
  REQUIRED when writing notes: the revision get\_card reported for this card. A card's notes have four independent writers, each reading the whole document and writing it back, so without this the last writer silently erases the others and still gets a success. If the card changed since you read it, the call fails with a conflict carrying the current notes and revision: merge onto those and retry with the revision they came with. Never guess it or add one to it, because it only advances when notes actually change.
</ParamField>

<ParamField body name="notes" type="string">
  Working notes on the card. Plain text or markdown; tables render here. No emojis.
</ParamField>

<ParamField body name="notesEdits" type="object[]">
  Change the notes in place without resending them. Each edit is \{ append: text }, which adds to the end, or \{ find, replace }, where find must match exactly one place in the current notes. Edits apply in order, all or none, to the notes as they are when the call arrives, so no expectedRevision is needed. Prefer this to notes for any change smaller than a rewrite.
</ParamField>

<ParamField body name="platform" type="`youtube` \| `instagram` \| `tiktok` \| `facebook` \| `linkedin` \| `x` \| `threads` \| `general`">
  The card's primary platform. This is a label on the card; what actually publishes is its posts.
</ParamField>

<ParamField body name="position" type="`top` \| `bottom`">
  Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
</ParamField>

<ParamField body name="posts" type="object[]">
  The card's posts, each \{ platform, format?, connectedAccountId?, platformSpecificData?, scheduledAt?, status? }. REPLACES the set, keyed by platform; \[] detaches all.
</ParamField>

<ParamField body name="scheduledAt" type="string,null">
  ISO time to publish. Sets the card AND every post. null clears the schedule.
</ParamField>

<ParamField body name="spaceId" type="string">
  Move the card to a different space (id or slug). Without a stage, it lands in that space's stage whose slug matches its current one, or that space's first stage.
</ParamField>

<ParamField body name="stage" type="string">
  Move the card to this stage (id, slug, or name).
</ParamField>

<ParamField body name="tags" type="string[]">
  Tag names to set on the post (must already exist; replaces the set). Omit to leave tags unchanged.
</ParamField>

<ParamField body name="title" type="string">
  Rename the card. Keep it short: a long title wraps and makes the column unreadable.
</ParamField>

### `list_tags`

<Badge>read</Badge>

List the account's tags (the organizational tag library). Set a post's tags with the `tags` field on create\_card / update\_card. A tag is just a lowercase name.

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

### `create_tag`

<Badge>write</Badge>

Create a tag in the account's tag library (the name is lowercased). Tags organize posts; apply them with the `tags` field on create\_card / update\_card. Requires the planner:write scope.

<ParamField body name="name" type="string" required>
  The tag name (lowercased on save).
</ParamField>

### `update_tag`

<Badge>write</Badge>

Rename a tag (preserves its assignments on all posts). To detach a tag from one post, set that post's `tags` without it via update\_card. Requires the planner:write scope.

<ParamField body name="name" type="string" required>
  The new tag name (lowercased on save).
</ParamField>

<ParamField body name="tagId" type="string" required>
  The tag id (from list\_tags).
</ParamField>

### `delete_tag`

<Badge>write</Badge>

Delete a tag from the account's library. This DESTROYS the tag and removes it from every post it was on. To just detach a tag from one post, set that post's `tags` without it via update\_card instead. Requires the planner:write scope.

<ParamField body name="tagId" type="string" required>
  The tag id (from list\_tags).
</ParamField>

### `publish_post`

<Badge>write</Badge>

Publish a card's posts NOW: every post on the card, or only the named platform's post when `platform` is given. Each post must have a connected account. Requires a key with the publish:write scope; holding that scope is the account owner's consent to autonomous publishing. Returns one result per post.

<ParamField body name="cardId" type="string" required>
  The card id to publish.
</ParamField>

<ParamField body name="platform" type="`youtube` \| `instagram` \| `tiktok` \| `facebook` \| `linkedin` \| `x` \| `threads` \| `general`">
  Publish only this platform. Omit to publish all posts.
</ParamField>

### `list_connected_accounts`

<Badge>read</Badge>

List the social accounts the owner has connected (the publish targets), default first. Use an account's id as connectedAccountId on a post in update\_card, then publish\_post. Read-only: connecting an account is done in the ContentHero app.

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

### `get_connected_account`

<Badge>read</Badge>

Get one connected account's detail: platform, status, and capabilities. Use it to confirm a target can publish before attaching it to a post.

<ParamField body name="accountId" type="string" required>
  The connected account id from list\_connected\_accounts.
</ParamField>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.