# MCP server: drive Pamba from Claude Code, Codex or any MCP client

> Connect an MCP client to your workspace, pick the toolsets you need, and learn how calls, credits, waits, approvals and hand-offs behave before you automate anything.

Pamba's MCP server exposes every tool the in-app assistant has, over the Model Context Protocol, at one endpoint: `https://api.pamba.app/mcp`. Your client's model gets the same product knowledge the assistant runs on, so it can chain the tools into whole workflows (research a format, build the avatar, create the projects, generate, schedule) without you translating screens into API calls. This guide covers what the reference cannot: how the surface is cut, how a call behaves, what waits, what costs credits, and the ordering rules that trip people up. The exact tool list, with every parameter, is the [MCP tool reference](/help/mcp-tool-reference).

## Connect

- **Endpoint:** `https://api.pamba.app/mcp`, Streamable HTTP, one JSON-RPC message per POST.
- **Credential:** an `X-API-Key` header with a workspace API key. Create one in workspace settings (open the workspace switcher, then Settings, then API keys); the secret is shown once. A key belongs to exactly one workspace and every call runs there; sending an `X-Workspace-Id` header that names a different workspace is refused. To work in another workspace, connect with that workspace's key. Three tools act on the person behind the key rather than the connected workspace, because that is what they do in the app too: `delete_workspace` accepts any workspace the person administers, and `list_api_keys` and `revoke_api_key` cover their keys in every workspace. The instructions tell the model to confirm with the person before the destructive two. An `Authorization: Bearer <JWT>` plus `X-Workspace-Id` header works too, the same way it does on the REST API.

Claude Code:

```
claude mcp add --transport http pamba https://api.pamba.app/mcp --header "X-API-Key: <your workspace API key>"
```

