# io.github.bitsandtea/postking-mcp (npm · postking-mcp)

PostKing MCP server for managing brands, generating posts, scheduling content, and publishing blogs.

- Trust score: 54/100 (low)
- Change this week: +21
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- remote · `mcp.postking.app`: 36/100, [markdown](https://verifymcp.io/servers/bitsandtea-postking-mcp/mcp.md), [page](https://verifymcp.io/servers/bitsandtea-postking-mcp/mcp)
- npm · `postking-mcp`: 54/100 (this document), [markdown](https://verifymcp.io/servers/bitsandtea-postking-mcp/postking-mcp.md), [page](https://verifymcp.io/servers/bitsandtea-postking-mcp/postking-mcp)

## Channel facts

- Registry: `npm`
- Package: `postking-mcp`
- Version: `1.1.7`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Supply Chain Security**: 37/100
  - Malware scan not yet available for this package.
  - Only part of the dependency tree could be resolved (96 of 100), so this covers what we could see, not the whole tree.
  - No install/post-install scripts declared.
  - Only part of the dependency tree could be resolved (96 of 100), so this covers what we could see, not the whole tree.
- **Provenance & Transparency**: 45/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 26 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 76/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 32233 tokens (~146/item across 220 items; 220 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 27/100
  - Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 99/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 96% of tool parameters carry a description.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add bitsandtea-postking-mcp -- npx -y postking-mcp
```

### Codex

```bash
codex mcp add bitsandtea-postking-mcp -- npx -y postking-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "bitsandtea-postking-mcp": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "postking-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add bitsandtea-postking-mcp --command npx --arg -y --arg postking-mcp
```

### Hermes

```yaml
mcp_servers:
  bitsandtea-postking-mcp:
    command: "npx"
    args: ["-y", "postking-mcp"]
```

### Other

```json
{
  "mcpServers": {
    "bitsandtea-postking-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "postking-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-03 (score 54, −3)

No change was recorded against any check on this day. Supply Chain Security went from 47 to 37. Other categories moved too: Stability & Change Management rose 4.

### 2026-08-02 (score 57, +32)

- [security regression] Provenance: unverified → fail
- [security improvement] Install scripts: unverified → pass
- [security improvement] Known CVEs: unverified → partial
- [functional improvement] Schema quality: unverified → good
- [functional improvement] Stability: unverified → 0.23
- [functional improvement] License: unverified → pass
- [functional improvement] Dependency health: unverified → partial
- [functional improvement] MCP protocol: unverified → pass
- [functional improvement] Maintenance: unverified → pass
- [functional] Licence: MIT

### 2026-07-31 (score 25, −8)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-27 (score 33)

First indexed and scored.

## MCP tools (220)

### `login_start` (~91 tokens)

Start the PostKing login flow. Returns a URL and short code for the user to visit in their browser. IMPORTANT: Immediately after this tool returns, show the URL + code to the user and then call `login_complete` — it will wait (polling in the background) until the user approves the request in their browser, so there is no need to ask the user to tell you when they are done.

### `login_complete` (~40 tokens)

Wait for the user to approve the PostKing login code in their browser and then save the token. Polls automatically; call this immediately after `login_start`.

### `logout` (~14 tokens)

Clear locally stored PostKing credentials.

### `whoami` (~29 tokens)

Return the profile of the currently authenticated PostKing user (email, plan, credit balance, token source).

### `list_brands` (~140 tokens)

List all brands on your PostKing account. Returns id+name+website by default (short). Use detail='medium' for description, 'full' for all raw fields. To zoom into one brand call get_brand_info with detail='full'.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `set_active_brand` (~40 tokens)

Set the active brand for this session. All subsequent tools will use this brand by default.

Input parameters:

- `brandId` (string, required): The brand ID to activate

### `get_brand_info` (~184 tokens)

Get a brand's profile. Returns core identity fields plus slim summaries: themes as [{id,title}], voiceProfiles as [{id,name,isActive,optimizedMedium}], blogAuthors as [{id,firstName,lastName}], and postCount/memberCount as numbers. Use list_themes, list_voices, list_blog_authors, list_posts, or get_brand_members for full detail on each.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…

### `create_brand` (~154 tokens)

STEP 1 of brand onboarding (manual path — no website crawl). Creates a new brand from user-supplied name/description, sets it as active, then RETURNS a `nextStep` payload telling you to ask the user which platforms they publish on. After the user answers, call `set_brand_mediums` with their picks. Ask the user for a description if they did not provide one — onboarding will fail without it.

Input parameters:

- `audience` (string): Target audience description
- `description` (string): Brand description (required if no website)
- `name` (string, required): Brand name
- `tone` (string): Writing tone, e.g. 'Bold & Direct'
- `website` (string): Website URL

### `onboard_brand` (~121 tokens)

STEP 1 of brand onboarding (website path). Crawls the site in the background and kicks off audience analysis + 10 themes. Sets the new brand as active and RETURNS a `nextStep` payload. IMMEDIATELY after this returns, ask the user which social platforms they publish on, then call `set_brand_mediums`. Do not call `get_onboarding_status` until mediums are set.

Input parameters:

- `name` (string): Brand name (inferred from site if omitted)
- `websiteUrl` (string, required): The website to crawl and analyze

### `set_brand_mediums` (~96 tokens)

STEP 2 of brand onboarding. Saves the list of platforms the brand publishes on. Call this immediately after `create_brand` or `onboard_brand` and the user has picked platforms. After this, call `get_onboarding_status` every ~15 seconds until it reports `done`.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `mediums` (array, required): Platforms the brand publishes on

### `get_brand_mediums` (~75 tokens)

Read which publishing platforms (mediums) a brand posts to. Read-only counterpart to set_brand_mediums. NOTE: Reddit is NOT a medium — Reddit is a separate repurpose module; use the reddit_* tools or dashboard_link section 'reddit'.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `get_brand_members` (~45 tokens)

List all active members of a brand with their role and user info (id, name, email).

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)

### `get_onboarding_status` (~171 tokens)

STEP 3 of brand onboarding. Polls background analysis + theme generation. detail='short' returns status only; detail='medium' (default) adds audienceSummary+themeCount; detail='full' returns raw data. Poll every ~15s. When done, surface the audience review + themes to the user.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "medium". Lists…

### `list_themes` (~146 tokens)

List all content themes for the active brand with their IDs. detail='short' (default) returns id+title; detail='medium' adds content preview; detail='full' returns raw content.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `edit_theme` (~72 tokens)

Edit an existing content theme's title or content instructions.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `content` (string): New content instructions for the theme
- `themeId` (string, required): Theme ID to edit
- `title` (string): New title for the theme

### `delete_theme` (~45 tokens)

Delete a content theme from the active brand.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `themeId` (string, required): Theme ID to delete

### `generate_themes` (~88 tokens)

Generate new content themes using AI. Polls until complete. Deducts credits.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `count` (number): Number of themes to generate
- `input` (string): Source text or file path to derive themes from
- `instructions` (string): Custom instructions, e.g. 'Focus on startup growth'

### `get_social_media_rules` (~115 tokens)

Read a brand's per-platform social media content rules (the same rules editable in dashboard Settings → Social media rules). Covers content/structure/engagement/visual-strategy guidance plus secrets, guidelines, content types, things to avoid, and core principles. Call this BEFORE set_social_media_rules so you only change the fields you intend to.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `platform` (string): Filter to a single platform. Omit to return all 6 platforms.

### `set_social_media_rules` (~169 tokens)

Update a brand's social media content rules for ONE platform. By default this MERGES your provided fields into the existing rules (send only what you want to change — other fields are preserved; arrays you provide replace the old array). Set replace=true to overwrite the entire platform ruleset. Valid platforms: linkedin, x/twitter, facebook, instagram, threads, general. Tip: call get_social_media_rules first to see current values.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `platform` (string, required): The platform whose rules you want to update.
- `replace` (boolean): Overwrite the entire platform ruleset instead of merging.
- `rules` (object, required): Partial or full ruleset to apply. Only provide the fields you want to change when merging.

### `generate_post` (~540 tokens)

Generate AI content for a platform. Polls until complete. Deducts 10 credits per variation. To control what the post is about, pass `theme` with a free-text brief (any topic/angle/facts/tone). If you omit `theme`, the topic is RANDOM — so always pass it when the user wants specific content. When variations > 1, ALL variations are returned under a SINGLE postId in the `variations` array — it does NOT create one post per variation. `content` is variation 1 (the primary, already saved on the post). Never call generate_post again to 'get the other variations' — they're all in the response. `originalContent` (if seen elsewhere) is the pre-voice-rewrite draft, not a separate variation. After generating, use create_post to save a chosen variation, then approve_post to schedule it. To repurpose: call repurpose_content first, then create_post with the result, then approve_post. Generation can take 1-5 minutes (longer with multiple variations or voice rewrite); this tool waits for completion. If it ever returns status 'still_generating', DO NOT write the post yourself — poll get_post with the returned postId until operationStatus is 'completed', then use that content. After generation, brand visual options (quote/card templates, brand images, stock photos) are prepared but NOT attached — share viewInBrowser for the visual picker, or call pick_post_visual to attach one. Do not attach a visual unless the user chooses it. The response includes editInVisualEditor: a direct URL to edit the post in the visual editor.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `platform` (string, required): Platform: x | linkedin | instagram | threads | facebook | custom:<charLimit>
- `theme` (string): Free-text topic/brief describing what the post should be about — include any angle, emphasis, key facts, or tone (e.g. "Launch announcement for our new MCP; emphasize NVIDIA + Stripe + Amotron; B2B a…
- `themeId` (string): Optional ID of a saved brand theme (from list_themes). Most callers should pass the free-text `theme` instead. If both are given, the free-text `theme` wins.
- `variations` (number): Number of variations to generate
- `voice` (string): Voice profile ID to apply

### `generate_bulk_posts` (~120 tokens)

Generate and schedule multiple posts across a date range in the background.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `days` (number, required): Number of days to fill
- `frequency` (string): Posting frequency
- `platform` (string, required): Platform: x | linkedin | instagram | threads | facebook
- `postsPerDay` (number)
- `times` (string): Comma-separated posting times, e.g. '09:00,14:00'
- `voice` (string): Voice profile ID

### `create_post` (~168 tokens)

Save a post draft with custom content to one or more platforms. Supported platforms: x, linkedin, instagram, threads, facebook. After creating, call approve_post with a future ISO 8601 datetime to schedule it. To check which platforms are connected first, call check_social_accounts. Each created post includes editInVisualEditor: a direct URL to edit it in the visual editor.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `content` (string, required): Post content
- `platforms` (array, required): One or more platforms: x | linkedin | instagram | threads | facebook
- `scheduledAt` (string): ISO 8601 UTC datetime to schedule immediately on save, e.g. 2026-03-11T09:00:00Z

### `list_posts` (~180 tokens)

List recent posts and drafts. Filter by status or platform. Use status='created' to find unscheduled drafts. Returns id+status+scheduledAt by default; use detail='medium' or 'full' for more fields. For a single post use get_post.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `limit` (number)
- `platform` (string)
- `status` (string)

### `get_post` (~221 tokens)

View the full content and status of a single post. Use detail='short'|'medium'|'full' to control verbosity (default full). Multi-variation posts expose a `variations` array (all variations live on one postId). Output shows the final voice-rewritten content by default; pass includeOriginal=true to also see the pre-rewrite draft. Includes editInVisualEditor: a direct URL to edit the post in the visual editor.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `includeOriginal` (boolean): Include the pre-voice-rewrite original draft (originalContent). Default false — you normally want the rewritten voice content.
- `postId` (string, required): Post ID

### `approve_post` (~110 tokens)

Approve and schedule a draft post. Requires a future datetime. The scheduledAt must be an ISO 8601 UTC datetime, e.g. 2026-03-11T09:00:00Z. After approving, the post status becomes 'scheduled'.

Input parameters:

- `postId` (string, required): Post ID to approve
- `scheduledAt` (string, required): Future ISO 8601 UTC datetime to post at
- `timezone` (string): User timezone, e.g. 'America/New_York'

### `reschedule_post` (~57 tokens)

Move a scheduled post to a new time. Pass a future ISO 8601 UTC datetime.

Input parameters:

- `postId` (string, required): Post ID to reschedule
- `scheduledAt` (string, required): New future ISO 8601 UTC datetime

### `schedule_post` (~115 tokens)

Schedule a draft or approved post for a specific time. Equivalent to approve_post but named to match 'pking posts schedule'. Pass a future ISO 8601 UTC datetime. After scheduling the post status becomes 'scheduled'.

Input parameters:

- `postId` (string, required): Post ID to schedule
- `scheduledAt` (string, required): Future ISO 8601 UTC datetime, e.g. 2026-06-01T09:00:00Z
- `timezone` (string): User timezone, e.g. 'America/New_York'

### `cancel_post` (~44 tokens)

Cancel a scheduled or approved post, reverting it to draft status without deleting it. Use delete_post to remove it entirely.

Input parameters:

- `postId` (string, required): Post ID to cancel

### `delete_post` (~38 tokens)

Cancel and delete a post regardless of its current status (draft, scheduled, or posted).

Input parameters:

- `postId` (string, required): Post ID to delete

### `get_calendar` (~157 tokens)

View upcoming scheduled posts sorted by date. Returns id+status+scheduledAt by default; use detail='medium' or 'full' for more fields. For a single post use get_post.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `days` (number): How many days ahead to show
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `repurpose_content` (~468 tokens)

Turn a URL, text, blog post, or existing PostKing post into new content for social media or blogs. IMPORTANT: When the source is a URL, pass it directly to this tool via sourceUrl — do NOT fetch or crawl the URL yourself first. PostKing handles all crawling internally. Source types: url | text | blog | social_post. Target types: social (LinkedIn, X, etc.) | blog | text. Supports detail param: short=ids only, medium=key fields (default), full=raw response.

Input parameters:

- `angle` (string): Specific angle or focus, e.g. 'focus on ROI data'
- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "medium". Lists…
- `includeLink` (boolean): Include source link in output
- `sourceBlogId` (string): Existing PostKing blog article ID (when sourceType=blog). Get IDs from list_blogs.
- `sourceContent` (string): Raw text to repurpose (when sourceType=text)
- `sourcePostId` (string): Existing PostKing post ID (when sourceType=social_post)
- `sourceType` (string, required): Where the source content comes from
- `sourceUrl` (string): URL to repurpose (when sourceType=url)
- `targetPlatforms` (array): Platforms for social output, e.g. ['x','linkedin']
- `targetType` (string, required): What to generate
- `textLength` (string): For text target: short | medium | long | custom:<words>
- `themeId` (string): Content theme ID to attach
- `variations` (number)
- `voiceProfileIds` (array): Voice profile IDs. Single ID applies to all platforms: ['clxvoice1']. Per-platform: ['x:clxvoice1','linkedin:clxvoice2']. Get IDs from list_voices.

### `generate_text` (~511 tokens)

Generate or rewrite general-purpose, email-style/formal text — follow-up emails, cover letters, outreach messages, formal notes, etc. Polls until complete. This is NOT for social media posts — use generate_post for those. This is also NOT for a simple voice-only rewrite of existing text with no other options — for that, use rewrite_with_voice or rewrite_text instead. Reach for generate_text when you need mode selection (generate vs rewrite), a stated purpose, a target length, or when there's no source text at all (mode='generate'). mode='generate' writes new text from a `prompt` (what to write, e.g. 'a follow-up email after a sales call, polite but direct'). mode='rewrite' rewrites existing `sourceText` into a new form/tone. `purpose` is optional free text describing the goal, e.g. 'win-back email' or 'job application cover letter'. `length` can be short | medium | long, or a specific target word count (20-5000). Supports detail param: short=status+wordCount only, medium=truncated content (500 chars)+wordCount (default), full=raw result including content, aiDetectionScore, burstinessScore, promptLogId, wordCount.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `length`: Target length: short | medium | long, or a specific target word count (20-5000).
- `mode` (string, required): generate = write new text from a prompt; rewrite = rework existing sourceText
- `prompt` (string): What to write, required when mode='generate'. Max 4000 chars.
- `purpose` (string): Free-text goal of the text, e.g. 'win-back email', 'job application cover letter'. Max 200 chars.
- `sourceText` (string): Existing text to rewrite, required when mode='rewrite'. Max 20000 chars.
- `voiceProfileId` (string): Voice profile ID to apply. Get IDs from list_voices.

### `list_voices` (~112 tokens)

List all available voice profiles with their IDs. Lists default short; pass detail=medium/full for more fields.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `rewrite_with_voice` (~73 tokens)

Rewrite text using a specific voice profile.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `platform` (string): Platform context: x | linkedin | instagram | threads | facebook
- `profileId` (string, required): Voice profile ID
- `text` (string, required): Text to rewrite

### `rewrite_text` (~76 tokens)

Rewrite text using a voice profile or general writing rules.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `platform` (string): Platform context: x | linkedin | instagram | threads | facebook
- `text` (string, required): Text to rewrite
- `voice` (string): Voice profile ID to apply

### `humanize_text` (~71 tokens)

Apply LLM rewrite and BERT replacements to reduce AI detection signals in text.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `platform` (string): Platform context: x | linkedin | instagram | threads | facebook
- `text` (string, required): Text to humanize

### `check_ai_content` (~49 tokens)

Check whether text is likely AI-generated. Returns a score and analysis.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `text` (string, required): Text to check

### `check_social_accounts` (~139 tokens)

List all connected and disconnected social accounts for the active brand. Lists default short; pass detail=medium/full for more fields. Run before posting to confirm platform availability.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `generate_connect_link` (~61 tokens)

Generate a secure browser link to connect a social media account. Share this URL with the user to complete OAuth.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `platform` (string): Target social platform for the connect link

### `disconnect_social_account` (~47 tokens)

Disconnect a social account by its account ID.

Input parameters:

- `accountId` (string, required): Social account ID to disconnect
- `brandId` (string): Brand ID (uses active brand if omitted)

### `list_domains` (~139 tokens)

List all custom domains for the active brand. Lists default short; pass detail=medium/full for more fields including SSL status, verification, and connected blogs or landing pages.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `add_domain` (~100 tokens)

Add a new custom domain to the active brand. After adding, call verify_domain to check DNS and activate it.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `domain` (string, required): Domain name, e.g. 'myblog.com' (protocol and www stripped automatically)
- `isPrimary` (boolean): Set as primary domain for the brand
- `primaryContentType` (string): What this domain primarily serves

### `verify_domain` (~48 tokens)

Check DNS verification for a domain. Returns whether it's pointing to PostKing servers and what A record is needed if not.

Input parameters:

- `domainId` (string, required): Domain ID to verify (from list_domains)

### `delete_domain` (~41 tokens)

Remove a custom domain. Connected blogs and landing pages are unlinked but not deleted.

Input parameters:

- `domainId` (string, required): Domain ID to delete (from list_domains)

### `connect_domain_to_publication` (~111 tokens)

Connect a verified domain to a blog publication so articles are served from that domain.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `domainId` (string, required): Domain ID (from list_domains)
- `pathPrefix` (string): Path prefix if routingType is 'path', e.g. '/blog'
- `publicationId` (string, required): Blog publication ID (from list_blogs)
- `routingType` (string): How the blog is routed on the domain

### `get_credits` (~102 tokens)

Check your current PostKing credit balance and free-tier status.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…

### `health` (~41 tokens)

Check PostKing API health and local auth state. No authentication required — safe to call first thing in a session to see whether you're logged in and what to do next.

### `list_blogs` (~197 tokens)

LIST tool. Even detail='full' OMITS article bodies (kept bounded) — to read an article's content, call get_blog_article. The number of rows is controlled by `limit` (default 50, max 200), NOT by `detail`; for a 'full list' of titles, raise `limit` and keep detail='short'.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `limit` (integer)
- `status` (string): Filter articles by status

### `create_publication` (~78 tokens)

Create a new blog publication (the container that articles live under). Returns a publicationId needed for generate_blog_post.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `description` (string)
- `layout` (string)
- `title` (string, required): Publication name, e.g. 'My Blog'

### `update_publication` (~158 tokens)

Update an existing blog publication's metadata — title, description, domain/routing config, or layout. Only the fields you pass are changed (partial update). Distinct from create_publication (which creates a new one). publicationId comes from list_publications or list_blogs.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `description` (string): Publication description / tagline
- `domainId` (string): Custom domain ID to route this publication under
- `layout` (string)
- `pathPrefix` (string)
- `publicationId` (string, required): Blog publication ID (from list_publications or list_blogs)
- `routingType` (string)
- `title` (string)

### `generate_blog_post` (~330 tokens)

Generate a full AI blog article. Requires a publicationId (from list_blogs or create_publication). Pass a voiceProfileId to write in a specific person's style (IDs from list_voices). Returns an articleId + operationId; generation is async — poll get_blog_status until completed, then get_blog_article. Use update_blog_article to edit, or publish_blog_article to push to external platforms. To make it live on your PostKing blog, call update_blog_article with status: 'published'.

Input parameters:

- `attachVisualAsset` (boolean): Use a brand visual asset (with branding) for the header image
- `brandId` (string): Brand ID (uses active brand if omitted)
- `generateAiImage` (boolean): Generate an AI header image
- `imageVariationCount` (integer): Number of AI image variations to generate (1-5). Only used when generateAiImage is true.
- `primaryKeywords` (array): SEO keywords to target
- `publicationId` (string, required): Blog publication ID
- `readabilityTarget` (string): Readability level for the writing, e.g. 'grade-8'
- `secondaryKeywords` (array): Secondary SEO keywords to target
- `selectedAssetId` (string): ID of the brand asset to use for the header image
- `skipBrandContext` (boolean): Omit brand context from the generation prompt when true
- `targetLength` (string)
- `topic` (string, required): Topic or working title for the post
- `voiceProfileId` (string): Voice profile ID for writing style (from list_voices)

### `get_blog_article` (~241 tokens)

Fetch a blog article by ID. detail='short' returns id/title/slug/status; detail='medium' adds excerpt+wordCount+previewUrl+editUrl; detail='full' (default) returns the COMPLETE content plus previewUrl (GUI preview link) and editUrl (dashboard editor link). Pass maxContentChars only if you need to bound the body size; omit it to get the whole article.

Input parameters:

- `articleId` (string, required): Blog article ID
- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `maxContentChars` (integer): Optional cap on the returned content body length (full detail only). Omit to return the ENTIRE article body. Use only to bound payload size for small clients.

### `update_blog_article` (~163 tokens)

Edit a blog article — title, content, excerpt, SEO fields, status, author, or category. Set status='published' to make it live on your PostKing blog.

Input parameters:

- `articleId` (string, required): Blog article ID
- `authorId` (string): Author ID (from list_blog_authors)
- `brandId` (string): Brand ID (uses active brand if omitted)
- `categoryId` (string): Category ID (from list_blog_categories)
- `content` (string): Full post body (HTML or markdown)
- `excerpt` (string)
- `metaDescription` (string)
- `metaTitle` (string)
- `status` (string): 'published' makes it live on your blog
- `title` (string)

### `schedule_blog_article` (~146 tokens)

Schedule an existing blog article to auto-publish at a future date/time. scheduledAt must be a future ISO 8601 datetime. When the time arrives, the article publishes to the PostKing blog and auto-pushes to any connected external platforms flagged autoPublish. To publish immediately instead, use update_blog_article with status='published'.

Input parameters:

- `articleId` (string, required): Blog article ID
- `brandId` (string): Brand ID (uses active brand if omitted)
- `scheduledAt` (string, required): ISO 8601 datetime in the future, e.g. 2026-07-10T14:30:00Z, when the article should auto-publish

### `delete_blog_article` (~43 tokens)

Permanently delete a blog article.

Input parameters:

- `articleId` (string, required): Blog article ID
- `brandId` (string): Brand ID (uses active brand if omitted)

### `list_blog_authors` (~149 tokens)

List all blog authors for the active brand. Returns id+name by default (short); use detail='medium' for email/social links. Author IDs can be passed to generate_blog_post or update_blog_article.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `list_blog_categories` (~152 tokens)

List all categories for a blog publication. Returns id+name+slug by default (short); use detail='medium' for description+articleCount.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `publicationId` (string, required): Blog publication ID (from list_blogs)

### `create_blog_category` (~78 tokens)

Create a new category in a blog publication.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `description` (string)
- `name` (string, required): Category name
- `publicationId` (string, required): Blog publication ID
- `slug` (string, required): URL slug, e.g. 'marketing-tips'

### `list_publishing_connections` (~56 tokens)

List external publishing connections for a blog publication (WordPress, Medium, Substack, etc.).

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `publicationId` (string, required): Blog publication ID

### `publish_blog_article` (~77 tokens)

Push a blog article to connected external platforms (WordPress, Medium, Substack, etc.). Get connection IDs from list_publishing_connections.

Input parameters:

- `articleId` (string, required): Blog article ID
- `brandId` (string): Brand ID (uses active brand if omitted)
- `connectionIds` (array, required): Connection IDs to publish to

### `import_blog_articles` (~191 tokens)

Import articles from an external blog, RSS feed, or Blogger URL into a PostKing publication as drafts. Returns id+title+slug by default (short); use detail='medium' for wordCount. Inspect individual articles with get_blog_article.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `limit` (number)
- `publicationId` (string, required): Blog publication ID to import into
- `sourceUrl` (string, required): URL of the blog or RSS feed

### `get_blog_status` (~71 tokens)

Poll the async generation status of a blog article. Use the articleId returned by generate_blog_post. Status: pending | running | completed | failed.

Input parameters:

- `articleId` (string, required): Blog article ID returned by generate_blog_post
- `brandId` (string): Brand ID (uses active brand if omitted)

### `create_blog_author` (~82 tokens)

Create a new author for blog articles. Returns an authorId that can be used in generate_blog_post and update_blog_article.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `email` (string): Author email address
- `firstName` (string, required): Author first name
- `lastName` (string, required): Author last name

### `list_publications` (~151 tokens)

List all blog publications (the containers that blog articles live under). detail='short' returns id+name; detail='full' returns raw rows. Distinct from list_publishing_connections which lists external platforms like WordPress.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `get_seo_roadmap` (~173 tokens)

View the SEO / GEO content roadmap — suggested blog topics, keywords, and completion status. detail='short' returns stats only; detail='medium' adds slim item list (id+title+status+keyword); detail='full' (default) returns raw response.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `status` (string): Filter by status

### `seo_add_seeds` (~79 tokens)

Step 1 of the SEO / GEO flow. Add 3–10 seed keywords that describe what the brand wants to rank for. After this, call seo_generate_keywords to expand them into the full keyword universe.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `seeds` (array, required): Seed keywords or topics

### `seo_generate_keywords` (~164 tokens)

Step 2 of the SEO / GEO flow. Async — expands seed keywords into the full keyword universe. Typically takes ~1–3 min. Uses credits. Returns `{operationId, status}` — Poll `get_job` with the operationId until `state` is `completed` (or `failed`/`partially_failed`/`cancelled` on error). The server picks the expansion size automatically; the only thing the agent can tweak is `autoScore` (defaults to true server-side — set false to skip volume/difficulty scoring). After completion, call seo_categorize.

Input parameters:

- `autoScore` (boolean): Whether to auto-score generated keywords (server default: true)
- `brandId` (string): Brand ID (defaults to active brand)

### `seo_list_keywords` (~177 tokens)

List generated keywords for the brand. Useful for auditing between steps. Returns short detail by default: {id, keyword, intent} per keyword. Use detail="medium" for the full compact summary (priority, searchVolume, difficulty, excludedFromClustering, userTags) or detail="full" for raw keyword objects.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `limit` (integer)

### `seo_edit_keyword` (~205 tokens)

Edit a single SeoScoredKeyword. Housekeeping op — not part of the main flow. At least one of `intent`, `userTags`, `priority`, or `excludedFromClustering` must be supplied. Use to override the auto-detected intent label, attach user tags, manually nudge priority (0..1), or exclude a noisy keyword from clustering. Example: {"keywordId":"kw_123","intent":"commercial","userTags":["enterprise"]}.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `excludedFromClustering` (boolean): When true, the keyword is held out of the next clustering pass
- `intent` (string): Override the auto-detected search-intent label
- `keywordId` (string, required): Keyword ID from seo_list_keywords
- `priority` (number): Manual priority override in [0,1]
- `userTags` (array): Replacement array of user tags (overwrites existing tags)

### `seo_delete_keyword` (~135 tokens)

Soft-delete a single SeoScoredKeyword (sets deletedAt; the row stays in the DB but is filtered out of all queries). Housekeeping op — not part of the main flow. Pass `confirm: true` to proceed. Use for obvious junk keywords the auto-pipeline missed. Prefer seo_edit_keyword with excludedFromClustering=true when you only want to hold a keyword out of the next clustering pass.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `confirm` (boolean, required): Must be true to confirm soft-delete
- `keywordId` (string, required): Keyword ID from seo_list_keywords

### `seo_categorize` (~115 tokens)

Step 3 of the SEO / GEO flow. Tag keywords by search intent and/or user tags. Pass `updates`: an array of `{keywordId, intent?, userTags?}` (intent ∈ informational | commercial | navigational | transactional). Example: {"updates":[{"keywordId":"kw_123","intent":"commercial"}]}. After this, call seo_generate_clusters.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `updates` (array, required): Per-keyword categorization updates

### `seo_generate_clusters` (~122 tokens)

Step 4 of the SEO / GEO flow — async cluster-generation step. Groups related keywords into topic clusters that become candidate pillar topics. Typically takes ~1–2 min. Returns `{operationId, status}` — Poll `get_job` with the operationId until `state` is `completed` (or `failed`/`partially_failed`/`cancelled` on error). After completion, call seo_list_clusters to pick a target, then seo_generate_roadmap.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `seo_list_clusters` (~223 tokens)

Step 5. List clusters so the agent can pick one (or several) to approve before brief and roadmap generation. Returns short detail by default: {id, name, status} per cluster. Use detail="medium" for the full compact summary (pillarKeyword, briefGenerationStatus, briefCount, keywordCount, topKeywords, firstBriefId, description) or detail="full" for raw cluster objects. Full keyword detail (keywordsMeta, contentMix, briefAssignments) is intentionally omitted at short/medium to keep context small — use cluster IDs with approve/reject tools directly.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `seo_bulk_approve_clusters` (~159 tokens)

Step 5b — bulk-approve N clusters in one call. Recommended path when an agent wants to move multiple clusters forward. Approving a cluster fires an async seo_brief_generate Operation per cluster; brief generation only runs on approved clusters. Typically takes ~2–5 min per cluster. Response includes `operations: [{ clusterId, operationId }]` and `operationIds: string[]` — Poll `get_job` with the operationId until `state` is `completed` (or `failed`/`partially_failed`/`cancelled` on error).

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `clusterIds` (array, required): Cluster IDs from seo_list_clusters to approve

### `seo_approve_cluster` (~149 tokens)

Approve a single cluster. Gates brief generation — only approved clusters get briefs drafted. Position in flow: after seo_generate_clusters + seo_list_clusters, before seo_generate_roadmap / brief review. Typically takes ~2–5 min per cluster. Returns `{ cluster, operationId }` — the operationId is for the async brief-generation job kicked off by approval. Poll `get_job` with the operationId until `state` is `completed` (or `failed`/`partially_failed`/`cancelled` on error).

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `clusterId` (string, required): Cluster ID from seo_list_clusters

### `seo_reject_cluster` (~62 tokens)

Reject a single cluster — marks it rejected and detaches its scored keywords. Use when a generated cluster isn't relevant.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `clusterId` (string, required): Cluster ID from seo_list_clusters

### `seo_bulk_reject_clusters` (~60 tokens)

Bulk-reject N clusters in one call. Symmetric to seo_bulk_approve_clusters.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `clusterIds` (array, required): Cluster IDs from seo_list_clusters to reject

### `seo_unapprove_cluster` (~67 tokens)

Revert an approved cluster back to pending_review. Fails if briefs have already been generated for this cluster (cannot be reverted once briefs exist).

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `clusterId` (string, required): Cluster ID to unapprove

### `seo_restore_cluster` (~51 tokens)

Restore a rejected cluster back to pending_review so it can be approved again.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `clusterId` (string, required): Cluster ID to restore

### `seo_generate_roadmap` (~123 tokens)

Step 6. Turn one or more clusters into a prioritized content roadmap of blog articles to write. Pass `clusterId` (single) or `clusterIds` (array of cluster IDs from seo_list_clusters) — omit both to roadmap all clusters. After this, call seo_write_article.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `clusterId` (string): Single cluster ID from seo_list_clusters (convenience for one cluster)
- `clusterIds` (array): Array of cluster IDs from seo_list_clusters

### `seo_list_roadmap` (~188 tokens)

List roadmap items (blog topics queued for writing). Returns short detail by default: {id, title, status} per item. Use detail="medium" for the compact summary (priority, primaryKeywords, clusterId) or detail="full" for raw objects. Call seo_roadmap_get with detail="full" for a single item's complete detail.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `status` (string): Filter by status

### `seo_list_briefs` (~458 tokens)

Returns ALL briefs across EVERY status by default — do NOT add a status filter unless the user explicitly asks. `statusBreakdown` gives the per-status counts (match these to the dashboard's brief count). NOTE: briefs whose generation is still in-flight may not appear here yet — check `list_operations` / `get_job` for in-progress generation. Returns short detail by default: {id, type, status, title} per brief. Use detail="medium" for compact summary (clusterId, clusterName, briefSummary, sidePageUrl, generationError, blogArticleId, generatedAt, approvedAt) or detail="full" for raw briefs including briefData outline. To inspect a single brief's full outline, call seo_get_brief with detail="full". Filters: status (CSV — e.g. 'pending_review,approved'), type (CSV — e.g. 'blog,comparison,landing'), clusterId, roadmapItemId, q (cluster-name fuzzy match), limit (default 50, max 200), cursor. Response shape: { count, total, nextCursor, statusBreakdown, detail, briefs: [...] }.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `clusterId` (string): Filter to briefs under one cluster
- `cursor` (string): Pagination cursor from a previous page
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `limit` (integer): Page size (default 50)
- `q` (string): Fuzzy match on cluster name
- `roadmapItemId` (string): Filter to briefs for a single roadmap item
- `status` (string): CSV of statuses: pending_review | approved | rejected | writing | drafted | published | failed | needs_human_review
- `type` (string): CSV of brief types: blog | comparison | tool | landing

### `seo_get_brief` (~216 tokens)

Fetch a single SeoBrief by id, including its briefData outline, status, roadmap item, and cluster. Returns full detail by default (complete brief incl briefData). Use detail="medium" for {id, type, status, title, clusterName, briefSummary, sidePageUrl} or detail="short" for {id, type, status}. Use to inspect a brief before refining it with seo_edit_brief or approving it with seo_approve_briefs.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `briefId` (string, required): Brief ID from seo_list_briefs
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…

### `seo_edit_brief` (~304 tokens)

Edit an SeoBrief before approval. The inner route does NOT accept free-text 'instructions' — refinement is structured. Pass `briefData` (the full replacement JSON for the brief outline — H2s, FAQs, keyword targets, etc.) and/or `status` ('approved' | 'rejected'). Approving via this tool fires L4 article/comparison generation immediately and returns { brief, operationId } — prefer seo_approve_briefs for the canonical approval step. Typical refinement loop: seo_get_brief → mutate briefData locally → seo_edit_brief with the new briefData (status omitted) → seo_approve_briefs.

Input parameters:

- `attachedAssetId` (string): When approving, attach an existing asset to the article.
- `brandId` (string): Brand ID (defaults to active brand)
- `briefData`: Full structured replacement for the brief outline (object). Pulled from seo_get_brief, edited, sent back.
- `briefId` (string, required): Brief ID from seo_list_briefs
- `generateHeroImage` (boolean): When approving, also generate a hero image (extra credits).
- `parentSource` (string): Landing-brief approval: which parent LP draft to extend from.
- `status` (string): Flip to 'approved' (fires generation) or 'rejected'. Omit to update briefData without changing status.
- `voiceProfileId` (string): When approving, write in this voice profile.

### `seo_approve_briefs` (~203 tokens)

Approves one or more SeoBriefs and AUTO-fires L4 article/comparison generation immediately — do NOT call seo_write_article after this. Typically takes ~3–8 min per article. After approval, poll each returned operationId with get_job until state is `completed` (or `failed`/`partially_failed`/`cancelled` on error). A brief in status `writing` means generation is already in progress — poll the existing operation, do not re-submit. Body: briefIds (array, max 200). Response: { approved, failed: [{briefId, reason}], operationIds, operations: [{briefId, operationId, type}] }.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `briefIds` (array, required): Brief IDs to approve (from seo_list_briefs)
- `generateHeroImage` (boolean): Also generate a hero image per blog brief (extra credits).

### `seo_list_results` (~224 tokens)

List the 'Generated Results' (blog articles + side pages + comparisons) the SEO/GEO pipeline has produced — mirrors the dashboard Results tab. Includes BOTH draft and published items. Use kind to filter. This is the canonical 'what content has been generated' list — prefer it over reconstructing results from briefs.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `cursor` (string): Pagination cursor from a previous page
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `kind` (string): Kind of result to filter by
- `limit` (integer): Page size (default 50, max 200)
- `status` (string): CSV of statuses to filter by

### `seo_regenerate_brief` (~149 tokens)

Re-runs L3 brief generation for a single brief (scoped to its cluster). Typically takes ~2–5 min. Async — returns `{operationId, status}`. Poll `get_job` with the operationId until `state` is `completed` (or `failed`/`partially_failed`/`cancelled` on error), then re-fetch with seo_get_brief to see the refreshed briefData. Use when the existing brief's outline is unusable and a structured seo_edit_brief won't recover it.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `briefId` (string, required): Brief ID from seo_list_briefs

### `seo_write_article` (~244 tokens)

ALTERNATIVE write path — only call this when a brief was NOT approved via seo_approve_briefs (which already auto-generates). If the brief status is `writing`, generation is already in progress — poll the existing operationId with get_job until state is `completed` rather than calling this again. Step 7 (manual path). Draft a full blog article for a roadmap item. Uses credits. Typically takes ~3–8 min per article. Precondition: the brief for this roadmap item must be in status `approved`. If the brief is still `drafted` or `pending_review`, review with `seo_list_briefs` / `seo_get_brief`, refine with `seo_edit_brief`, then approve with `seo_approve_briefs` (which will auto-generate). Returns an articleId that can be reviewed, edited, or published.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `count` (integer)
- `roadmapItemId` (string, required): Roadmap item ID from seo_list_roadmap
- `voiceProfileId` (string): Voice profile ID to write in a specific style

### `seo_gap` (~166 tokens)

Identify content gaps — topics the brand's competitors cover but the brand doesn't. Returns short detail by default: {id?, topic?, keyword?} labels only per gap. Use detail="medium" for compact gaps (+ searchVolume, difficulty, competitorDomains) or detail="full" for the raw API response.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `seo_competitor` (~193 tokens)

Compare the brand's keyword coverage against a competitor domain. Returns short detail by default: bucket totals only {overlapping: {total}, brandUnique: {total}, competitorOnly: {total}}. Use detail="medium" for capped keyword lists (up to 50 per bucket) or detail="full" for the raw API response.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `competitorDomain` (string, required): Competitor domain, e.g. 'competitor.com'
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `seo_roadmap_get` (~174 tokens)

View a single roadmap item by ID. Returns full detail by default (raw object). Use detail="medium" for {id, title, status, primaryKeywords, clusterId} or detail="short" for {id, title, status}.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `itemId` (string, required): Roadmap item ID from seo_list_roadmap

### `seo_roadmap_edit` (~104 tokens)

Edit a roadmap item — update its title, status (suggested|in_progress|completed|ignored), or priority.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `itemId` (string, required): Roadmap item ID from seo_list_roadmap
- `priority` (integer): Priority integer (lower = higher priority)
- `status` (string): New status
- `title` (string): New title for the roadmap item

### `seo_roadmap_delete` (~71 tokens)

Permanently delete a roadmap item. Pass confirm: true to proceed — this is irreversible.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `confirm` (boolean, required): Must be true to confirm deletion
- `itemId` (string, required): Roadmap item ID to delete

### `seo_roadmap_stats` (~41 tokens)

Progress stats for the content roadmap (completed, in-progress, suggested counts).

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `seo_publish_article` (~116 tokens)

Step 10. Publish or schedule a roadmap-generated article to a publication. Free-tier choke point — may return FREE_CAP_REACHED with a checkoutUrl.

Input parameters:

- `articleId` (string, required): Blog article ID
- `brandId` (string): Brand ID (defaults to active brand)
- `connectionIds` (array): External publishing-connection IDs (WordPress, Medium, ...)
- `publicationId` (string): Target publication ID
- `scheduledAt` (string): ISO 8601 datetime to schedule; omit to publish now

### `seo_auto_assign_cta` (~333 tokens)

Optional capstone step — runs AFTER blog articles are published. Auto-suggests a published side-page CTA for each blog article in the batch by matching the article's keywords (cluster + tags + title) against the brand's published side pages, then writes the result to BlogArticle.sidePageInfo (header + ctaText + ctaButtonText + ctaHref). Defaults are safe to re-run: skips blogs that already have a CTA (`overwriteExisting=false`) and skips Webflow-synced blogs (`skipWebflowSynced=true`) since CTA edits don't push back to Webflow. Synchronous — returns `{ updated, skipped, errors, total, results: [{blogId, status, sidePageId?, sidePageSlug?}] }` directly; no polling needed. Pass `blogIds: "all"` to process every blog for the brand, or a list of up to 50 blog IDs. Hard cap of 50 blogs per call. For brands with more uncovered blogs, batch your `blogIds` arrays in groups of 50. The matcher uses keywords today; reusing an existing CTA header/ctaText body as a matching signal is a planned enhancement.

Input parameters:

- `blogIds` (required): Blog article IDs to process, or the literal "all" for every blog in the brand
- `brandId` (string): Brand ID (defaults to active brand)
- `overwriteExisting` (boolean): Overwrite blogs that already have a sidePageInfo CTA (default false)
- `skipWebflowSynced` (boolean): Skip blogs with externalProvider=webflow (default true)

### `seo_generate_side_page` (~426 tokens)

Generates a side page for a brand's landing page, optionally linked to an SEO cluster (`clusterId`). When linked, the side page surfaces in cluster-context queries and inherits the cluster's keyword targeting — strengthening topical authority that feeds GEO citation patterns. Two body modes:   • freeform: pass `key` + `prompt` (+ optional `keywords`, `selectedSections`, `voiceProfileId`, `sidePageType`).   • brief: pass `key` + `brief` (structured outline) + optional `briefId` and `roadmapItemId`. Typically takes ~2–5 min. Async — returns `{ success, operationId, operationRowId, pollUrl, sidePageId }`. Poll `get_job` with the operationId until `state` is `completed` (or `failed`/`partially_failed`/`cancelled` on error); comparison-type briefs run synchronously and return `sidePageId` directly. `slug` is the PARENT landing page slug under which the side page is created.

Input parameters:

- `autoAssignAssets` (boolean): Auto-assign brand assets to image slots after generation
- `brief`: Brief-mode structured outline. When set, this is the canonical payload.
- `briefId` (string): Persisted SeoBrief ID — required for comparison-type generation
- `clusterId` (string): SEO KeywordCluster ID to link this side page to (maps to SidePage.sourceClusterId)
- `key` (string, required): Side-page key (URL slug fragment under the parent LP)
- `keywords` (array): Freeform-mode: target keywords to weave into the page
- `prompt` (string): Freeform-mode generation prompt (omit when passing `brief`)
- `roadmapItemId` (string): Roadmap item ID this side page is fulfilling
- `sidePageType` (string): Defaults to 'landing'. Use 'comparison' only with a persisted comparison briefId.
- `slug` (string, required): Parent landing page slug
- `voiceProfileId` (string): Voice profile to write in

### `create_comparison_page` (~698 tokens)

Create a comparison / 'X vs Y' / 'best <category>' page for the brand WITHOUT going through the full SEO cluster → brief flow. One call kicks off generation, then this tool polls until the page is built and returns its slug(s) + link. `mode` controls the engine: 'research' crawls the named competitors + live SERP results before writing — slower (can take several minutes) but produces the strongest, best-grounded page; 'simple' skips all crawling and lets the LLM author from what you provide — fast, best when you already have the facts or just want a quick draft. When your inputs are sparse (few/no options, no domains, no seedData), prefer 'research' — it will discover and ground the comparison for you and yield a far stronger page than 'simple'. `seedData` (simple mode): paste your own raw facts/notes/competitor details here and the LLM writes from them instead of crawling — this is how you feed your own data and avoid a crawl. Async — fires the create, then polls the operation up to ~5 min. On success returns { briefId, sidePageId, sidePageSlug, landingPageSlug, webUrl, warnings }. If it is still running after the wait, returns { status: 'still_generating', operationId } — poll get_job with that operationId until state is 'completed'; do NOT fabricate the page yourself. Any `warnings` are surfaced verbatim — relay them to the user (e.g. sparse-input notes such as 'research mode would produce a stronger page').

Input parameters:

- `allowGenericRoundup` (boolean): Allow a generic category roundup when no concrete options are supplied.
- `brandId` (string): Brand ID (defaults to active brand)
- `briefData`: Advanced: a full pre-built structured comparison brief. When supplied, generation uses it directly and skips the LLM authoring step.
- `mode` (string, required): Generation engine. 'research' = crawl the competitors + live SERP, then write (slower, strongest, best for sparse inputs). 'simple' = no crawl, LLM authors from what you pass (fast; pair with seedDat…
- `options` (array): The things being compared. Omit to let research mode discover them.
- `parentLandingPageSlug` (string): Slug of the parent landing page to nest this comparison under. Defaults to the brand's primary landing page.
- `pinnedCompetitor` (object): A specific competitor to anchor a head-to-head comparison around.
- `preset` (string): Page shape: head_to_head (X vs Y), alternatives_listicle (X vs alternatives), or category_roundup (best <category>). Inferred when omitted.
- `primaryKeyword` (string, required): The topic/keyword the page targets, e.g. "Acme vs alternatives", "best CRM for startups", "Notion vs Obsidian".
- `proposedSlug` (string): Desired URL slug fragment for the new page (auto-generated from primaryKeyword if omitted).
- `seedData` (string): Simple-mode only: your own raw data/notes/facts about the options. When set, the LLM writes from this instead of crawling — feed it here to avoid a research crawl.
- `voiceProfileId` (string): Voice profile ID to write the page in.

### `list_api_keys` (~127 tokens)

List all API keys on the account. Shows prefix, scope, and revocation status. Does NOT show raw tokens. Lists default short; pass detail=medium/full for more fields.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `create_api_key` (~81 tokens)

Create a new PostKing API key. The raw token (pk_live_*) is returned ONCE in this response and never shown again. Store it immediately — treat it like a password. Scope: 'write' (default) or 'read'.

Input parameters:

- `name` (string): Descriptive name for the key
- `scope` (string): Permission scope

### `revoke_api_key` (~66 tokens)

Permanently revoke an API key. Pass confirm: true to proceed — this is irreversible and any clients using this key will stop working immediately.

Input parameters:

- `confirm` (boolean, required): Must be true to confirm revocation
- `keyId` (string, required): API key ID from list_api_keys

### `list_jobs` (~226 tokens)

To check a SPECIFIC operation's status, call get_job with its operationId — this list only returns the most recent ops and may omit a just-started one. List background jobs for the active brand. Use status='pending' to see in-flight ops, 'completed' to see finished ones. Use get_job with an operationId to poll a specific job — most async tools return an operationId. Supports detail param: short=id+kind+state, medium=6 key fields, full=raw.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `limit` (integer)
- `status` (string): Filter by job status

### `list_operations` (~297 tokens)

To check a SPECIFIC operation's status, call get_job with its operationId — this list only returns the most recent ops and may omit a just-started one. List recent Operations (the newer async-op system) for the active brand. Each operation has: id, kind, state (pending|running|completed|partially_failed|failed|cancelled), progress, result, errors. Use get_job with an operationId to poll a specific one. Filters: kind (e.g. seo_keyword_generate, seo_cluster_generate, lp_generate), state. Supports detail param: short=id+kind+state, medium=adds scopeId+progress+timestamps (no result/errors), full=raw including result.

Input parameters:

- `brandId` (string): Brand ID (uses active brand if omitted)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `kind` (string): Filter by operation kind (e.g. seo_keyword_generate)
- `limit` (integer)
- `state` (string): Filter by state: pending | running | completed | partially_failed | failed | cancelled

### `get_job` (~466 tokens)

To check whether ONE specific operation you started is finished, pending, or failed, call this with its operationId (the `pollUrl` param accepts a bare operationId + brandId). Do NOT use list_operations to check a specific op. Poll the status of any background Operation by its operationId or pollUrl. Returns the Operation row: { id, kind, state (pending|running|completed|partially_failed|failed|cancelled), brandId, scopeType, scopeId, progress, result, errors, startedAt, finishedAt, createdAt, updatedAt }. Call repeatedly until state is 'completed' or 'failed' (or 'cancelled'). When completed, the payload you want is in `result`; on failure, see `errors`. Pass wait:true to block until the job is done (polls every 3s, up to ~2 min) — preferred over calling repeatedly. Most generate_* and vibe_edit_* tools (including seo_generate_clusters and seo_generate_keywords) return an operationId — use this to poll them. Supports detail param: short=id+kind+state+progress, medium=adds timestamps+resultRef (key IDs from result, no full payload), full=raw including complete result JSONB.

Input parameters:

- `brandId` (string): Brand ID — required if pollUrl is a bare operationId
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `maxWaitSeconds` (integer): Wait-window cap in seconds (default ~120s). Only applies when wait=true.
- `pollUrl` (string, required): Full poll URL returned by an async tool, OR a bare operationId (plus brandId param).
- `wait` (boolean): Block and poll until the job reaches a terminal state (completed/failed/partially_failed/cancelled) or the wait window elapses. Polls every 3s. Use this instead of calling get_job repeatedly yourself.

### `get_weekly_schedule` (~158 tokens)

Retrieve the current weekly content schedule for the active brand. Returns enabled status, lead time, timezone, and per-day platform configs. If no schedule is configured yet, returns suggested defaults. Includes a viewInBrowser link to open the calendar page in the app.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…

### `set_weekly_schedule` (~180 tokens)

Create or replace the weekly content schedule for the active brand. dayConfigs: array of { dayOfWeek (0=Sun…6=Sat), mediums: [{ medium, postsPerDay }] }. Mediums: x/twitter | linkedin | instagram | facebook | threads | blog. After setting, call enable_weekly_schedule to activate it.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `dayConfigs` (array, required): Array of day configs. Omit a day to have no posts that day.
- `enabled` (boolean): Whether the schedule is active
- `leadTimeDays` (integer): How many days ahead to generate content (1–7)
- `timezone` (string): Timezone for scheduling, e.g. 'Europe/London'
- `voiceProfileId`: Default voice profile ID (optional)

### `enable_weekly_schedule` (~47 tokens)

Enable the weekly content schedule without changing its configuration. The schedule must already be created with set_weekly_schedule.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `disable_weekly_schedule` (~43 tokens)

Pause the weekly content schedule without deleting it. Re-enable later with enable_weekly_schedule.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `delete_weekly_schedule` (~56 tokens)

Permanently remove the weekly schedule for the active brand. Pass confirm: true to proceed.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `confirm` (boolean, required): Must be true to confirm deletion

### `run_weekly_schedule_day` (~83 tokens)

Trigger the smart-week content generation engine for a specific date. This immediately queues posts for that day's schedule configuration. Use YYYY-MM-DD format for the date. Returns postsCreated count.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `date` (string, required): Date to run, e.g. '2026-05-01'

### `list_assets` (~213 tokens)

List assets in the brand's visual library. Filter by type (IMAGE|DOCUMENT|VIDEO|LINK|LOTTIE), tags, or search text. Supports detail param: short=id+type+name, medium=key fields, full=raw.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `limit` (integer)
- `search` (string): Full-text search within asset name/description
- `tags` (string): Comma-separated tags to filter by
- `type` (string): Asset type filter: IMAGE | DOCUMENT | VIDEO | LINK | LOTTIE

### `view_asset` (~146 tokens)

View details of a single asset by ID. Supports detail param: short=id+type+name, medium=key fields, full=raw.

Input parameters:

- `assetId` (string, required): Asset ID from list_assets
- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…

### `upload_asset` (~299 tokens)

Upload an asset to the brand library from a local file path or base64-encoded content. Prefer filePath for local files: the server reads and base64-encodes the file itself, avoiding truncation of large base64 strings over the tool_call boundary. fileBase64 is still supported for remote/inline use where no local path is available. For large files over the remote/HTTP transport, a single fileBase64 string can get truncated crossing the LLM→tool_call boundary — use the chunked flow instead: upload_asset_begin → upload_asset_chunk (× N) → upload_asset_finish. Provide exactly one of filePath or fileBase64. Returns the new asset ID and URL.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `description` (string)
- `fileBase64` (string): Base64-encoded file content. Use filePath instead when the file is local.
- `fileName` (string): Original file name including extension, e.g. 'logo.png'. Derived from filePath if omitted.
- `filePath` (string): Absolute path to a local file to upload. Preferred over fileBase64 for local files.
- `mimeType` (string): MIME type of the file, e.g. 'image/png'
- `name` (string): Display name for the asset
- `tags` (array): Tags to apply, e.g. ['logo', 'brand']

### `upload_asset_begin` (~249 tokens)

Start a chunked asset upload. Use this instead of upload_asset's fileBase64 param for large files over the remote/HTTP transport, where a single large base64 string can get truncated crossing the LLM→tool_call boundary. Flow: upload_asset_begin (once) → upload_asset_chunk (once per chunk, in order) → upload_asset_finish (once). Returns an uploadId that expires after 10 minutes of inactivity.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `description` (string)
- `fileName` (string, required): Original file name including extension, e.g. 'logo.png'.
- `fileSize` (number): Raw decoded byte length of the original file. Recommended — enables truncation/corruption detection at finish time.
- `mimeType` (string): MIME type of the file, e.g. 'image/png'
- `name` (string): Display name for the asset
- `sha256` (string): Sha256 hex digest of the original file's decoded bytes. Recommended — enables end-to-end integrity verification at finish time.
- `tags` (array): Tags to apply, e.g. ['logo', 'brand']

### `upload_asset_chunk` (~94 tokens)

Send one chunk of a base64-encoded file previously started with upload_asset_begin. Chunks must be sent in order, index starting at 0, with no gaps.

Input parameters:

- `fileBase64Chunk` (string, required): A slice of the full base64 string for this chunk
- `index` (integer, required): 0-based, contiguous chunk index
- `uploadId` (string, required): Upload ID returned by upload_asset_begin

### `upload_asset_finish` (~59 tokens)

Finalize a chunked asset upload: reassembles the buffered chunks, verifies integrity, and uploads the asset to the brand library. Returns the new asset ID and URL.

Input parameters:

- `uploadId` (string, required): Upload ID returned by upload_asset_begin

### `upload_asset_abort` (~38 tokens)

Cancel a chunked asset upload in progress and discard any buffered chunks.

Input parameters:

- `uploadId` (string, required): Upload ID returned by upload_asset_begin

### `import_asset_from_url` (~160 tokens)

Import an asset into the brand library from a public URL. The server fetches and stores the file. Set assetType (e.g. 'google-image') when importing a result surfaced by search_web_images — it is recorded as a tag on the asset to preserve provenance.

Input parameters:

- `assetType` (string): Optional source-provenance for the imported asset, e.g. 'google-image' when importing a search_web_images result. Recorded as a tag (not a separate field).
- `brandId` (string): Brand ID (defaults to active brand)
- `name` (string): Display name for the asset
- `tags` (array)
- `url` (string, required): Publicly accessible URL of the image/video/PDF to import

### `import_assets_csv` (~150 tokens)

Batch-import up to 50 assets by providing an array of public URLs. All are added to the brand library. Supports detail param for the returned asset list.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `urls` (array, required): Array of public URLs to import

### `tag_asset` (~73 tokens)

Add or remove tags on an asset. Provide addTags and/or removeTags as arrays.

Input parameters:

- `addTags` (array): Tags to add
- `assetId` (string, required): Asset ID
- `brandId` (string): Brand ID (defaults to active brand)
- `removeTags` (array): Tags to remove

### `delete_asset` (~64 tokens)

Soft-delete an asset from the brand library. Pass confirm: true to proceed.

Input parameters:

- `assetId` (string, required): Asset ID to delete
- `brandId` (string): Brand ID (defaults to active brand)
- `confirm` (boolean, required): Must be true to confirm deletion

### `list_asset_tags` (~36 tokens)

List all unique tags used across the brand's asset library.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `suggest_assets_for_post` (~67 tokens)

Get AI-suggested assets from the brand library that match a given post context or topic.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `context` (string, required): Post content or topic to find matching assets for
- `limit` (integer)

### `search_stock_images` (~190 tokens)

Search stock photo/video libraries for images matching a query. Returns URLs and descriptions. Use import_asset_from_url to add a result to the library. Supports detail param: short=url+thumbnail, medium=adds description+credit, full=raw.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `platform` (string): Platform to optimize image dimensions for, e.g. 'linkedin'
- `query` (string, required): Search query, e.g. 'startup team meeting'

### `generate_post_visual_options` (~222 tokens)

Fetch available visual options for a post — card templates, library matches, stock photos. Returns a bestPick recommendation plus categorized options per platform. Use pick_post_visual to apply one of the returned options. Returns slim option list with pickArgs by default (medium). Use detail='short' for counts only, 'full' for raw catalog. Includes editInVisualEditor: a direct URL to edit the post in the visual editor (when active brand is set).

Input parameters:

- `category` (string): Filter by visual category
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "medium". Lists…
- `platform` (string): Filter to a specific platform, e.g. 'linkedin'
- `postId` (string, required): Post ID

### `regenerate_post_visual` (~200 tokens)

Regenerate the visual option set for a post — refreshes stock photo results and re-scores library assets. Returns slim option list with pickArgs by default (medium). Use detail='short' for counts only, 'full' for raw catalog. Includes editInVisualEditor: a direct URL to edit the post in the visual editor (when active brand is set).

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "medium". Lists…
- `loadExternal` (boolean): Also reload external stock sources
- `platform` (string): Limit regeneration to a specific platform
- `postId` (string, required): Post ID

### `pick_post_visual` (~250 tokens)

Select a visual for a post on a given platform. Use the chosen option's `pickArgs` from generate_post_visual_options VERBATIM — for card/quote templates that means passing kind + style + variant together; for a library/smart asset or a stock photo, pass the assetId or slot from its pickArgs. Do not invent style names. variant is a 1-based template variant index. Includes editInVisualEditor: a direct URL to edit the post in the visual editor (when active brand is set).

Input parameters:

- `assetId` (string): Library asset ID from list_assets
- `kind` (string): Visual kind, taken verbatim from the chosen option's pickArgs. REQUIRED when picking a card or quote template style (e.g. kind:'quote' with style:'gradient-bold'). If omitted, the server assumes 'car…
- `platform` (string, required): Target platform, e.g. 'linkedin'
- `postId` (string, required): Post ID
- `slot` (string): Internal template slot key (advanced)
- `style` (string): Template style name from generate_post_visual_options
- `variant` (integer): Template variant index

### `clear_post_visual` (~74 tokens)

Remove the selected visual from a post for a specific platform. Includes editInVisualEditor: a direct URL to edit the post in the visual editor (when active brand is set).

Input parameters:

- `platform` (string, required): Platform to clear the visual for, e.g. 'linkedin'
- `postId` (string, required): Post ID

### `list_post_cards` (~144 tokens)

List the carousel cards attached to a post. Cards are used for LinkedIn carousel / PDF generation. Returns {index,title} by default; use detail='medium' for truncated body, 'full' for all fields.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `postId` (string, required): Post ID

### `edit_post_card` (~113 tokens)

Edit a single carousel card on a post by its 1-based index. Provide title and/or body to update. Set rerender=true to regenerate the visual asset.

Input parameters:

- `body` (string): Card body text
- `cardIndex` (integer, required): 1-based card index (1 = first card)
- `number` (string): Card number label
- `postId` (string, required): Post ID
- `rerender` (boolean): Regenerate visual assets after edit
- `title` (string): Card title

### `set_post_cards` (~72 tokens)

Bulk-replace all carousel cards for a post. Provide a full array of card objects with title, body, and optional number.

Input parameters:

- `cards` (array, required): Full replacement array of card objects
- `postId` (string, required): Post ID
- `rerender` (boolean): Regenerate visual assets after update

### `generate_post_carousel` (~96 tokens)

Generate a carousel PDF for a LinkedIn (or other) post from the post's cards. Returns an asset ID and download URL for the PDF. Requires cards to be set first via set_post_cards or edit_post_card.

Input parameters:

- `postId` (string, required): Post ID
- `style` (string): Card template style name
- `title` (string): Carousel title override
- `variant` (integer): Template variant index

### `search_web_images` (~318 tokens)

Search Google Images across the whole web for a query — broader than search_stock_images, which only searches licensed stock photo/video libraries. Returns image URLs, the source site, and a link back to the source page. openLicensedOnly is a best-effort source filter, not a legal license guarantee — agents and users should still verify usage rights before publishing any result. Use import_asset_from_url to add a chosen result to the library. Supports detail param: short=url+source, medium=adds sourceUrl+alt+dimensions, full=raw.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `gl` (string): Google country code to bias results, e.g. 'us'
- `hl` (string): Google language code, e.g. 'en'
- `maxResults` (integer): Maximum number of results to return (1–100)
- `openLicensedOnly` (boolean): Best-effort filter to sources that tend to be openly licensed. Not a legal guarantee — still verify usage rights before publishing.
- `query` (string, required): Search query, e.g. 'startup team meeting'

### `web_search` (~170 tokens)

Perform a live, real-time web search and get back a raw answer with sources — for questions like 'find me the top 10 places to share/index my MCP', 'what's the latest on X', or 'where can I find Y'. Distinct from search_web_images, which only returns images — this tool returns a synthesized text answer plus the sources it drew from (each with title, url, and optional description), and optionally related follow-up prompts. Results are returned as-is (unsummarized) — pass the answer and sources straight through to the user.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `prompt` (string, required): The search query or question to answer via live web search, e.g. 'top 10 places to share/index my MCP'

### `list_landing_pages` (~131 tokens)

Lists landing pages. Default detail='short' (id/slug/name/status). Use view_landing_page for full content.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `generate_landing_page` (~152 tokens)

Create and AI-generate a new landing page for the brand. Step 1: Creates the LP record with the given slug. Step 2: Kicks off async AI content generation immediately. Returns { slug, operationId, pollUrl } — poll with get_job(operationId) until state is 'completed' (or 'failed'/'partially_failed'/'cancelled' on error).

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `slug` (string): URL slug (auto-derived from topic if omitted)
- `topic` (string, required): Topic or product this landing page should be about
- `voiceProfileId` (string): Voice profile ID for writing style

### `view_landing_page` (~154 tokens)

Fetch a landing page by slug. detail='full' (default) returns full data JSONB; 'medium' returns summary + sectionKeys/sectionWordCounts; 'short' returns id/slug/name/status. Section bodies and rendered HTML appear only at full.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `slug` (string, required): Landing page slug

### `edit_landing_page` (~63 tokens)

Update the title or instructions of a landing page. For AI-powered edits, use vibe_edit_landing_page.

Input parameters:

- `instructions` (string): Editor instructions stored for reference
- `slug` (string, required): Landing page slug
- `title` (string): New title

### `set_landing_page` (~94 tokens)

Overwrite the content and/or metadata of a landing page. Pass content as a string (HTML or markdown). Pass metadata as a JSON object. Returns a versionId — all writes are versioned.

Input parameters:

- `content` (string): Full page content (HTML or markdown)
- `metadata` (object): Arbitrary metadata object
- `slug` (string, required): Landing page slug
- `title` (string): New title

### `regenerate_landing_page` (~92 tokens)

Re-generate a landing page's content using AI. Optionally restrict to specific sections. Returns an operationId — poll with get_job to track progress.

Input parameters:

- `instructions` (string): Extra guidance for the AI
- `sections` (array): Specific section keys to regenerate (omit to regenerate all)
- `slug` (string, required): Landing page slug
- `voiceProfileId` (string): Voice profile ID

### `vibe_edit_landing_page` (~128 tokens)

Use AI to edit a landing page based on natural-language instructions. Returns an operationId — poll with get_vibe_edit_status or get_job until status is 'completed'. Optionally limit to a specific scope ('headline' | 'cta' | 'full') or a single sectionId.

Input parameters:

- `instructions` (string, required): Natural-language edit instructions, e.g. 'Make the CTA more urgent'
- `scope` (string): Restrict edits to a specific section type
- `sectionId` (string): Specific section ID to edit
- `slug` (string, required): Landing page slug

### `get_vibe_edit_status` (~150 tokens)

Poll vibe (AI) edit status. detail='full' (default) includes the result payload; 'short'/'medium' return just state+progress.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `operationId` (string, required): Operation ID from vibe_edit_landing_page
- `slug` (string, required): Landing page slug

### `publish_landing_page` (~33 tokens)

Publish a landing page, making it publicly accessible at its URL.

Input parameters:

- `slug` (string, required): Landing page slug

### `delete_landing_page` (~51 tokens)

Permanently delete a landing page. Pass confirm: true to proceed — this is irreversible.

Input parameters:

- `confirm` (boolean, required): Must be true to confirm deletion
- `slug` (string, required): Landing page slug

### `view_lp_draft` (~144 tokens)

View the unpublished draft of a landing page. detail='full' (default) includes full versionData; 'medium' adds sectionKeys/sectionWordCounts; 'short' is id/slug/name/status only.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `slug` (string, required): Landing page slug

### `list_lp_versions` (~123 tokens)

List all saved versions of a landing page. Default detail='short'. Use view_lp_version to see section content.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `slug` (string, required): Landing page slug

### `view_lp_version` (~143 tokens)

View a specific LP version. detail='full' (default) returns full data; 'medium' adds sectionKeys/sectionWordCounts.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `slug` (string, required): Landing page slug
- `versionId` (integer, required): Numeric version ID from list_lp_versions

### `list_side_pages` (~131 tokens)

List side pages attached to a landing page. Default detail='short'. Heavy JSONB (overrides/config) only at full via view_side_page.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `slug` (string, required): Parent landing page slug

### `generate_side_page` (~363 tokens)

AI-generates a side page under a parent landing page. Async — kicks off the side-page generator and returns `{ success, operationId, operationRowId, pollUrl, sidePageId }`. This is the GENERATOR (POST /side-pages/generate) — NOT a row creator. Comparison-type briefs may run synchronously and return `sidePageId` directly with no operationId. Two body modes:   • freeform: pass `key` + `prompt` (+ optional `keywords`, `selectedSections`, `voiceProfileId`, `sidePageType`).   • brief: pass `key` + `brief` (structured outline) + optional `briefId` and `roadmapItemId`. Poll `get_job` until `state` is `completed` (or `failed`/`partially_failed`/`cancelled` on error).

Input parameters:

- `autoAssignAssets` (boolean): Auto-assign brand assets to image slots after generation
- `brief`: Brief-mode structured outline. When set, this is the canonical payload.
- `briefId` (string): Persisted SeoBrief ID — required for comparison-type generation
- `key` (string, required): Side-page key (URL slug fragment under the parent LP)
- `keywords` (array): Freeform-mode: target keywords to weave into the page
- `prompt` (string): Freeform-mode generation prompt (omit when passing `brief`)
- `roadmapItemId` (string): Roadmap item ID this side page is fulfilling
- `sidePageType` (string): Defaults to 'landing'. Use 'comparison' only with a persisted comparison briefId.
- `slug` (string, required): Parent landing page slug
- `voiceProfileId` (string): Voice profile to write in

### `view_side_page` (~171 tokens)

View a side page including sections and rendered HTML. detail='full' (default) includes rendered HTML and full overrides; 'medium' gives summary + overrideSectionKeys; 'short' gives id/slug/name/type/isPublished. Rendered HTML appears only at full.

Input parameters:

- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `sideKey` (string, required): Side page key (from list_side_pages)
- `slug` (string, required): Parent landing page slug

### `edit_side_page` (~64 tokens)

Update the instructions or metadata of a side page. For section-level edits, use set_side_page_section.

Input parameters:

- `instructions` (string): Updated instructions for the AI
- `sideKey` (string, required): Side page key
- `slug` (string, required): Parent landing page slug

### `delete_side_page` (~56 tokens)

Delete a side page. Pass confirm: true to proceed.

Input parameters:

- `confirm` (boolean, required): Must be true to confirm deletion
- `sideKey` (string, required): Side page key
- `slug` (string, required): Parent landing page slug

### `set_side_page_section` (~87 tokens)

Update the content of a specific section within a side page.

Input parameters:

- `content` (string): New section content (HTML or markdown)
- `instructions` (string): AI-guided edit instructions for this section
- `sectionId` (string, required): Section ID from view_side_page
- `sideKey` (string, required): Side page key
- `slug` (string, required): Parent landing page slug

### `set_side_page_state` (~71 tokens)

Publish or unpublish a side page. Set published=true to make it live, false to pull it back to draft.

Input parameters:

- `published` (boolean, required): true = publish, false = unpublish
- `sideKey` (string, required): Side page key
- `slug` (string, required): Parent landing page slug

### `dashboard_link` (~111 tokens)

Return a clickable browser link to a PostKing dashboard page. ALWAYS use this instead of guessing dashboard URLs. The SEO roadmap / content plan is section 'seo_briefs' (or 'seo' for the overview) — there is NO '/seo/roadmap' page.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `section` (string): Dashboard section to link to. Defaults to 'overview'. SEO roadmap/content plan → 'seo_briefs'. Reddit module → 'reddit'.

### `reddit_get_pool` (~144 tokens)

Returns the brand's pool of relevant subreddits — THIS IS the brand-level subreddit match (each scored). Use it to answer 'which/what/top N subreddits should my brand post in?' — NO content needed; results are sorted most-relevant first; pass `top` for a top-N list. (reddit_suggest is a different, content-specific step.) Reddit is a repurpose-to-Reddit workflow, NOT a scheduled publishing medium.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `top` (integer): Return only the top N most-relevant subreddits (results are always sorted most-relevant first)

### `reddit_generate_pool` (~98 tokens)

Kick off async generation of a subreddit pool for the brand — finds relevant subreddits by crawling Reddit. Typically takes ~1–3 min. Returns { operationId, status }. Poll get_job with the operationId until state=completed, then call reddit_get_pool to see results. Reddit is a repurpose-to-Reddit workflow, NOT a scheduled publishing medium.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `reddit_global_pool` (~50 tokens)

Return stats on the global subreddit dataset (count of known subreddits available for matching). Informational only — no DB writes.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `reddit_suggest` (~333 tokens)

Given a brand's content (a blog post by id, or pasted title+text), return up to 8 best-fit subreddits from the brand's pool, each with 2-3 distinct posting angles (angle type + tailored title + framing hook), plus promotion mode, buyer intent, match score, and the key rule to watch. Use an angle's title/hook to then draft a native post via reddit_rewrite. short {subreddit,match_score,promotion_mode,buyer_intent,angle} (angle = first angle only); medium adds reason,rule_to_watch,angles (all angles); full = raw. Synchronous, 0 credits. Requires the brand pool to exist — call reddit_generate_pool first if it doesn't. Flow step 2 of 4: pool → SUGGEST → rewrite → list_posts.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `content` (string): Content body (optional; used with title when not passing postId)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "medium". Lists…
- `postId` (string): BlogArticle ID to suggest for (use this OR title+content)
- `title` (string): Content title (required when not passing postId)

### `reddit_rewrite` (~305 tokens)

Async. Rewrite a blog article (or raw content) into a Reddit-native post for a specific subreddit. Typically takes ~30–90 sec per variation. This tool polls inline and returns the finished Reddit post once generation completes — you do NOT need to poll separately. If it times out before completing, it returns a postId; call get_post with that postId until operationStatus is COMPLETED. Requires pool to exist. The subreddit must be in the brand's pool. Flow step 3 of 4: pool → suggest → REWRITE → list_posts.

Input parameters:

- `angle` (string): Angle/framing for the post, e.g. 'educational', 'story' (default: 'educational')
- `brandId` (string): Brand ID (defaults to active brand)
- `length` (string): Target post length
- `sourceContent` (string): Raw content body to rewrite (use this OR sourcePostId)
- `sourcePostId` (string): BlogArticle ID to rewrite (use this OR sourceContent)
- `sourceTitle` (string): Title of the source content (used with sourceContent)
- `subreddit` (string, required): Target subreddit name (from reddit_suggest results, e.g. 'entrepreneur')
- `variations` (integer): Number of variations to generate (default 1, max 3)
- `voiceId` (string, required): Voice profile ID (use list_voices to get IDs; pass 'none' for no voice)

### `reddit_list_posts` (~138 tokens)

List saved Reddit posts (outputs of reddit_rewrite). Cursor-paginated. Returns { posts: [{ id, platform, content, outputData, sessionId, voiceId, createdAt }], nextCursor }. outputData contains: redditTitle, body, notes, wordCount, angle, length, subreddit, variationIndex. Flow step 4 of 4: pool → suggest → rewrite → LIST_POSTS.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `cursor` (string): Pagination cursor from a previous page
- `limit` (integer): Page size (default 20, max 50)

### `storyline_list` (~135 tokens)

List the brand's storylines (marketing plans). short detail {id,title,status}; medium adds isLive, dates, promptSummary; full = raw.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `storyline_create` (~107 tokens)

Create a new storyline (marketing plan) for the brand. Pass a prompt describing the campaign goal; optionally provide a title. After creation, call storyline_clarify to gather context, then storyline_generate_brief.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `prompt` (string, required): Campaign goal or intent — e.g. 'Q3 product launch for Feature X targeting SMBs'
- `title` (string): Optional display title (auto-generated if omitted)

### `storyline_get` (~161 tokens)

Fetch a single storyline by ID with its full state (brief, strategy, line items, status). Returns full detail by default. Use detail="medium" for a compact summary or detail="short" for {id,title,status}.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `storylineId` (string, required): Storyline ID

### `storyline_update` (~140 tokens)

Update metadata on a storyline: title, live status, or start/end/launch dates. Pass at least one field to change.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `isLive` (boolean): Whether the storyline is currently live/active
- `launchDate`: ISO 8601 launch date, or null to clear it
- `storylineEndDate` (string): ISO 8601 end date for the storyline
- `storylineId` (string, required): Storyline ID
- `storylineStartDate` (string): ISO 8601 start date for the storyline
- `title` (string): New display title

### `storyline_delete` (~72 tokens)

Archive (soft-delete) a storyline. Pass confirm: true to proceed. Use storyline_restore to reverse this.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `confirm` (boolean, required): Must be true to confirm archiving
- `storylineId` (string, required): Storyline ID to archive

### `storyline_restore` (~45 tokens)

Restore an archived storyline back to active state.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `storylineId` (string, required): Storyline ID to restore

### `storyline_clarify` (~102 tokens)

Run the intake/clarification step for a storyline. The API returns follow-up questions or a readiness signal; call iteratively until the system has enough context. After clarification is complete, call storyline_generate_brief.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `storylineId` (string, required): Storyline ID
- `userMessage` (string): User's response to the last clarifying question (omit for the first call)

### `storyline_generate_brief` (~108 tokens)

Async. Generate the marketing brief for a storyline (positioning, key messages, audience, timing, tone notes). Returns { operationId, status } — poll get_job with the operationId until state=completed. Typically takes ~1–3 min. After completion, review the brief with storyline_get, then call storyline_confirm_brief to advance to strategy.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `storylineId` (string, required): Storyline ID

### `storyline_set_brief` (~143 tokens)

Manually set (replace) the full brief for a storyline. Use to provide a human-authored brief or to push back an edited version retrieved via storyline_get. Pass the complete brief object and the current expectedVersion (use 0 when setting from scratch). After setting, call storyline_confirm_brief to advance to strategy generation.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `brief` (object, required): Full brief object — replaces the entire current brief
- `expectedVersion` (integer, required): Optimistic-concurrency version from the current brief (pass 0 when setting from scratch)
- `storylineId` (string, required): Storyline ID

### `storyline_edit_brief` (~112 tokens)

Apply an AI-driven edit to the current brief using a natural-language instruction. Examples: 'Make the tone more casual', 'Add a proof point about our 99% uptime SLA'. Optionally pass expectedVersion for optimistic concurrency.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `expectedVersion` (integer): Optimistic-concurrency version (omit to skip version check)
- `instruction` (string, required): Natural-language edit instruction
- `storylineId` (string, required): Storyline ID

### `storyline_confirm_brief` (~61 tokens)

Confirm (lock) the brief for a storyline, advancing it to the strategy phase. After confirmation, call storyline_generate_strategy.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `storylineId` (string, required): Storyline ID

### `storyline_get_strategy` (~163 tokens)

Fetch the strategy and line items for a storyline. Returns full detail by default (raw strategy + all line items). Use detail="medium" for a compact summary or detail="short" for {id,status} + line-item count.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `storylineId` (string, required): Storyline ID

### `storyline_generate_strategy` (~102 tokens)

Async. Generate the strategy and line-items for a storyline based on its confirmed brief. Returns { operationId, status } — poll get_job with the operationId until state=completed. Typically takes ~2–5 min. After completion, review with storyline_get_strategy, then optionally edit with storyline_edit_strategy or add/remove line items.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `storylineId` (string, required): Storyline ID

### `storyline_edit_strategy` (~89 tokens)

Apply an AI-driven edit to the current strategy using a natural-language instruction. Examples: 'Add a LinkedIn video post for launch week', 'Remove the influencer outreach line items'.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `instruction` (string, required): Natural-language instruction for editing the strategy and/or line items
- `storylineId` (string, required): Storyline ID

### `storyline_estimate` (~80 tokens)

Read-only dry-run. Estimate the credits required to execute the storyline without actually running it. Use before storyline_execute to surface the cost to the user for approval. Returns { estimatedCredits, lineItemBreakdown } or similar.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `storylineId` (string, required): Storyline ID

### `storyline_add_line_item` (~204 tokens)

Add a new line item (deliverable) to a storyline's strategy. Specify the channel, type, title, description, and a config map (pass {} if no extra config).

Input parameters:

- `automatable` (boolean): Whether PostKing can auto-generate this item (defaults to server-side logic)
- `brandId` (string): Brand ID (defaults to active brand)
- `channel` (string, required): Publishing channel — e.g. 'linkedin', 'twitter', 'blog', 'email'
- `config` (object, required): Channel/type-specific configuration — pass {} if none
- `description` (string, required): What this piece of content should achieve or cover
- `storylineId` (string, required): Storyline ID
- `targetDate` (string): Target publish date (ISO 8601)
- `title` (string, required): Line-item headline / title
- `type` (string, required): Content type — e.g. 'post', 'article', 'newsletter', 'video'

### `storyline_update_line_item` (~158 tokens)

Update a line item on a storyline — select/deselect, retitle, change description, targetDate, or config. Pass only the fields to change.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `config` (object): Replacement channel/type-specific configuration
- `description` (string): New description for the line item
- `itemId` (string, required): Line item ID from storyline_get_strategy
- `selected` (boolean): Include (true) or exclude (false) this line item from execution
- `storylineId` (string, required): Storyline ID
- `targetDate` (string): New target publish date (ISO 8601)
- `title` (string): New title for the line item

### `storyline_delete_line_item` (~81 tokens)

Permanently remove a line item from a storyline's strategy. Pass confirm: true to proceed.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `confirm` (boolean, required): Must be true to confirm deletion
- `itemId` (string, required): Line item ID to delete
- `storylineId` (string, required): Storyline ID

### `storyline_regenerate_line_item` (~90 tokens)

Async. Re-generate a single line item's content draft. Returns { operationId, status } — poll get_job with the operationId until state=completed. Typically takes ~1–3 min.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `itemId` (string, required): Line item ID to regenerate
- `storylineId` (string, required): Storyline ID

### `storyline_execute` (~92 tokens)

Async. Execute a storyline — generates content drafts for all selected line items. Returns { operationId, status } — poll get_job with the operationId until state=completed. Typically takes ~3–8 min. Call storyline_estimate first to surface the credit cost before execution.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `storylineId` (string, required): Storyline ID to execute

### `competitor_list` (~156 tokens)

List the brand's tracked competitors. short {id,domain,analysisState}; medium adds source,creditsCharged,lastError,addedAt,lastCrawledAt,cacheSource; full = raw. Also returns a dashboard link to the Competitors tab.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `competitor_add` (~98 tokens)

Async. Batch-add competitor domains (1–20) to the brand. Returns { operationId, status } — poll get_job until state=completed. Each domain triggers crawl + profile analysis. Typically takes ~2–5 min.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `domains` (array, required): One to twenty competitor domains to add (e.g. ['acme.com', 'rival.io']).

### `competitor_update` (~108 tokens)

Update a competitor's settings. Currently supports toggling excludeFromSeoPull (removes the domain from keyword-gap pull without deleting it). Send an empty body to restore a soft-deleted competitor.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `competitorId` (string, required): BrandCompetitor ID to update.
- `excludeFromSeoPull` (boolean): When true, excludes this competitor from the SEO keyword-pull. Omit to restore a soft-deleted row.

### `competitor_delete` (~61 tokens)

Soft-delete a tracked competitor from the brand. No credits are refunded. Use competitor_update (empty body) to restore.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `competitorId` (string, required): BrandCompetitor ID to delete.

### `competitor_analyze` (~106 tokens)

Async. Trigger analysis for existing BrandCompetitor rows that are pending or failed. Use competitor_add to create new rows; use this to retry failed ones. Returns { operationId, status } — poll get_job until state=completed. Typically takes ~2–5 min.

Input parameters:

- `brandCompetitorIds` (array, required): IDs of existing BrandCompetitor rows to (re-)analyze (1–20).
- `brandId` (string): Brand ID (defaults to active brand)

### `competitor_refresh` (~64 tokens)

Async. Refresh all active competitors for the brand (re-crawl + re-profile). Returns { operationId, status } — poll get_job until state=completed. Typically takes ~2–5 min.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `competitor_get_comparison` (~162 tokens)

Get the brand's head-to-head competitive comparison. short {id,status,generatedAt,competitorCount}; medium adds summary+strengths+weaknesses; full = raw. Returns null when no comparison has been generated yet — use competitor_recompute_comparison to generate.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…

### `competitor_recompute_comparison` (~53 tokens)

Trigger a fresh head-to-head comparison recompute. Use after adding or re-analyzing competitors to get an up-to-date comparison.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `competitor_get_overview` (~147 tokens)

Get the brand's competitive landscape overview. short {id,status,generatedAt}; medium adds summary excerpt; full = raw. Returns null when no overview exists — use competitor_generate_overview to create one.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…

### `competitor_generate_overview` (~62 tokens)

Async. Generate the competitive landscape overview (uses ~20 credits). Returns { operationId, status } — poll get_job until state=completed. Typically takes ~2–5 min.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `competitor_comparison_sources` (~56 tokens)

List the data sources used to build the brand's competitive comparison. Returns raw source records (URLs, snippets, timestamps) — useful for auditing comparison accuracy.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `competitor_probe` (~85 tokens)

Start a competitor discovery probe. PostKing crawls the web to find rival domains automatically. Returns { started: true } when kicked off, or { alreadyRan, status } when a probe result already exists. Poll competitor_probe_status to check readiness, then use competitor_probe_classify to accept/reject candidates.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `competitor_probe_status` (~66 tokens)

Check the status of the competitor discovery probe. Returns { status, candidates? } — status is one of: pending | running | completed | failed. When completed, candidates contains the discovered domains ready for classification.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)

### `competitor_probe_classify` (~162 tokens)

Classify a discovered (or manually added) competitor candidate. 'direct' seeds a BrandCompetitor row; 'similar' and 'not_relevant' update the probe JSON only. Call once per domain. Probe must be in completed state first. Returns { candidates } — the updated full candidate list.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `classification` (string, required): 'direct' = head-to-head competitor (adds to tracked list); 'similar' = adjacent market; 'not_relevant' = not a competitor.
- `domain` (string, required): The competitor domain to classify (e.g. 'rival.io').
- `name` (string): Optional human-readable name for the competitor (used when adding manually).

### `knowledge_list` (~203 tokens)

List the brand's knowledge-base items. short {id,name,contentType}; medium adds tags+summary+timestamps; full = raw. Filter by tag, active status, or global scope.

Input parameters:

- `activeOnly` (boolean): When true, only return active (non-soft-deleted) items. Defaults to true on the backend.
- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `global` (boolean): When true, only return account-wide (isGlobal) items.
- `tag` (string): Filter by tag (exact match).

### `knowledge_create` (~235 tokens)

Async. Create a new knowledge-base item for the brand. Returns { operationId, status } — poll get_job until state=completed. If description or tags are omitted, PostKing auto-generates them from the content. For contentType='json', content must be valid JSON — this tool validates client-side before calling the API. Typically takes ~15–30 s.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `content` (string, required): The full content body. Must be valid JSON string when contentType='json'.
- `contentType` (string, required): Content format: 'text' for prose/markdown, 'json' for structured data.
- `description` (string): Optional short description. If omitted, PostKing auto-generates one from the content.
- `isGlobal` (boolean): Mark as account-wide knowledge (shared across all the user's brands)
- `name` (string, required): Human-readable name for the knowledge item (e.g. 'Brand Voice Guidelines').
- `tags` (array): Optional tags for filtering (e.g. ['brand', 'voice']). Auto-generated if omitted.

### `knowledge_get` (~163 tokens)

Fetch a single knowledge-base item by ID. short {id,name,contentType}; medium adds tags+summary+timestamps; full = raw (includes full content). Use detail='full' to retrieve the complete content body.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `itemId` (string, required): KnowledgeBase item ID to retrieve.

### `knowledge_update` (~204 tokens)

Update a knowledge-base item. Minor edits (name, tags, description) return the updated item synchronously. When content changes significantly, PostKing queues an AI description regeneration and returns { operationId, status } — poll get_job until state=completed. For contentType='json', content must be valid JSON — this tool validates client-side before calling the API. Typically takes ~15–30 s.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `content` (string): Updated content body. Must be valid JSON when contentType (new or existing) is 'json'.
- `contentType` (string): Updated content format. Must still match the content body.
- `description` (string|null): Updated description. Pass null to clear it.
- `itemId` (string, required): KnowledgeBase item ID to update.
- `name` (string): Updated name.
- `tags` (array): Replacement tag list. Pass [] to clear all tags.

### `knowledge_delete` (~72 tokens)

Soft-delete a knowledge-base item. The item is deactivated (isActive=false) and no longer appears in knowledge_list. This action is not reversible via the API.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `itemId` (string, required): KnowledgeBase item ID to soft-delete.

### `brand_truth_list` (~244 tokens)

List the brand's stored brand truths (atomic facts/observations used to ground generation). short {id,name,type,pinned}; medium adds personaScope+tags+summary+timestamps; full = raw. Filter by type, personaScope, tags (array or comma-separated), or a free-text query.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…
- `limit` (integer): Max number of entries to return.
- `personaScope` (string): Filter by persona scope.
- `query` (string): Free-text search across truth name/content.
- `tags`: Filter by tags. Accepts an array (['pricing','tone']) or a comma-separated string ('pricing,tone').
- `type` (string): Filter by truth type.

### `brand_truth_get` (~165 tokens)

Fetch a single brand truth by ID. short {id,name,type,pinned}; medium adds personaScope+tags+summary+timestamps; full = raw (includes full content). Use detail='full' to retrieve the complete content body.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…
- `id` (string, required): Brand truth entry ID to retrieve.

### `brand_truth_create` (~192 tokens)

Capture brand truths from plain natural-language text. Describe facts/observations about the brand in ordinary prose — a sentence or short paragraph (≤8000 chars). Do NOT pre-classify, pre-format, or guess the taxonomy: PostKing's LLM extraction pipeline reads the text and decides which atomic 'brand truths' to persist and which to skip (duplicates, too vague, or previously rejected facts). Returns { addedCount, skippedCount, added, skipped }: 'added' are the truths that were stored; 'skipped' lists each rejected quote with a reason. Review both — the skipped reasons tell you what didn't make it in and why.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `text` (string, required): Free-form natural-language description of facts/observations about the brand (≤8000 chars). Write it as plain prose — the extraction pipeline handles classification.

### `brand_truth_update` (~204 tokens)

Targeted edit of an existing, known brand truth (by ID). This does NOT re-run the LLM extraction pipeline — it writes the provided fields directly. Use it to correct wording (name/description/content), fix the type/personaScope, adjust tags, or pin/unpin. To capture NEW facts from prose, use brand_truth_create instead.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `content` (string): Updated full content body of the truth.
- `description` (string): Updated description/summary.
- `id` (string, required): Brand truth entry ID to update.
- `name` (string): Updated short label/name.
- `personaScope` (string): Updated persona scope.
- `pinned` (boolean): Pin (true) or unpin (false) this truth.
- `tags` (array): Replacement tag list. Pass [] to clear all tags.
- `type` (string): Reclassify the truth type.

### `brand_truth_delete` (~84 tokens)

Delete a brand truth by ID. This also records rejection memory: the system learns to stop re-suggesting that fact during future extractions. Use it deliberately — deleting a truth teaches PostKing that the fact should not come back.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `id` (string, required): Brand truth entry ID to delete.

### `get_audience` (~201 tokens)

Fetch the brand's audience intelligence / ICP (ideal customer profile). short = compact summary (name + primaryRoles + top painPoints + persona.role); medium adds positioning + fuller demographics; full = the raw payload (audienceData + positioning + persona + contentModulation). `persona` is the ghostwriter persona ({ role, styleNotes, ... }) and may be null. If the brand has no audience data yet, run website/audience analysis (onboarding) first.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "full". Lists d…

### `preview_audience_edit` (~138 tokens)

Read-only helper. Call this BEFORE edit_audience to discover which `sections`/`subsections` values are valid for this brand. Pass the natural-language change you intend to make; returns the available sections/subsections (computed from the brand's current audience data) plus a preview. Use the returned section keys (e.g. 'demographics', 'painPoints', 'positioning') as the required `sections` array for edit_audience.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `prompt` (string, required): Natural-language description of the change you want to make to the audience/ICP.

### `edit_audience` (~257 tokens)

Async LLM ai-edit of the brand's audience/ICP. Describe the change in `prompt` (natural language). `sections` is REQUIRED — the audience section(s) to edit (e.g. 'demographics', 'painPoints', 'positioning'). If unsure which sections are valid for this brand, call preview_audience_edit first and use the section keys it returns. Returns { operationId, status } — poll get_job(operationId) until state is 'completed' (or 'failed'/'cancelled' on error). Only one audience edit can run at a time; a second call returns an 'already_running' notice. Typically takes ~30–90 s.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `prompt` (string, required): Natural-language instruction describing the change to make to the audience/ICP.
- `sections` (array, required): REQUIRED. Audience section keys to edit (e.g. ['demographics'], ['painPoints'], ['positioning']). Call preview_audience_edit to discover valid values for this brand.
- `subsections` (object): Optional map of section → subsection keys to scope the edit more narrowly (e.g. { demographics: ['ageRange'] }).

### `trends_list` (~224 tokens)

Browse top-trending posts (with deconstructions) for a niche + platform. Account/niche-scoped — NOT tied to a specific brand. Each post includes hook, template, pattern, and virality reason from the deconstruction. The crawler runs every 3 days; use days=3 (default) for the freshest batch. Supported niches: ai-saas, marketing, web3. Supported platforms: x.

Input parameters:

- `days` (integer): Look-back window in days (1–30). Defaults to 3 (the freshest crawler batch).
- `limit` (integer): Maximum number of posts to return (1–50). Defaults to 20.
- `niche` (string): Niche to filter by. One of: ai-saas, marketing, web3. Defaults to ai-saas.
- `platform` (string): Platform to filter by. Currently only 'x' is supported. Defaults to x.
- `sort` (string): Sort order: 'engagement' = highest engagement score first (default); 'recent' = newest crawled first.

### `template_list` (~193 tokens)

List the brand's saved content templates. short {id,title,category}; medium adds platforms,isFavorite,bodyPreview (160 chars); full = raw. Pass category to filter (e.g. 'hook', 'cta', 'thread'). Results are ordered by isFavorite desc, then usageCount desc.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `category` (string): Filter templates by category (e.g. 'hook', 'cta', 'thread'). Omit for all.
- `detail` (string): Verbosity of returned item(s): "short" = id + primary label + status (cheap list scan); "medium" = key summary fields + counts (a useful glance); "full" = the complete object. Default "short". Lists…

### `template_create` (~232 tokens)

Create one or more content templates for the brand. Pass a single template object, or pass the 'templates' array param for bulk creation. If both are supplied, 'templates' (bulk) takes precedence. Returns the created template record(s).

Input parameters:

- `body` (string): Template body with placeholders, e.g. '[HOOK] … [CTA]' (required for single creation).
- `brandId` (string): Brand ID (defaults to active brand)
- `category` (string|null): Category label (e.g. 'hook', 'thread', 'cta').
- `example` (string|null): An example post filled in using this template.
- `isFavorite` (boolean): Mark as favorite to surface it first in template lists.
- `pattern` (string|null): The rhetorical or structural pattern described in plain language.
- `platforms` (array): Platforms this template is best suited for (e.g. ['x', 'linkedin']).
- `templates` (array): Bulk-create multiple templates in one call. Overrides the single-template fields when present.
- `title` (string): Template title (required for single creation).

### `template_update` (~146 tokens)

Update an existing content template. All fields are optional — only supplied fields are changed. Use isFavorite=true/false to toggle favorite status.

Input parameters:

- `body` (string): New template body.
- `brandId` (string): Brand ID (defaults to active brand)
- `category` (string|null): New category label.
- `example` (string|null): New example post.
- `isFavorite` (boolean): Set true to favorite, false to un-favorite.
- `pattern` (string|null): New pattern description.
- `platforms` (array): Updated platform list.
- `templateId` (string, required): ID of the content template to update.
- `title` (string): New title.

### `template_delete` (~53 tokens)

Permanently delete a content template from the brand. This action is irreversible.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `templateId` (string, required): ID of the content template to delete.

### `template_extract` (~121 tokens)

AI extracts a reusable content template from a pasted post text. Synchronous (seconds) — no polling needed. Pass save=true to also persist the template to the brand's library. Returns the extracted template object; if saved, includes the new template id.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `postText` (string, required): The full text of the post to deconstruct into a reusable template (min 10 chars).
- `save` (boolean): When true, the extracted template is saved to the brand's template library.

### `template_pick` (~186 tokens)

RE-RANKER: AI scores a caller-supplied list of candidate templates and picks the N best-fitting ones for a given theme or topic. This tool does NOT read stored templates server-side — the caller must supply the candidates explicitly (e.g. call template_list first, then pass the results here). Synchronous — no polling needed. Pass the templates array from template_list (full detail) as candidates. Returns { templateIds } — an ordered array of the winning template IDs.

Input parameters:

- `brandId` (string): Brand ID (defaults to active brand)
- `count` (integer, required): How many templates to pick (1–20).
- `templates` (array, required): Candidate templates to evaluate. Use template_list with detail='full' and pass the relevant fields.
- `theme` (string, required): The topic or theme the agent should optimise for (e.g. 'AI productivity tips').

### `billing_list_packs` (~71 tokens)

List available credit packs for one-off top-ups. Returns all packs with their SKU, price in USD, and credit amount. Call this tool first to show the user their options, then ask which pack they want. Only after the user explicitly chooses a pack, call billing_topup with that packSku.

### `billing_topup` (~324 tokens)

Top up credits by charging the account's card on file (Stripe off-session). Prefers the headless path — no checkout link required. IMPORTANT: Only call this tool after the USER has explicitly chosen a specific pack — it triggers a real charge. To show the user their options first, call billing_list_packs and present the results; do NOT pick a pack on the user's behalf. The response is ONE OF two shapes: (1) Headless success (default when a card is on file): { status: 'paid', paymentIntentId, amountUsd, credits, balance, packSku, cardLast4/receiptUrl when available }. When you receive status === 'paid', the payment has already completed and the credits are in the wallet NOW. Announce to the user: payment ID, last-4 card digits, credits added, and new balance. You are DONE — do NOT call billing_wallet to poll. (2) Checkout fallback (only when there is no card on file): { checkoutUrl, sessionId, amountUsd, credits, packSku }. When you receive checkoutUrl, hand that link to the user so they can complete payment in their browser. Valid skus: agent_4 ($4 / 160 credits), agent_5 ($5 / 220 credits), agent_25 ($25 / 1200 credits), agent_50 ($50 / 2600 credits).

Input parameters:

- `packSku` (string, required): Credit pack SKU to purchase. One of: agent_4, agent_5, agent_25, agent_50.

### `billing_wallet` (~93 tokens)

Fetch the user's credit balance and recent usage entries. Returns { credits, recent } where recent is the last ~10 usage rows. Poll this to confirm a top-up landed ONLY after a billing_topup CHECKOUT result (one that returned a checkoutUrl), until credits rise. Do NOT poll after a status:'paid' headless top-up — those credits are already applied and the new balance is in the topup response.

### `billing_list_tiers` (~59 tokens)

List available PostKing subscription tiers (GROWTH, PRO, ENTERPRISE). Returns each tier's name, price in USD, monthly credits, and billing interval options. Use billing_subscribe to create a subscription Checkout session for a chosen tier.

### `billing_subscribe` (~113 tokens)

Create a Stripe Checkout session for a PostKing subscription. Returns { checkoutUrl, sessionId, tier, interval, amountUsd }. Subscription credits refill User.credits each billing period. The subscription Checkout is best completed by a human; the Link virtual card covers the first invoice only (renewals need a durable payment method).

Input parameters:

- `interval` (string): Billing interval: "month" (default) or "year".
- `tier` (string, required): Subscription tier. One of: GROWTH, PRO, ENTERPRISE.

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/bitsandtea-postking-mcp/postking-mcp#diagnostics

## Score history

- 2026-08-03: 54
- 2026-08-02: 57
- 2026-08-01: 25
- 2026-07-31: 25
- 2026-07-30: 33
- 2026-07-28: 33
- 2026-07-27: 33

## Links

- npm package: https://www.npmjs.com/package/postking-mcp
- Socket report: https://socket.dev/npm/package/postking-mcp
- Repository: https://github.com/bitsandtea/postking-mcp
- Website: https://postking.app/
- Changelog RSS feed: https://verifymcp.io/servers/bitsandtea-postking-mcp/postking-mcp/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/bitsandtea-postking-mcp/postking-mcp/changelog.json
- HTML version of this page: https://verifymcp.io/servers/bitsandtea-postking-mcp/postking-mcp