Codex (`~/.codex/config.toml` or the project's `.codex/config.toml`):

```
[mcp_servers.pamba]
url = "https://api.pamba.app/mcp"
http_headers = { "X-API-Key" = "<your workspace API key>" }
```

Any client that takes a JSON server list:

```
{
  "mcpServers": {
    "pamba": {
      "type": "http",
      "url": "https://api.pamba.app/mcp",
      "headers": { "X-API-Key": "<your workspace API key>" }
    }
  }
}
```

Once connected, ask the model "What can you do with Pamba?": the answer comes from the product map the server sends at connection, not from guesses.

## Choose how much of the surface to load

The full surface is about three hundred tools. Most clients work best with fewer in context, and a script that only reads analytics has no reason to see the tools that spend money. Three switches on the endpoint URL shape a connection: two cut the surface, one caps spend.

- `?toolsets=videos,avatars` loads only the named toolsets. The names: `videos` (create, generate, replicate, drafts, scheduling, campaigns, the avatar wizard, and `list_avatars`, because every project starts from an avatar id), `editing` (the editor: concepts, takes, renders, text hooks, captions, model switches), `avatars` (appearance, voices, scene libraries, image editing), `accounts` (the account fleet, seats and billing, profile edits, warming, health, individual scheduled posts), `ideas` (the ideas library and tracked inspiration accounts), `discover` (the viral corpus, niche feed, trends, the source picker), `content` (script batches, the review desk, knowledge base, writer engine, learning loop), `automation` (campaign strategy and content plans), `workspace` (settings, members, onboarding, billing and credits, the media library, API keys, help, support).
- `?readonly=true` loads only read-only tools. A tool outside the connection's scope is unknown to it, so this is a guard, not a hint: a read-only connection cannot be talked into a write.
- `?max_credits=500` refuses any single credit-spending call whose up-front estimate is above five hundred credits, or that cannot be estimated, with `status: spend_limit_exceeded` and nothing charged. It is a gate on the estimate, not a meter on the charge: the estimate is what the tool would charge on its first attempt, and a tool's own retries and model fallbacks follow its usual accounting once it runs. The in-app assistant's per-turn spend tripwire does not apply over MCP, so an autonomous client without a ceiling has only the workspace's balance as a brake.

Combine them: `https://api.pamba.app/mcp?toolsets=accounts,videos&readonly=true`. A toolset name the server does not know is refused at connection time with the list of known names, and so is a `toolsets` value that names nothing, so a typo or an empty computed list shows up in your client's connection error rather than as a silently widened surface.

Every tool is annotated. `readOnlyHint` is true on reads; everything else carries `destructiveHint`, which is what clients such as Claude Code use to decide when to ask you before a call. The description of every tool that spends credits ends with its cost basis.

## What else the client receives

- **Instructions.** The initialize response carries the transport rules below, the connected workspace's name, niche and website, and the assistant's full product map. Clients inject this into the model's context, which is why the model knows the order of operations without you explaining it.
- **Prompts.** Four golden-path workflows, listed as prompts (Claude Code shows them as slash commands): `first_video`, `replicate_tiktok`, `new_avatar`, `performance_review`. Each is the tool chain the assistant runs for that goal, with the waits and the spend points named. A connection only lists the prompts whose every tool is inside its scope, so a read-only or narrowed connection is not offered a workflow it cannot run.
- **Resources.** Every help-center article, including this one and the tool reference, as `pamba://help/<slug>` in markdown. The model can read them on demand, and clients that let you attach resources let you drop an article into the conversation.

## How a call behaves

- **Nothing waits for your approval on Pamba's side.** In the app, the assistant parks writes and credit spends on a confirmation card, and a turn that accumulates about eight hundred credits of estimated spend parks too. Over MCP there is no card and no per-turn tripwire: a write or credit-spending tool runs the moment the client sends it. Your client's own permission prompt is the safety net, so keep it on for anything that is not read-only, connect with `readonly=true` for exploration, and set `max_credits` on a connection an autonomous agent drives. Two server-side refusals remain: affordability, where a spend the workspace cannot cover is refused before anything runs with `status: insufficient_credits`, the credits required and the credits remaining; and the connection's ceiling, where a call estimated above `max_credits` is refused with `status: spend_limit_exceeded`. Neither charges anything.
- **Results are JSON objects, in both the text content and `structuredContent`.** A `status` field carries the outcome. Success reads `ok`, `created`, `updated`, `saved`, `deleted`, `scheduled` and similar. `not_found` means no such object in this workspace. `invalid_id` means an id was not a UUID from the matching list tool. Other `invalid_*` statuses name a rejected field, and `message` says why. `insufficient_credits`, `payment_required` (a paid plan is required), `forbidden` (the caller's workspace role is too low, for example a member trying an admins-only setting, or an operator-only tool), `busy`, `queue_full`, `work_in_flight` and `automation_running` (work in flight, wait for it), `rate_limited` and `free_tier_exhausted` (a usage limit, with `retry_at`; see Usage limits below) and `error` (the operation failed unexpectedly) are refusals. `message` is written to be relayed to a person. A `next` field, when present, names the follow-up call.
- **Refusals are results, not protocol errors.** Only `status: error` sets MCP's `isError` flag. A JSON-RPC error comes back for protocol problems only: an unknown tool or one outside the scope, arguments that do not fit the input schema, an unknown method, malformed JSON.
- **Never retry a failed or lost call blindly.** A tool may have completed part of its work (a charge, a scheduled row) before it failed, which is why `status: error` carries a generic line rather than the internal cause. A call your client abandons or times out is not cancelled either: it runs to completion on the server and its answer is simply lost. The assistant is told to report and stop; give your own automation the same rule, read the matching status tool after a lost answer, and retry only once the cause named in `message` is fixed.
- **Arguments.** Omit an optional argument to leave it unset; do not send an empty string in its place unless the tool says an empty string clears the field (the company profile fields do). Every argument is checked against the tool's input schema before anything runs, and every refusal names the argument: a name the tool does not declare is refused with the accepted names, a missing or null required argument is refused, a whole-number parameter takes a JSON integer or an integral numeric string and refuses a fraction, a boolean takes true or false, a string must be a JSON string, and a parameter with listed choices refuses a value outside them. A typo therefore never runs the tool with a default in place of your value. Ids are UUIDs; take them from the matching list tool and pass them back verbatim. An avatar is addressed by its avatar project id: `list_avatars` returns both `id` and `avatar_project_id`, and almost every other tool wants the latter. Dates are `YYYY-MM-DD`. Scheduling tools take a precise ISO-8601 time in UTC (`2026-12-25T14:00:00Z`) or a date-only value that auto-picks a time in the posting window. The workspace timezone (`update_workspace_settings`) is the clock for posting windows and campaign schedules.
- **Lists are truncated, not paged.** Most list tools return the full `count` alongside a fixed-size slice and no cursor: `list_projects` forty (archived excluded unless `include_archived=true`), `list_scheduled_posts` and `list_accounts` two hundred, `list_workspaces` twenty, `list_scene_frames` one hundred, `list_video_generations` forty clips with ten takes each, or twenty-five takes when asked for one clip, `list_creative_concepts` twenty versions. Narrow with the tool's filters rather than expecting a next page. The exceptions that do page or take a limit: `list_ideas` and `search_tracked_videos` (`page`, twenty-five per page), the Discover tools (`page`, twenty per page), `browse_picker_videos` (`page`, twenty-four per page), `list_assets` (`limit` and `offset`), `get_studio_overview` (`limit` up to two hundred).
- **Result size.** `get_project` returns the complete editor state and `get_content_batch` the whole batch. Read the narrower tools (`list_projects`, `get_render_job`, `list_video_generations`) when you only need a status.
- **URLs you pass in are fetched from Pamba's servers.** Public `http(s)` only; anything resolving to a private or loopback address is refused, and redirects are re-checked.
- **Conversation id.** `get_conversation_info` returns `mcp` as the conversation id because there is no chat transcript over this transport. The workspace id it returns is what support needs.

## Waiting for work

Most tools that produce media are asynchronous: the tool returns at once with a status, and the work finishes in the background over minutes. Poll the matching read tool about once a minute, and do not re-trigger the same work while a status reads pending, queued, generating, building, rendering or applying; a second call either reuses the live job or is refused.

- **Clips and videos:** `get_project` and `list_video_generations`. A failed clip generation retries itself up to three times, including an AI reword of the script after a content refusal, and falls back to the more permissive Grok Imagine model when the chosen model's safety filter still blocks it (the clip's `video_model_fallback_from` records the blocked model, and the clip's model preference then follows the fallback). So a clip can sit in generating for several minutes before it completes or fails for good. A project stays "generating" through the editing pass, captions, render and text hook, not just until the first clip lands.
- **Drafts and replications:** `get_replication_status` (build step, failure kind, whether a retry applies). A review-queue item with `building: true` is a draft still writing its concept, frames and script; do not generate or edit it until that clears.
- **Renders:** `get_render_job`, and `render_state` on the project (`none`, `current`, `dirty`, `rendering`, `failed`). Renders are never cancelled: an in-flight one settles and the next render supersedes stale output.
- **Avatars:** `get_avatar_wizard` for the wizard, `get_avatar_generation` and `list_avatar_generations` for redesigns, voices and scene jobs.
- **Accounts:** `list_accounts` shows a purchased seat's setup progress until the account is ready and `get_seat_billing` each seat's state; `list_account_profile_edits` tracks profile edits and `get_account_link_status` a link. These run on Pamba-managed phones; a profile edit takes about two minutes and a creation longer.
- **Onboarding jobs:** `get_onboarding_state` (`media_capture_status`, the presenter match, the avatar job).

A few tools block instead, for up to minutes, and return the finished result: `create_avatar`, `update_wizard_appearance`, `generate_wizard_scenes`, `generate_avatar_voices`, `redesign_avatar`, `render_project`, and `update_account_profile` when it edits a TikTok profile. Give them a generous client timeout.

## Credits, plans and money

- Metered operations consume credits: video generation per second at the clip model's rate (roughly sixteen credits a second on Grok Imagine, twenty-two on Gemini Omni, fifty-three on Seedance, rounded up per clip; `get_credit_pricing` is the live list), avatar photos, scenes and variations at fifteen credits an image, replication frames at about eighteen (generation plus a realism pass), premium voice clones at one thousand five hundred, clip transcription for captions at about two credits per minute of video (repeat calls served from cache), content-engine script runs, and Ideas script generation by tokens. Basic voices are free. `get_credit_balance` is the balance, and reads `unlimited` rather than a number for internal accounts.
- Charging is reserve-then-charge. Credits are reserved when work starts, the charge commits only on success, and a failed generation is never charged. A reservation holds for up to two hours and reduces the reported balance meanwhile, so a balance can look low while work is in flight.
- The affordability refusal is skipped for a project that already has live video work, so a repeat "generate it" reads the tool's own in-flight answer instead of a false shortfall.
- A new workspace starts with a signup grant of three hundred credits and its first avatar is free (photo, scenes, basic voice). Three hundred is below a typical first video (a twenty-five to thirty-five second concept runs about four to five hundred credits), so a first `generate_video` without a plan or top-up usually answers `insufficient_credits`. A workspace you add with `create_workspace` starts at zero.
- Top-ups come in units of one thousand credits, one to twenty units per purchase, and are available to subscribed workspaces only; `create_topup_checkout_session` returns the Stripe link. Plans: `get_credit_plans` (yearly plans grant twelve times the monthly credits up front), then `create_checkout_session` with the plan's `price_id`; for a workspace that already subscribes, the link is Stripe's confirm page showing the prorated amount, and confirming changes the plan in place. Downgrading a yearly plan is refused and goes through support. `create_billing_portal_session` opens subscription management; a cancellation there takes effect at the period end, and access lasts until then. Every one of these returns a URL a person completes in a browser; the purchase lands on the workspace when the payment settles, so re-read `get_credit_balance` or `get_seat_billing` afterwards.
- Purchases need the caller to be a billing admin of the workspace; other members get `forbidden`.
- A paid-plan gate returns `payment_required`: inspiration accounts, ideas and Discover details require an active subscription (a credit plan or account seats), and enabling or firing a campaign requires an active subscription on the workspace owner. Discover shows free workspaces the first rows and blurs the rest; `create_video_project` with an `idea_description` has no such gate.
- Booking a call with the Pamba team at pamba.app/book-a-call earns twenty dollars of credits, added by the team after the call.
- Usage limits are separate from credits: daily ceilings on the operations that cost Pamba compute without charging credits, set far above real use. A workspace with no plan and no account seats may start 100 renders a day (5 running at once), upload 50 videos a day and run 200 AI assists a day (`edit_video`, `get_text_hook_placement`, `reposition_text_hook`, `resolve_bullet_texts`, `reposition_bullets`, `generate_project_description`, `suggest_account_usernames`, website analysis, and the title a concept save writes); a workspace with a plan or account seats may start 1,000 renders a day (25 at once), upload 500 videos and run 2,000 AI assists. Limits reset at midnight UTC. A refused call answers `status: rate_limited` with `retry_at`, or `status: free_tier_exhausted` when the free tier's shared daily budget is spent (paid workspaces are never affected). Wait for `retry_at` instead of retrying; a `start_render` refused this way has already saved the editor state you passed. Two assists degrade instead of refusing, because the request they ride on must still succeed: past the cap a concept save keeps its title, and a website analysis reports `failed` with no profile.

## What needs a browser or a phone

- **Checkout.** Every purchase is a Stripe URL the person opens.
- **Files.** Tools take public `http(s)` URLs, never an upload body. `upload_asset` stores a URL in the media library (png, jpeg, webp, gif, mp4, quicktime, webm, mp3); avatar and scene tools take image URLs. The library holds up to thirty image and video files (audio does not count) and answers `library_full` until one is deleted. Asset URLs the tools return are signed and expire within twelve hours: never store one in editor state.
- **Visual judgment.** Watching generated footage and comparing scene images is the person's call. Hand over the URLs the tools return.
- **Accounts.** TikTok, Instagram and Facebook accounts are created, edited and posted from on Pamba-managed iPhones by background device jobs. The client never touches a phone; it schedules the job and polls.

## Ordering and nuance, by area

These are the rules the assistant follows and the ones people most often get wrong when driving the tools directly.

### Projects and generation

- Two ways to start a project with an avatar: `create_video_project` with `script` (spoken verbatim) or with `idea_description` (a fresh script is written on every call, so fanning one idea across avatars gives each a distinct script; never copy one generated script to several avatars). Passing both or neither is `invalid_request`; a script run that fails is `generation_failed`, and rephrasing the idea usually fixes it. `promotional=false` writes a non-promotional script with no brand weaving. Creation costs nothing; `generate_video` is the spend.
- The project returns before its concept exists. A background job writes the script split, the default scene and the title within a minute or so; while it runs, review-queue items show `building: true` and `get_project` reports the concept as `none`. A failed build marks the project failed rather than shipping an empty draft.
- The clip split is automatic, on natural speech boundaries, with durations the project's video model allows. Non-automated creations also pick a default scene automatically from the avatar's labeled library scenes, at random among those that fit the script, so two creates from the same script can differ; when nothing fits, a random face-visible library scene stands in. Override frames only when a specific look is wanted. `preview_script_clips` shows a split without saving anything.
- Authoring and generating are separate steps. Changing frames, scenes, script, concept or timeline is complete when saved; rendering or generating is a further, explicit request. The assistant is told never to queue a spend to "finish" an edit, and a script that drives the tools should not either.
- A project can exist without an avatar: `create_draft_project` with no avatar plus `set_edited_video` turns a ready-made video into a postable project. The avatar stays unassigned forever; it cannot be added later.
- Video models: `grok-imagine-1.5`, `seedance`, `gemini-omni`. The default resolves in this order: the project's explicit model, the campaign's per-member model, the avatar's `default_video_model`, the workspace routing rules, then Gemini Omni. `set_video_model` switches drafts and projects in bulk and re-splits the stored script under the new model's limits (skipped when every duration is already legal, because a re-split mints new clip rows). Gemini Omni authors clips of four to ten seconds, the extended models four to fifteen; every model shares the four-second floor, and a concept save with a clip outside its model's range is refused whole.
- `get_project` uses sentinel strings: `concept` and `editor_state_json` read `none` until they exist, `final_video_duration_seconds` reads `unknown` until the file is probed, and `generated_at` stays empty until every clip has a generated video. Prefer the measured duration over summing clip durations, which are the plan. `promotional` reads `true`, `false` or `not_applicable` (verbatim scripts decide nothing).
- `update_project` changes only the fields passed: name, notes, `user_review_state` (`not_reviewed` or `approved`), `posted_state`, `user_visibility`. Archiving also cancels the project's pending posts. `list_projects` with `posted_only` means the project actually went live, which `posted_state` alone does not tell you (a cross-post downgrades a live project to `scheduled`, and cancelling a pending post resets it to `approved`).
- Approving or scheduling is refused with a conflict while the video is still generating, with one exception: clips in their final 60fps upscaling pass (the take is already playable: `active_generation_status` is null while `preview_video_url` is set) do not block; approve or schedule, and the automatic render waits for the upscaled files before anything posts.
- `generate_clips` takes optional zero-based `clip_indices`; on a clip in that upscaling pass it replaces the take: a fresh take starts, the superseded pass finishes and parks in the clip's take history (billed like any completed take, revertable with `select_clip_video`). A clip still producing its take keeps that generation. `keep_raw_voice=true` keeps the model's own audio and skips the avatar voice dub and the speech trim.
- Refusals from the clip-starting tools: `insufficient_credits`, `automation_active` (a pipeline owns the project), `queue_full` (try again in a few minutes), `work_in_flight` (a draft still building, or clips, an edit or a render from a previous generate still running). A pipeline stamp older than two hours is treated as dead, so a crashed run cannot lock a project out forever.
- `duplicate_project` clones a shell (concept and frames, no script, clips or videos); `duplicate_project_full` clones an exact independent copy, nothing re-billed. Neither carries scheduled posts, approval state or in-flight generations.

### Editing

- `edit_video` is the entry point for timeline edits: pass the intent in natural language and a dedicated editing agent, which sees the full timeline, the concept and the media library, applies validated operations and reports `applied` or `no_changes` with what it rejected and why. `edit_clip_videos` is different: it changes a clip's pixels via video-to-video on Gemini Omni whatever model made the clip, on clips that already have a video and are three to ten seconds long; for a longer clip, pass `in_point` and `out_point` to edit a window, one clip at a time, billed for the window at the Omni rate. Each edit lands as a new take, auto-selected, with the prior take still available. A failed edit leaves the working take untouched and reports on `latest_edit_status`.
- `set_editor_state` replaces the whole editor state object. Always read it from `get_project`, modify, and write it back; a partial object deletes what it omits. When changing one field of an existing item, copy the item verbatim: an item with the same id inherits its earlier placement and style for fields you leave null, but explicit values always win.
- Editor item shapes: text items place with `x`, `y`, `w` normalized zero to one (`x` 0.5 centered, `y` the vertical center: a top hook sits near 0.12 to 0.2, a bottom one near 0.8), and `style.font_size` is pixels on a 1080 by 1920 reference (default sixty-four, readable minimum twenty-four). Always set `x` and `y` or the text renders center-frame. Text wraps only at its own line breaks unless `text_wrap` is true, which makes `w` the box width. Prefer `style_preset` ids from `list_text_style_presets` over hand-built styles. Media items need a durable public URL (a signed asset URL expires; re-host through the project first) and take `w` as a width fraction (1.0 fullscreen, about 0.6 for a picture-in-picture, about 0.4 for a logo flash); video items also take `speed` 0.5 to 2 and `volume`, which you set to zero so a demo clip never talks over the presenter.
- Clip ids are re-minted on every concept save. An id held across a save answers "Clips not found in the selected concept"; re-read the project. Changing a clip's starting frame also drops that clip's generated video, which comes back only by reverting to the prior concept version (`list_creative_concepts`, then `revert_creative_concept`, which restores script, clips and editor state together and is refused while automation runs).
- `select_clip_video` needs a completed take of the same project; re-render afterwards to bake the change in. A main-track entry with `use_original_voice: true` keeps the clip's pre-dub audio in renders, and the flag is dropped when the clip re-points to a new generation.
- Captions come from `transcribe_clips`. Word timings arrive in spoken order and must not be re-sorted.
- Renders are automatic when saved edits are newer than the last render: approving, scheduling and leaving the editor re-render, scheduled posts wait briefly for an in-flight render rather than posting stale video, and a sweeper renders recently active projects once they have been quiet a few minutes. `render_state: failed` means the last attempt for the saved edits failed and only the next edit or commit point retries it. A video supplied directly is recorded as the current render and is never re-rendered over: `set_edited_video` copies whatever file it is given, and a `video_url` on `update_scheduled_post`, `schedule_instagram_post` or the REST schedule route counts only when it is a file from outside the project's own outputs. A url the project itself produced (its `edited_video_url` or `stitched_video_url`, an earlier render, a file it was given before) is ignored on those post routes, and the pending post keeps following the project's latest render.
- `set_clip_voice_effects` adjusts the acoustic environment baked into generated clips' audio: free, seconds per clip, frames untouched.

### Replication and drafts

- `replicate_project` takes exactly one source: a `tiktok_url`, a `source_video_url` from the workspace, or a `source_project_id` of one of the workspace's own projects (its rendered video is the reference, so it needs a finished video). `draft_only=true` builds a reviewable draft and stops before clip generation, so video credits are spent only when the draft is generated. `max_duration_seconds` is a target, not a cap: it shortens or lengthens the replica, and every replica is capped at one hundred sixty seconds whichever model generates it. `variation_instructions` cap at four thousand characters. A workspace runs ten replication jobs at once; requests past that queue automatically.
- Replicating from a TikTok URL also captures the source into the ideas library, deduplicated on the video URL. Replication plans media from the workspace asset library on its own: an app demo the original cut to becomes a media scene, partial cards and logo flashes become timed overlays on a Media track. A non-promotional replica does not use the brand media library.
- A hub video judged hard to replicate returns `low_feasibility_warning` until the call repeats with `acknowledge_low_feasibility=true`. A video that needs the brand's own digital files returns `missing_brand_assets` naming what to upload (`upload_asset`) rather than starting; the same acknowledgement proceeds anyway and the replica drops those beats. Physical products are never a requirement.
- `get_replication_status` reports `build_step` (`source`, `analyze`, `concept`, `scenes`, `frames`, `assemble`, `done`) and, on failure, `failure_kind` (`transient`, `timeout`, `safety`, `quality`, `content`, `unknown`), `retriable` and a `next` hint. `retry_draft_build` resumes from the last completed checkpoint, so finished stages are never re-paid; prefer it for transient and timeout failures and `request_draft_edits` for safety or content failures, because a blocked look keeps getting blocked. A failed request-edits revision restores the draft intact and reports `failed_revision` with the preserved feedback.
- An ungenerated replication draft's editor is read-only: direct `set_editor_state` is refused. Its edit surface is the draft itself, through the edit ladder: words only, on one scene, `edit_scene_script` (free); the whole read, `edit_draft_script`; one scene should look or play differently, `edit_scene` (image credits); the whole video's look, `regenerate_look_canon`; restructure, duration, add or remove scenes, `request_draft_edits`, which `edit_scene` refuses and which cannot serve script drafts. Scene edits of different scenes run in parallel; same-scene and whole-script edits serialize. Every completed `edit_scene` is undoable with `revert_scene_edit`, per scene, free.
- A draft can complete with some scene frames failed; `regenerate_scene_frame` retries them, and `generate_replication_draft` is gated until every scene has a frame.
- `generate_replication_draft` approves the reviewed draft into clip generation plus the final editing pass and render, honoring editor changes present on the draft as deliberate overrides. `generate_video` on a draft instead runs the standard automation pipeline and does not carry draft edits as overrides.

### Scheduling and posting

- `schedule_post` posts a project's video to TikTok (`tiktok_username`), to the avatar's Instagram (`also_post_to_instagram`), or both in one atomic schedule: if either platform has no slot (device busy, no slot left that day), nothing is scheduled and the whole request is rejected. `schedule_instagram_post` schedules a standalone Reel, and its `share_to_facebook` is the only Facebook path. `update_scheduled_post`, `cancel_scheduled_post` and `retry_scheduled_post` manage the queue; `list_scheduled_posts` and `get_scheduled_post` read it.
- A project with an avatar may only post to a TikTok account linked to that avatar; check `get_avatar`. A project with no avatar can post to any account on the workspace's fleet, linked or unassigned; posting to an avatar's account attributes the analytics to that avatar, posting to an unassigned account attributes them to none.
- **Posting a video you already have** (nothing generated, rendered or credit-metered): `create_draft_project` makes an empty project with no avatar, so it can post to any account on the fleet; `set_edited_video` attaches the file from a publicly reachable video url and records it as the project's completed render; then `schedule_post` or `schedule_instagram_post` posts it. There is no upload body over MCP: a file on the client's machine is staged with the REST upload (`POST /uploads/videos`, multipart, the same API key) and its returned url passed to `set_edited_video`. To attribute an uploaded video's analytics to an avatar, create the project in the app's Upload button or with the REST create call and its avatar field, then attach and schedule from here; the avatar choice is permanent. `set_edited_video` is refused while automation runs on the project or a replication draft is locked, and a scheduled post for the project picks up the new video.
- What `schedule_post` posts depends on the project: a project with a concept whose clips have no video yet is generated and rendered first (the result says `mode: generate_then_post`, credits are spent); a project whose video was supplied (`set_edited_video`, the app's Upload button) posts that video as is (`mode: post_project_video`, nothing generated); a project with neither a video nor a concept is refused as `nothing_to_post`. The REST `automate` flag follows the same rule: it is 400 on a project with no concept to generate from. `generate_video` follows it too: a project whose video was supplied, or one with no concept at all, is refused as `nothing_to_generate` instead of starting a pipeline that has nothing to build.
- A post goes out only if its project is approved at its slot. `schedule_post` says where that stands in its result's `approval` field: `approved_by_schedule` when it posts a video that already exists (the schedule approves the project, nothing more is owed), and `required_before_slot` in `generate_then_post` mode, where the schedule does not approve a video that does not exist yet and someone must approve the rendered video before the slot. `update_project` with `user_review_state=approved` approves, with or without a schedule; approving is refused while the video is still generating. A post that failed at the gate is recovered by approving the project and then calling `retry_scheduled_post`. Automated pipelines (campaigns, content pushes) generate, render and schedule but never approve on their own, so a scheduled post whose project nobody approved fails at its slot with the message "Project not approved at scheduled time". That is the review step working, not an outage.
- Captions: `generate_project_description` writes a hook line plus exactly five hashtags from the script and company profile, but only returns the text; pass it as the caption yourself. TikTok captions may not contain the commercial-disclosure hashtags `#ad`, `#ads`, `#sponsored`, `#partnership` or `#paidpartnership`: TikTok's own disclosure gate blocks the post, so the caption validator refuses them up front.
- When the avatar has no account on a platform yet, `schedule_post` with `await_new_account=true` (TikTok) or `await_new_instagram_account=true`, or `schedule_instagram_post` with `await_new_account=true`, persists the post as waiting; `purchase_account_seats` with `link_avatar_project_id` then buys the account, and once it is created the waiting post books a real slot and goes out with no further action, independently per platform.
- If a schedule is rejected, read the reason and offer the valid accounts or another time; do not retry blind.

### Avatars and voices

- The wizard, in order: `create_avatar` (blocks until the candidate scenes exist and charges seventy-five credits: one appearance plus four scenes), then any of `discard_wizard_scene`, `generate_wizard_scenes` (fifteen credits a scene) and `update_wizard_appearance` (regenerates the look) before `accept_avatar_appearance` locks the appearance, then `select_avatar_voice`, which finishes the wizard. `get_avatar_wizard` polls and already lists the free basic `voice_options`; `reroll_preset_voices` swaps in three other free picks (`direction` accepts only `deeper` or `higher`). `generate_avatar_voices` (premium clones, one to three, one thousand five hundred credits each) only when a premium voice is wanted. Completion auto-starts pose and angle variations for every kept scene, free on the workspace's first avatar and charged per image after, so do not also call `generate_scene_variations` on a fresh avatar. The first kept scene becomes the profile image.
- Voices come in exactly two kinds: free basic voices (called "preset" in tool names for historical reasons) and paid premium clones (`generate_premium_voice`, cloned from a generated speaking clip on Seedance by default). `get_preset_voices` then `apply_preset_voice` changes an existing avatar's basic voice; browsing and rerolling is free.
- `update_avatar_settings` replaces whole sets: `tiktok_usernames`, `tags` and `warming_search_terms` are overwritten with what you pass (an empty list clears, and warming then falls back to niche terms), and the TikTok accounts must already exist on a workspace device. `default_video_model` takes `default` to clear back to the workspace routing rules.
- Scene library: every scene and library frame carries vision-derived `content_labels` from a fixed vocabulary plus a `scene_description`; match those against a clip's needs rather than picking by name. `generate_scene_frames` (one to ten, default three) lands frames inactive; activating one with `update_scene_frame` auto-generates six pose variations (ninety credits) when it has none, and `upload_scene_frame` is stored active and does the same. `delete_scene_frames` also deletes a scene's variations, and the profile frame cannot be deleted; set another first. Replication-generated scenes stay out of the avatar's active library on purpose.
- `edit_image` edits any Pamba-hosted image with a text instruction (fifteen credits) and returns a new URL; on a registered frame the result is itself usable as a starting frame. `save_scene_variation` keeps that result as a variation (original untouched), `replace_scene_image` points the frame at it instead, losing the original. `promote_scene_variation` swaps a variation with the scene's reference, so promoting it back undoes the swap.
- Camera styles are `selfie`, `tripod`, `tripod_full_body_visible` and `podcast` on the avatar tools; `generate_starting_frames` spells the full-body style `tripod_full_body`, and the other spelling falls through to selfie there.
- `import_avatar` creates an avatar from a photo URL with `gender` `MALE` or `FEMALE` and a free basic voice; `redesign_avatar` applies free-form appearance or voice changes and blocks until they land. `track_external_tiktok_account` attaches an account you post to from your own phone, for analytics only: posting and warming stay unavailable for it.
- Operator-only tools exist on the surface because the in-app assistant has them; for a customer workspace they answer `forbidden`: the reference-frame tools, `create_social_account`, `get_account_creation_status`, `resume_account_creation`, `generate_image`, the writer-engine controls (`run_content_engine`, `control_stage_run`, `update_engine_prompt`, `set_autonomy_dial`, `set_batch_share_page`), and the engine's spend bookkeeping (`log_llm_usage`, and `get_llm_usage_summary`, which reports the model spend at Pamba's cost).

### Accounts, seats and devices

- `list_accounts` is the inventory of every TikTok, Instagram and Facebook account on the workspace's fleet, including accounts assigned to no avatar (their avatar fields are null). Status is `posting`, `track_only` or `archived`; health flags include `SHADOWBAN_RISK` (after the account's first three weeks of tracking), `NOT_RECOMMENDED` and `SUSPENDED`.
- Managed account seats cost one hundred dollars a month each, every two include a dedicated phone, and every TikTok plus Instagram pair across the subscription prices its second account at fifty dollars, re-priced when seats are added or cancelled. Any quantity up to a thousand accounts per checkout can be bought; the seats the phones cannot back yet stay being set up until the team adds phones. Facebook has no seat of its own; it rides Instagram cross-posting. `purchase_account_seats` returns a checkout URL; an already subscribed workspace gets Stripe's confirm page raising the quantity with proration.
- A purchase never adopts a spare account already sitting on the workspace's phones. Check for spares first: an unassigned spare links to an avatar free with `reassign_account` and no `from_avatar_project_id` (TikTok or Instagram; no password, no phone work). The same tool moves an assigned account when `from_avatar_project_id` names its current avatar; without it an assigned account is never moved. `unlink_account` is the reverse: it drops the binding and the account is a spare again, still signed in on its phone, with its status and history.
- A bought seat waits for its username before creation starts. `suggest_account_usernames` returns a checked, currently free idea per waiting seat, `check_account_username` verifies a pick (availability is a hint, not a reservation), `set_account_username` saves it and starts that seat's creation, or `use_default_name=true` creates with a platform default. Instagram gets the typed name at signup; TikTok assigns its own handle and applies the pick as a rename right after, which spends TikTok's one username change per thirty days. A name that fails when applied falls back to close variations, then keeps the platform handle. Progress shows per account on `list_accounts` until the account is ready.
- `cancel_account_seat` keeps the account posting until the paid period ends, then stops posting and billing, no refund; `reinstate_account_seat` undoes it before that date at no cost. `get_seat_billing` shows the next renewal's total per seat. `recreate_social_account` replaces a seat-funded account with a new one on the same phone and seat (once every 30 days per seat, admins only; the old account's videos are not deleted and Pamba keeps its credentials in case they are ever needed); `get_account_recreation` says whether an account can be recreated now and how a recreation is going.
- **A workspace that only buys accounts and posts its own videos** runs this order, and nothing in it spends credits: `list_accounts` to rule out a spare, `purchase_account_seats` (pass `link_avatar_project_id` when the account is for an avatar) and the person pays at the checkout URL, `suggest_account_usernames` then `set_account_username` per seat (creation starts only once a seat has a name), `list_accounts` until the account reads `posting`, then `create_draft_project`, `set_edited_video` and `schedule_post` with the new username (see Scheduling and posting). To queue the post before the account exists, the project needs the avatar the purchase links to, and `schedule_post` takes `await_new_account=true` in place of a username.
- `update_account_profile` edits display name, handle, picture and bio on the hosting phone. Bios are capped at one hundred sixty characters on TikTok and one hundred fifty on Instagram; a URL typed into a bio is plain text, the link field is separate and untouched. `list_accounts` reports each account's current bio, whichever is newer of the one Pamba last set and the one last read off the phone; every linked account's bio is read once a day, so an in-app change shows within a day. Only the fields an edit asked for count as outcomes: a bio-only edit reports one operation, not three.
- Pamba-created TikTok accounts block AI comments automatically once linked to an avatar: an account linked at creation gets the filter after its first post is live, one linked later gets it queued at link time, and an unassigned account is never filtered. `disable_ai_comments` turns it off per account. `get_avatar` reports the state per account as `applied`, `queued`, `scheduled`, `failed` or `off`, and null when the account is not on one of the workspace's phones.
- `enable_instagram_ai_creator` is enable-only and idempotent: once on, the label locks on, and disabling is rejected.
- Warming (human-like feed browsing) is always on for every device-hosted account and cannot be turned off from the tools. Exactly one config governs an account: an avatar-assigned account warms through its avatar's config (`get_warming_config`, terms via `update_avatar_settings`), an unassigned one through its own (`get_account_warming_config`, `set_account_warming_search_terms`); assigning an avatar moves ownership and cancels pending account-level sessions. Terms cap at twenty-five of eighty characters; without explicit terms, sessions fall back to terms generated from the workspace niche. `cancel_warming_session` works only on pending sessions. `get_account_activity` is one feed per account of what the phone did for it, newest first: warming sessions (status, length, videos watched, likes, comment reads, the search term, a few sample screenshot URLs), daily TikTok health checks, posts and profile edits; pass a page's `next_before` back as `before` to read further back, and an empty `next_before` ends the feed.
- Every device-held linked TikTok account gets one account-health check a day. Findings open incidents (`ACCOUNT_WARNING`, `VIDEO_REMOVED`, `VIDEO_NOT_RECOMMENDED`), shown as health flags and cards, emailed to the workspace and surfaced on Home; these kinds never pause the account. `get_tiktok_account_health` returns the latest screenshots and open issues; `request_tiktok_appeal` records the decision to appeal a removed or restricted video, after which the Pamba team files it from the phone by hand, one appeal per issue. Outcomes are inferred, never read from TikTok directly: a removed video reappearing closes its issue, warnings close after two clean daily checks. `close_account_incident` dismisses an issue for good.
- Accounts suspended by Meta open a `SUSPENDED` incident that pauses posting and warming and rejects scheduling until a session runs clean or the incident is closed with `close_account_incident`, only after the account is verified restored. `reconcile_tiktok_rename` repairs an account renamed on TikTok itself, moving the avatar's active account and all forward-looking drafts, plan and scheduling to the new handle; old posts stay under the old handle.

### Ideas, discover and research

- `list_ideas` and `get_idea` read the library (twenty-five per page, sortable by date, views and engagement ratios); every hub video carries a replication `feasibility_verdict` (`pass`, `fail`, `unjudged`) with the judge's reason, and `exclude_low_feasibility=true` hides failed ones while unjudged stay visible. `get_idea` lists `required_brand_assets` with `in_library` per kind, which tells you before replicating whether an upload is needed. A workspace holds up to one hundred ideas.
- Research needs an active subscription, a credit plan or account seats; free workspaces browse but every addition (`create_idea`, `branch_idea`, `add_tracked_accounts`, `generate_script_from_idea`) answers `payment_required`. Saving a video that is already analyzed is allowed on every plan. The inspiration-account cap scales with the plan tier. Tracking an account feeds its breakout videos into the create page's For You blend once the feasibility judge passes them; `remove_tracked_account` cannot remove an account linked to one of the workspace's avatars.
- `search_tracked_videos` searches the workspace's own avatar accounts by default; pass `include_research_accounts=true` for the inspiration accounts. Ground recommendations there rather than in memory. `generate_script_from_idea` writes a fresh thirty to forty-five second script and does not save it; `save_idea_script` or `create_video_project` does.
- Discover: `get_trending_videos` (by default only avatar-replicable content, screened for promotion carriage; `include_unproducible=true` reveals everything), `get_niche_feed` (returns `niche_status` `pending` or `no_niche` for an empty feed), `search_viral_videos` (paid only; falls through to a live TikTok search when the corpus is thin, with a daily budget reported as `live_searches_remaining_today`), `get_breakout_creators`. To act: `use_discover_video` puts a video on the Ideas board (idempotent), `bookmark_discover_video` saves it, `track_video_author` adds the creator as an inspiration account. `browse_picker_videos` is the create page's shelf; a row whose `video_url` is null is an un-enriched Discover row, so prepare it with `use_discover_video` first.

### Content review, knowledge and automation

- Campaigns are autonomous cycles that plan, write, generate and queue posts for their member avatar and account bindings on a schedule, gated by review windows unless auto-approve is on. Schedules run in the workspace timezone and plans are ready for review by nine in the morning local time by default. Every workspace has a Default campaign that owns unassigned members and cannot be deleted; `create_campaign` copies its schedule and starts disabled with no members; `assign_avatar_to_campaign` is a complete move. `enable_campaign` spends credits on every future cycle until `disable_campaign`, and both need an active subscription on the workspace owner. `run_campaign_now` plans the gap days up to the next scheduled cycle without stealing that cycle's window.
- `preview_content_plan` is a dry run that creates nothing; `plan_content_batch` plans (or re-plans an unapproved batch via `batch_id`); `get_content_plan` explains, per avatar, why each got its slots; `edit_plan_slots` moves, removes, swaps, adds or pauses. `approve_content_plan` or `approve_all_content_plans` is where credit spend starts, and approval is refused while the plan has error-severity violations. `set_mix_targets` replaces all targets, so read `get_content_strategy` first. `delete_plan` only removes a planning or brief-pending batch and never touches projects.
- `push_content_batch` is safe by default: without `push_now=true` or a `preflight_id` it only runs the preflight. With `push_now`, each pushed approved script becomes a fully automated project that generates and schedules, and spends video credits. `decide_content_script` with a `batch_id` and no `script_id` approves every undecided script; `require_green=true` skips scripts with failing checks. `send_writer_chat_message` answers asynchronously: read the thread with `get_content_script` (`view=writer_chat`), then `apply_script_revision`.
- Knowledge base: patterns (a pattern's category is its canonical format; `lipsync` patterns are classification-only, the planner never allocates them), principles, playbooks and term lists (tiered pools of real terms the writer draws from: `proven`, `testing` and `exploratory` tiers are writer-eligible, `candidate` and `risk_flagged` terms are not). The writer's references are chosen automatically at claim time from pattern links; curate them with `manage_pattern_link` and preview with `get_pattern_references`.
- `get_studio_overview` is the one read for the Drafts and Videos tabs. Always pass `statuses`: `drafts` is the Drafts population (concept-stage items awaiting generation), the others (`queue`, `failed`, `approved`, `scheduled`, `posted`, `archived`) are the Videos tab's filter, and posted history and archived projects are reachable only here. Omitting `statuses` returns the classic queue capped at fifty with drafts sorted below everything else, which hides them on a busy workspace. `sort` (`newest`, `oldest`, `posttime`) decides which items make the window and requires `statuses`; a typo in either errors rather than defaulting. `view=home` also returns the getting-live setup, where `ready_to_post` (not `account_connected`) says whether posting would actually work.

### Analytics

- `get_analytics_summary` (`days` one to three hundred sixty-five, default thirty; `platform` `tiktok`, `instagram` or `facebook`, default TikTok; optional `avatar`) gives views and followers per platform, broken down by up to twenty avatars. `search_tracked_videos` is per-video TikTok performance, `list_social_videos` per-video Instagram and Facebook performance, `get_tracked_account_metrics` an account's lifetime totals and cadence.
- The numbers are snapshots and the surfaces bucket differently, so do not expect them to reconcile to the view. The summary tool buckets days in UTC; the app's dashboard uses the workspace timezone and includes the current partial day. The dashboard sums daily deltas, while per-account metrics sum lifetime view counts. Views a video earns before its first snapshot (about an hour after posting, when TikTok front-loads) never land in a daily bucket. Accounts assigned to a deactivated avatar keep posting and accruing views but do not appear in avatar-scoped dashboards. A TikTok rename starts a new account row, and each era's videos stay on their era's row. Instagram's headline views are the play count the app shows, and the first observation of a reel attributes its whole lifetime to one day.

### Workspace, members, onboarding and support

- `update_workspace_settings` is admins only; setting `website_url` re-scrapes the site and replaces the stored company profile (and the niche, unless it was hand-written). `update_company_profile` edits the brief field by field and leaves the others alone; an empty string clears a field. Name, niche and profile feed the model's own context, from the next connection onward. `list_workspaces` returns at most twenty workspaces and `list_workspace_members` one hundred.
- `create_workspace` with `website_url` sets a workspace up like an onboarded one in one call (brief, name, media library, niche feed, three matched avatars; the site read takes about thirty seconds); pass `niche_description` and `name` instead when there is no site. A new workspace has its own plan and starts with zero credits, and the connection stays bound to the workspace of its key. If setup fails after creation the error carries the `workspace_id`: finish or delete it, never create a second. `delete_workspace` is a soft delete and cannot remove the user's default workspace.
- Onboarding state (`get_onboarding_state`) runs website, brand, media, first video, avatar, plan, device; the `light` flow of an added workspace ends after `set_onboarding_brand` and `complete_onboarding`. `set_onboarding_website` is best-effort (an unreachable site still records the URL and advances) and kicks a background media capture into the asset library (`media_capture_status`). `list_onboarding_presenter_options` returns an empty list while the niche match is still running, so poll again rather than treating it as no avatars. `select_onboarding_presenter` picks a matched starter avatar for free; `start_onboarding_avatar` generates one and spends image credits. `create_onboarding_concept_projects` is free and idempotent. The device half must be resolved (purchased or skipped) before `complete_onboarding`, which refuses otherwise. `set_onboarding_referral` accepts only its listed sources, with free text in `detail` under `other`. A user with no credits and no avatars right after signing up has a torn bootstrap; `heal_signup_bootstrap` re-runs the missing grants idempotently.
- `contact_support` emails the Pamba team and allows three messages per workspace and five per user in twenty-four hours, so send one complete message of up to five thousand characters. `list_api_keys`, `create_api_key` and `revoke_api_key` manage keys; a revoked key's MCP connection fails at its next call. `create_api_key` returns the new secret in its result, which over a hosted client lands in a transcript, so create the key for an MCP connection in the app and keep the tool for keys the agent itself will hand to a system.
- Content rules: Pamba's Acceptable Use Policy is the public page `https://pamba.app/acceptable-use-policy`; point people there for what is allowed, why a generation was refused by safety filters, and what happens to accounts that break the rules.

## Security model

- A key or token resolves to one user in one workspace, and every tool runs with that identity. Ownership checks are the same the app enforces: an id from another workspace answers `not_found`, never data. Admins-only settings answer `forbidden` for members. Operator endpoints behind Pamba's admin key are not reachable through this server at all; the admin key is not a credential here.
- Rotate a key by creating a new one and revoking the old; connections are stateless, so the next call after revocation is the one that fails.

## Protocol details

- Streamable HTTP, stateless: every POST is independent and carries its own credential, there is no session id, and no server-to-client stream (GET and DELETE answer 405). A deploy between two calls is invisible to the client.
- One JSON-RPC message per POST, at most one megabyte (enough for a full editor state; media never travels in a message) and at most sixty-four levels of nesting; a larger body is answered with HTTP 413 and a deeper one with HTTP 400, each with a JSON-RPC error. Batches are refused. Responses are `application/json`; the server never opens an event stream. Sixteen tool bodies run at once across every connection and four per workspace: a call past the workspace's share answers `status: busy` at once, and one past the overall bound waits up to thirty seconds for a slot before answering the same, in both cases with nothing run, so a saturated server never hangs a client and one workspace's fan-out never starves another's. Protocol versions `2025-06-18` and `2025-11-25` are accepted; anything else, including `2025-03-26` (whose transport requires batches), negotiates to `2025-06-18`.
- Capabilities: tools, prompts and resources, none with change notifications; the listing changes only with a deploy. Regenerate the reference in your own docs from `tools/list` if you mirror it.
- JSON-RPC errors: `-32700` malformed JSON, `-32600` invalid request or batch, `-32601` unknown method, `-32602` unknown tool, tool outside the scope, bad arguments, unknown prompt or a missing `uri`, `-32002` resource not found (the `data` names the uri), `-32603` internal error. Auth failures are HTTP 401 before any message is read; a bad `toolsets`, `readonly` or `max_credits` value on the URL is HTTP 400 with a JSON-RPC error body.

---
HTML version: https://pamba.app/docs/mcp-server · All docs: https://pamba.app/llms.txt
