Developers

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.

Connect from a chat app

Choose your chat app in the visual setup guide: Claude, ChatGPT, Grok or Gemini. Each walkthrough shows the provider’s menus, where to paste the URL, how to approve access on Pamba and a read-only prompt to try. Account requirements and links to the provider’s current instructions are included.

In a chat app that supports remote MCP with OAuth, add https://api.pamba.app/mcp as a custom connector. Pamba opens an approval page: sign in and choose one or more workspaces, or enter your workspace API key on that Pamba page. Review the requesting app's return address and approve. Never paste the key into a chat message.

Find the URL in Settings → Connected apps and choose Copy URL. The same setup instructions and URL are available on Connected apps, where you can also disconnect apps later.

If signing in takes you to Pamba's onboarding flow, finish that setup, then return to your chat app and connect Pamba again. The original connection request does not resume automatically. You need an existing workspace to approve access.

OAuth connections get every tool an API-key connection has: projects, avatars and generation, scheduling and publishing posts, campaigns, social accounts, workspace settings and members, billing checkouts and API keys. They act in the workspaces you approved. Tool calls run immediately; your chat app controls any per-action confirmation. URL filters can narrow a connection but cannot expand it.

Beyond the approved workspaces, the app can read the name and company details of every workspace you belong to, create workspaces, accept workspace invitations, list or revoke your API keys in any workspace, disconnect your other connected apps, and change your notification settings.

What the app sets up keeps running after you disconnect it or the connection ends: posts it schedules still publish, campaigns it turns on keep spending credits, and API keys, invite links and members it adds keep working until you turn them off or remove them in Pamba.

Each authorization lasts up to 90 days. Access tokens expire after one hour and the client refreshes them automatically; reconnect when the authorization expires. Disconnect anytime from Connected apps. Losing membership or deleting a workspace stops access to that workspace. Revoking a source API key stops the connection. Disconnecting stops the entire connection in every approved workspace, including queued calls that have not started; it does not cancel generation already running. Disconnecting an app does not revoke an existing API key.

One connection can cover up to 50 workspaces you explicitly select at approval. Ask your chat app to use a workspace by name. It calls list_connected_workspaces to find approved IDs, then passes workspace_id on each action. A multi-workspace grant requires that ID; the server never silently switches workspaces. Projects, avatars and credit charges stay within the selected workspace. New memberships and workspaces are not added automatically—reconnect to approve a different selection. Existing connections keep their original workspace. API-key approval remains limited to that key’s workspace.

Pamba staff accounts cannot connect a chat app; staff connect Claude Code or Codex with a workspace API key instead.

Availability and setup menus depend on the chat provider, account and administrator settings. A supported connector must be installed; an API key pasted into regular chat does not give the model an API tool. The API-key setup for Claude Code and Codex below continues to work.

Connect using an API key

  • •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. A key can make one for a workspace it created itself: create_workspace over the key, then create_api_key with key_workspace_id set to the new workspace's id, sets up a new workspace and gives its key without opening the app; it never makes keys for the person's other workspaces. 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. Deleting the person's own Pamba account is not available over MCP, to the in-app assistant or with an API key: only the person can delete it, in the app's Settings. 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, comments, individual scheduled posts), ideas (the ideas library and tracked inspiration accounts), discover (the viral corpus, niche feed, trends, the source picker), content (script batches and their drafts, 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, workspace selection rules (or, for API keys, 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. Three 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; the plan rule, where a workspace on its free signup credits alone is refused every video generation with status: video_generation_locked (see Credits below); and the connection's ceiling, where a call estimated above max_credits is refused with status: spend_limit_exceeded (a TikTok schedule_post that must generate its video first counts, priced like generate_video). None of them 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), video_generation_locked (the workspace is on its free signup credits alone, which never pay for video: a plan, seats or an admin grant unlocks it, and next names the checkout tools), forbidden (the caller's workspace role is too low, for example a member trying an admins-only setting), operator_only (an action limited to Pamba operator accounts), 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 (a parameter whose description says it carries JSON, a list of ids say, also takes the JSON list or object itself), 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), list_post_analytics (limit up to one hundred, thirty by default, with total the full count).
  • •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.
  • •Media URLs come in two kinds. A source_url on an ingest tool (upload_editor_media, set_edited_video, upload_scene_frame, upload_asset, import_avatar) is fetched from Pamba's servers: public http(s) only, anything resolving to a private or loopback address is refused, and redirects are re-checked. Every other media field (an editor item's src, a post's video_url, edit_image's image_url, a starting frame, update_account_profile's new_profile_picture_url) takes a file already on Pamba storage (https://storage.googleapis.com/pamba_videos/..., pamba_images/... or pamba_audio/..., with no query string) and refuses a link to another host with a message naming the fix. Slideshow pictures and sounds and a replication source_video_url also take a media-library link. So bring an outside file in with the ingest tool that fits and pass on the Pamba URL it returns: upload_editor_media for timeline media, set_edited_video for a project's final video (then schedule the project and leave video_url out), upload_scene_frame for a starting frame, upload_editor_media for a profile picture (a media-library link or another host is refused with the error code profile_picture_invalid). A media-library link from upload_asset is signed and expires: to put a library file on the timeline, pass its link to upload_editor_media and place the URL that comes back. Over REST, POST /uploads/videos and POST /projects/{projectId}/upload-editor-asset take the file itself and return its Pamba URL.
  • •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). An on-camera talking take (not an edit_clip_videos edit) whose speech plays over a closed mouth, or that does not say its line as written (a word that carries meaning missing, added or swapped, a phrase said twice, or the line cut off before its end), is generated again on the same model, at most twice for both together, and its clip reads pending again while it waits. So a clip can sit in pending or 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 an avatar's profile edits, get_account_profile_edit one edit by its job_id, get_account_link_status a link and get_account_import_status an account import (every few seconds while it may stop on a verification code). 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, update_account_profile when it edits a TikTok profile, and create_workspace with a website_url, or with niche_description and what_they_do (it reads the site, or writes the brief from what the company does, in the call). 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). A workspace on the grant alone cannot generate video: until it has a paid plan, account seats or an admin credit grant, generate_video, generate_replication_draft, a replicate_project without draft_only, a create_video_project with generate_video, and a schedule_post that would generate answer video_generation_locked (with next naming the checkout tools), whatever the balance. Analysis, drafts, scripts, photos, scenes and voices spend the grant as usual, and once video is unlocked the grant pays for it too. 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, and confirming changes the plan in place: a pricier plan starts a new billing period that day with its full credits, priced at the new plan's price less what the current plan's unspent credits cost (on a subscription that has a promotion code those credits stay in the balance instead), and a cheaper plan takes effect when the current period ends. No money is refunded. Asking for the plan the workspace is already on cancels a change scheduled for the period's end and is otherwise refused. Downgrading a yearly plan is refused and goes through support. The checkout page of a new plan takes a Stripe promotion code, which the person types there; top-up and seat checkouts take none. create_billing_portal_session opens subscription management (payment method, invoices, billing details, cancellation; plans change only through create_checkout_session); a cancellation there takes effect at the period end, and access lasts until then. When a renewal is declined, seat-funded accounts keep posting and the plan's monthly credits pause while the renewal is past due or unpaid (get_seat_billing says renewal_declined; for the plan, get_user_profile's subscription_status); update_payment_card returns a page for a new card, which Pamba puts on every subscription and charges the declined renewals to at once (a card changed in the billing portal does not reach subscriptions that carry their own card). A workspace's seats bill to one card unless the Pamba team split them across cards at the customer's request: then get_seat_billing lists cards (each card's seats, monthly total and renewal, all renewing on the same day), purchase_account_seats lists the cards a purchase can add to (pass subscription_id), and update_payment_card replaces one card only (pass the subscription_id of the card being replaced). 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, reposition_bullets, generate_project_description, suggest_account_usernames, website analysis, the title a concept save writes, the slide copy create_slideshow writes, the rewrite set_draft_scene makes, and the look description a product's photos write); 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 URLs, never an upload body: an ingest tool's source_url is any public http(s) URL, and every other media field takes a Pamba URL (see Media URLs above). 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. An account import is the exception that needs the person: the verification code TikTok or Instagram sends them, passed on within minutes.

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. A video is spoken in the language its script is written in: a verbatim script keeps its language, and an idea_description written in or naming a language gets a script written in it. Latin-alphabet languages work best. 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: set_draft_scene moves a built script or idea draft onto another scene of the avatar's library (every clip re-deals from that scene and its pose variations at once, and its place and video prompts are rewritten for the new scene in the background, free, until anything generates; a generate that arrives first applies the rewrite before it shoots). 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 (campaign videos only), then the platform default: Gemini Omni for campaign videos and for every video an MCP client or API key creates, while in the Pamba app itself a video created from a script or an idea, and a replication whose avatar speaks a line on camera, currently gets grok-imagine-1.5, and an experiment currently splits any other video, such as a replication with no line spoken on camera, between gemini-omni and grok-imagine-1.5. 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). Every model authors clips of four to fifteen seconds and 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 (and audio items) need a Pamba image, video or audio URL: bring an outside file in with upload_editor_media and place the URL it returns (a signed library asset URL is refused). They 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, which is English only, so the on-screen captions timed from the speech suit English videos. 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, an instagram_url (a reel; inspect_instagram_reel reads a pasted link first), 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.
  • •For source_video_url, use a Pamba-hosted video or a video asset owned by the selected workspace's media library. Upload an external source to that library first; arbitrary remote URLs and local files are refused before replication starts.
  • •Replicating from a TikTok URL also captures the source into the ideas library, deduplicated on the video URL; a replicated Instagram reel is not captured. 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. An Instagram Reel can go out as a trial (schedule_post's instagram_trial_reel, trial_reel on schedule_instagram_post and update_scheduled_post): release_to_followers_once_it_performs_well lets Instagram share it to everyone if it performs well in its first 72 hours, release_to_followers_never keeps it a trial that is never shared to the account's followers; leave it out for a regular reel, and update_scheduled_post's remove_trial_reel takes a trial off. Instagram shows a trial to non-followers first and keeps it off the profile; it never goes to Facebook (refused with share_to_facebook true), Instagram offers it only to professional accounts with at least 200 followers (an account under 200 is refused with instagram_trial_reel_ineligible: its last read follower count decides, or, for an account Pamba has not read yet, its profile read when the post is scheduled; list_posting_accounts and get_project name the accounts known to be under it in instagram_below_trial_floor; a personal account is refused the same way, and they name the ones Pamba knows in instagram_personal: switch one to creator with update_account_profile's instagram_account_type and the trial can be scheduled as soon as the switch is queued), and a slideshow's carousel has none. update_scheduled_post, cancel_scheduled_post and retry_scheduled_post manage the queue; list_scheduled_posts and get_scheduled_post read it. A completed post is the phone's word that it posted, and its verification_status says whether Pamba then found it on the account's public profile: pending while the checks run (20 minutes after posting, then an hour, another hour and a day later), verified once it matched (its link in posted_tiktok_video_url or posted_instagram_video_url; an Instagram slideshow post (a carousel, or a reel built from its slides), and an Instagram reel posted before 2026-10-03, read verified on the phone's word alone until the link is known), video_not_found when no new post appeared, frame_mismatch when a new post appeared that is not this one, error when the check could not finish; verification_message says it in one sentence. The check runs once, so verified says the post was found, not that it is still up. The app shows these as Posted, Verified (for an Instagram post, once its link is known), Not visible and Not matched. A failed post whose retry already failed again, in a way that alerted Pamba's engineers, reads being_fixed true: it is theirs to fix and retry, usually within a few hours, and retry_scheduled_post refuses it with status being_fixed (the REST route answers 409 post_being_fixed). A hold nobody works ends a day after the failure, and the retry is yours again. A failure you remedy yourself (another Instagram song or sound, the slideshow's missing images, the TikTok Shop link, approving the project, reconnecting a signed-out account you connected: account_needs_reconnect, new audio for a video TikTok would mute for copyrighted music: copyrighted_sound_muted, where a TikTok sound added on top keeps the video's own audio, a reel Instagram offered no trial: ig_trial_reel_unavailable, fixed by switching the account to creator with update_account_profile or removing the trial with update_scheduled_post's remove_trial_reel) stays yours to retry. A trial reel whose outcome was lost reads being_fixed true from its first failure, and that hold does not end after a day: it may be live and no lookup can see a trial, so Pamba's engineers check it on the phone and settle it, and retry_scheduled_post refuses it with status being_fixed. A post is never retried, by anyone, while another post of the same video or slideshow to the same account is scheduled, posting or already posted, so nothing posts twice. And one video or slideshow goes to one account per platform: schedule_post, schedule_instagram_post and update_scheduled_post refuse with status duplicate_content, and schedule_slideshow_post with status rejected and code duplicate_content (over REST, error_code duplicate_content, and code on the slideshow schedule route), a post whose content another account of the workspace has scheduled, is posting or posted on the same platform (a draft-only TikTok post, only_create_draft, never counts: it never goes public): the same project or slideshow, or a file with the same bytes (the same upload twice; a carousel all of whose slides another account's carousel holds, or that holds all of another's). TikTok and Instagram flag the same content on a second account. The same video on TikTok from one account and on Instagram from another is fine, and an upload re-cut, re-encoded or with other words on it is another file.
  • •Pick a time the phone has free. Each account posts from one Pamba iPhone, and a phone runs one post or task at a time for every account on it (an avatar's TikTok and Instagram accounts usually share one). An exact TikTok time that overlaps another post, a task running on the phone or a maintenance window is refused as schedule_conflict with the next free time in message; an exact Instagram time there is moved to the next free slot that day (read the returned scheduled_at) and is refused only when the day has no room left. The refusal carries a next field pointing at get_account_schedule: schedule_post and schedule_instagram_post answer it as their status, schedule_slideshow_post as its code, and update_scheduled_post as update_failed with code schedule_conflict. That tool (platform tiktok or instagram, username, optional from and to in UTC, seven days by default and fourteen at most) returns openings, the stretches in which a post picked at an exact time is kept as picked (a schedule with automate, or while its video is still being finished, waits at least 30 minutes from the request instead) (whole minutes, both ends included, none before earliest_post_at, which moves with the clock: a pick must still be that far ahead when it is sent), busy, what holds the phone (post with this workspace's post_id, post_platform and post_username, task, or maintenance), and post_minutes, how long one post holds the phone. Read it before scheduling at exact times. A new account being set up on a phone goes first: a post that comes due while Pamba creates an account on its phone waits for the setup and goes out soon after it, its scheduled_at moved to the new time, and the wait costs none of its retries. Each post you schedule holds its phone for post_minutes from its time, so space exact picks on one phone at least that far apart. When a TikTok and an Instagram account share a phone, the Instagram post takes the first free time after the TikTok one that day (Pacific time; right after it when the phone is free), and a day with no room left for it refuses both, so leave post_minutes free after a TikTok time that cross-posts; a Facebook share rides the Instagram post. A post you move with update_scheduled_post is not blocked by itself or its pending cross-posts; the cross-posts then follow it to the first free time after the new time that day, and one with no room left keeps its old time, so leave post_minutes free after the new time for each cross-post on the same phone. A new caption on the TikTok post likewise carries to its Instagram cross-posts in the same status (pending with pending, failed with failed) that still had the old caption; one whose caption no longer matched keeps it, and editing a cross-post never changes the TikTok post. A post schedule_post booked away from the exact time asked for carries moved_from (that time) and move_reasons, a list of too_soon, video_to_make, video_finishing, accounts_setting_up and phone_busy in the order they applied, with a note: tell the user the new time and why. A date-only scheduled_at finds a free slot by itself. The openings are a read, not a booking: another schedule can take the time first, and on rare occasions the fleet-wide cap on posts at one time refuses a pick inside one, again with the next free time. Over REST it is GET /devices/accounts/schedule.
  • •How often an account should post. An account Pamba created less than 10 days ago does best posting once a day, and any account at most 2 or 3 times a day: past that each post brings diminishing returns and makes TikTok or Instagram likelier to flag the account. Pamba warns and never refuses. A post that makes a new account's 2nd post in a day (the workspace's day, counting its pending, posting and posted posts) or any account's 4th is booked all the same and carries posting_warning in the result of schedule_post, schedule_instagram_post, schedule_slideshow_post, and update_scheduled_post when the edit moves the post: tell the user the warning and offer to move the post to another day. list_accounts sets new_account_until_utc on each account on the workspace's phones that Pamba created less than 10 days ago, so spread several posts on one account across days before scheduling them. Over REST the schedule and edit responses carry posting_warning as { code, message, day, posts_that_day, posts_per_day }, code being new_account_posts_per_day or posts_per_day.
  • •A project with an avatar may only post to a TikTok account linked to that 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. get_project's posting_accounts (and get_slideshow's, for a slideshow) lists the accounts a schedule may name, the choices the app's schedule dialog offers, and list_posting_accounts lists every avatar's and the no-avatar pool at once: read one before schedule_post. A listed TikTok account that is not signed in on one of the workspace's phones, or one with an open suspension, is still refused.
  • •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. A TikTok caption with one of the commercial-disclosure hashtags #ad, #ads, #sponsored, #partnership or #paidpartnership is posted with TikTok's commercial content disclosure on, as Branded content ("You are promoting another brand or third-party"), so TikTok labels it "Paid partnership" and Pamba cannot take the label off once it is live. TikTok publishes no post with such a hashtag without the disclosure. generate_project_description never writes one: whether a post is an ad is the user's call. An Instagram caption, a reel's or a carousel's, holds at most 2,200 characters and 5 hashtags: Instagram keeps no more than 5 hashtags on a post, so a longer list is refused rather than posted with the rest stripped to plain words.
  • •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.
  • •TikTok Shop. A TikTok account enrolled in TikTok Shop can link one product of its shop to each post, chosen per post (there is no default product). get_tiktok_shop reads the shop as the account's phone last saw it (linked, the showcase, the catalog's products with their ids and showcase_state); sync_tiktok_shop reads it again on the phone, which takes a few minutes. The account's daily phone visit also looks for the Shop tab and reads the shop by itself when the tab appears or goes, so an account TikTok enrolls shows up without a sync. Pass tiktok_product_id (and optionally tiktok_product_link_name, at most 30 letters, numbers, spaces, apostrophes or hyphens) to schedule_post or update_scheduled_post; remove_tiktok_product takes it off a pending or failed post, and a post moved to another account drops a product it does not restate. Only a video post links a product; a slideshow cannot. A post links from the showcase only, so a product not in it is queued for adding the moment the post is scheduled (add_tiktok_showcase_products does the same on its own), and the post goes out only after the phone can have added it: an exact time before that is refused with status tiktok_product_not_in_showcase_yet and earliest_at_utc, and a date-only time is given a slot after it. The phone reads a product's title off the screen, so a long one reads cut short with … at its end; rename_tiktok_shop_product gives a product its display_title, the name to show for it (the phone keeps finding it by its title, and a read of the shop never changes the name). The phone reads each of the showcase and the catalog up to about 80 products; a list it could not read whole (longer than that, or with a product the phone saw but could not read) reads showcase_complete or catalog_complete false, and the lists then hold part of the shop. The phone adds the link before its Post tap, and a post whose link cannot be added fails rather than go out without it. TikTok caps a new shop's shoppable videos (shoppable_videos_left, as the latest post that linked a product left it).
  • •AI label. ai_label: true on schedule_post labels the video as AI-generated on every platform that call schedules (TikTok's "AI-generated content" label, Instagram's "AI info" label); schedule_instagram_post takes it for the reel, and update_scheduled_post turns it on or off for one pending or failed post. TikTok and Instagram video posts only: a slideshow post and a Facebook post refuse it. The phone sets the label before its Post or Share tap, and a post it cannot label fails and is retried rather than go out unlabeled. Pamba cannot take the label off once the post is live, so set it only when the user asks.
  • •First-frame cover. Left alone, TikTok and Instagram pick a video's cover from a frame a few seconds in. first_frame_cover: true on schedule_post makes the video's first frame, where its burned-in hook is, the cover on every platform that call schedules; schedule_instagram_post takes it for the reel, and update_scheduled_post sets it (true) or leaves the cover to the platform again (false) for one pending or failed post. TikTok and Instagram video posts only: a slideshow post and a Facebook post refuse it. The phone sets the cover in the app's Edit cover before its Post or Share tap, and a post it cannot set it on fails and is retried rather than go out with another cover.
  • •If a schedule is rejected, read the reason and offer the valid accounts or another time; do not retry blind.
Slideshows
  • •A slideshow is a TikTok photo post or an Instagram carousel (or, with as_reel, one reel Instagram builds from the slides): up to twenty pictures with words burned in, and one sound per platform under the whole post: a TikTok sound for the photo post, an Instagram track for the Instagram post. It is made from pictures the workspace already holds — nothing about a slideshow spends credits, and there is no slide image generation. Get the pictures in first: upload_asset with a public url (or list_assets for ones already there), then create_slideshow with slide_images (one Pamba-hosted url per slide, in order) and lines (write_with_ai=false keeps them verbatim; no lines gives picture-only slides). Both are strings: a JSON array serialised as a string, or one entry per line; the JSON array itself is taken too. A slide with a picture but no line has no text element.
  • •The tools are in the content toolset (list_slideshows, get_slideshow, create_slideshow, update_slide, add_slide, remove_slide, reorder_slides, add_slide_image, update_slide_image, remove_slide_image, suggest_slide_lines, search_tiktok_sounds, set_slideshow_sound, search_instagram_audio, set_slideshow_instagram_sound, schedule_slideshow_post, get_slideshow_scheduled_post, duplicate_slideshow, export_slideshow, update_slideshow). Every edit re-burns the slide; get_slideshow reports each slide's status (ready once burned) and its image_url.
  • •Sound: search_tiktok_sounds with no query returns TikTok's Hot chart (source hot, in chart order, the editor's default listing); with a query it pages TikTok's own library (cursor from the previous page's next_cursor), and set_slideshow_sound picks one for the whole post by tiktok_music_id (the search result's music_id); the search is a plain read, not one of the capped AI assists. Searches that reach TikTok run at most 5 a second, 30 a minute and 1,000 a day per workspace (a repeat the day's cache answers is free); past that the tool answers rate_limited with retry_at (midnight UTC once the day's are used), and the REST route a 429 with a Retry-After header. Every result carries commerce_right: whether TikTok clears the sound for commercial use, which a Business account needs to post with it (its sound page shows no Use sound otherwise), so prefer a cleared sound for such an account.
  • •Instagram sound: the Instagram post's sound is picked apart from the TikTok one, because the two platforms' libraries are different. search_instagram_audio with no query returns Instagram's Trending chart (source top, read from a US account's picker); with a query it returns Instagram's own results for the words, ten at most with no further page, and the same per-workspace rate and daily limit as the TikTok search. set_slideshow_instagram_sound picks one by instagram_audio_id (the result's audio_id; none clears it). The track starts where Instagram's picker starts it (highlight_start_seconds), and the phone picks it in Instagram's own music picker when the Instagram post goes out. One it cannot find there goes out as the same song by the same artist at another length, else as a song Instagram picks, and get_scheduled_post and list_scheduled_posts name it in audio_fallback. The searches run on accounts outside the US, which list some songs US Instagram lacks, so a song a Pamba phone did not find, posting from an account Pamba knows is not a Business account (a Business account's picker lists only commercial music), is recorded and left out of every later search and of the chart. A pending or failed post still using such a song reads instagram_song_unavailable true on get_scheduled_post and list_scheduled_posts: set another song before it goes out to choose it yourself. A pick the chart or the day's searches did not list is looked up on Instagram's audio page, and that lookup counts against the same per-workspace rate as a search (not the daily limit, which counts searches only). No provider says which songs an Instagram Business account may use, so the whole library is listed. When Instagram's music search or audio page is not answering, the tool answers search_unavailable and the REST route a 503.
  • •Posting: schedule_slideshow_post schedules the slides on a Pamba-managed iPhone and approves the slideshow; it is refused while any slide still has no picture. A TikTok photo post takes a single slide; an Instagram carousel or reel (platform instagram or both) needs at least 2. With platform tiktok (the default: tiktok_username, scheduled_at, caption; the caption is the post's description, capped at TikTok's 4,000 characters with the same hashtag rules as a video, and the post has no separate title) it is a public photo post carrying the slideshow's sound; with platform instagram (instagram_username, omitted when the avatar has one Instagram account; scheduled_at; caption, at most 2,200 characters and 5 hashtags, no @) it is a public carousel carrying the slideshow's Instagram sound, each slide fitted whole into Instagram's square or 3:4 feed frame (square when the slides are square or wider), and it is refused until the slideshow has one (set_slideshow_instagram_sound): without a track Instagram does not treat the carousel as a reel and shows it only to followers, and the track cannot be cleared while an Instagram post waits; as_reel=true posts it instead as one reel Instagram builds from the slides, each 4 seconds with the same track under it (which the reel needs too), in the full 9:16 frame with the first slide as its cover (refused with platform tiktok; with platform both the TikTok photo post is unchanged); with platform both (tiktok_username, and instagram_username as above) the two are booked together in one plan, both or neither, under one caption that must pass both platforms' rules, and the result's posts lists each. A slideshow posts once per platform. get_slideshow_scheduled_post follows them (posts holds one per platform: pending, in_progress, completed, failed), and cancel_scheduled_post with a post's id cancels it while it is still pending. A TikTok photo post is verified against the account's profile and gets analytics like a video. An Instagram carousel is verified on the phone's word (the phone reads it as the profile's newest post), and the account's hourly analytics refresh then links it to its live post (posted_url, a /p/ link) and tracks its likes and comments; Instagram publishes no views for a carousel. A slideshow's reel is verified the same way, then linked among the account's reels (a /reel/ link) with its public views; each Instagram post in posts carries as_reel.
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. One exception keeps a new account from being dropped: a TikTok account get_avatar lists with name_pending (its picked username is still being applied) stays linked while its rename runs and for an hour after it lands, whatever set you pass, and the set names it by its picked username; unlink_account removes it. 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. A free workspace (signup credits only) can connect up to five such accounts; the sixth answers connected_account_limit, and any plan, account seat or credit purchase lifts the limit.
  • •Operator-only tools are listed only to Pamba staff. A customer workspace or a connected chat app never sees them, and calling one by name is an unknown tool: the reference-frame tools (create_reference_frame, update_reference_frame, delete_reference_frame), create_social_account, get_account_creation_status, resume_account_creation, generate_image, the writer-engine controls (run_content_engine, push_content_batch, apply_script_revision, set_script_starting_frame, decide_learning_item, set_autonomy_dial, set_batch_share_page, send_writer_chat_message), and the engine's spend bookkeeping (log_llm_usage, and get_llm_usage_summary, which reports the model spend at Pamba's cost). control_stage_run is listed to everyone, but cancelling a run, and retrying some runs, answers operator_only for a customer workspace.
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 BANNED, SHADOWBAN_RISK (after the account's first three weeks of tracking), NOT_RECOMMENDED and SUSPENDED.
  • •set_account_order saves the order the workspace's Accounts page lists accounts in, for everyone in the workspace, and list_accounts reports each account's position in it. A save replaces the whole order, so moving one account means sending every account in its new place; accounts left out, and accounts added later, follow in the default order (open issues first, then the most viewed), and under a saved order archived accounts are listed last. An empty list goes back to that default.
  • •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. A paid purchase also pairs with seats already on the subscription it raises, the newest one that is active and not set to cancel: purchase_account_seats called with no seat counts returns unpaired_seats, per platform the seats on that subscription with no partner yet, and the next paid purchase pairs up to that many seats of the other platform with them. One Instagram seat next to one unpaired TikTok adds fifty dollars a month, not one hundred; two Instagram seats next to it add one hundred fifty. Only what unpaired_seats lists pairs this way: an account with no seat, a seat on a free-seat grant (free_seats_remaining) or on another subscription, and a seat cancelled with cancel_account_seat (even while it posts until its period ends) pair with nothing, and when no subscription can be raised, the purchase starts a new one where only its own seats pair. 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. While no Pamba phone is free, purchase_account_seats reports high_demand true, and a checkout with more seats on a platform than open_slots lists (what the workspace's own phones still take) carries high_demand_notice: those new accounts may take up to 3 days to be created (for purpose connect, the accounts may wait up to 3 days for a phone to sign in on), and the user hears so before paying. Facebook has no seat of its own; it rides Instagram cross-posting. purchase_account_seats returns a checkout URL: a purchase that raises an existing subscription gets Stripe's confirm page raising its quantity with proration, and one that starts a new subscription gets a new checkout. When Stripe cannot list the workspace's seat subscriptions or open the confirm page, the tool answers purchase_unavailable with a message to try again, and nothing is charged or started. A checkout paid by bank transfer completes before its payment clears (usually up to four business days): until then its seats are listed under waiting_for_payment, nothing is created for them, and they move to being_set_up once Stripe confirms the payment; a payment that fails drops them. Do not buy again while seats are waiting for their payment.
  • •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. A TikTok or Instagram account can post for several avatars (a Facebook account follows its Instagram account): link_account_to_avatar adds one without taking the account from the others, so a video for a second avatar can go to an account the first one already uses instead of a new purchase. unlink_account is the reverse: it drops one avatar's binding, and an account no other avatar links 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 (names for a waiting order are prepared in the background, so a call usually answers at once; otherwise a few seconds each, so for a large order pass batch_size, for example 8, and call again with exclude set to the names already offered; a batch answers within 90 seconds), 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; until the rename lands (usually a few minutes) the account stays off list_accounts' accounts, listed under accounts_being_named with its pick, and its seat shows in purchase_account_seats' being_set_up at stage applying_username. A name that fails when applied falls back to close variations, then keeps the platform handle; the account-ready email and Home item then say which pick was not used, and list_accounts carries it as picked_username_not_applied for thirty days or until the account is renamed. 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. Once a workspace holds no seats, the phones its seats came with are signed out and returned to the pool three days after its last seat subscription ends, even while a credit plan runs. When a whole seat subscription ends (its seats cancelled, or Stripe gave up on a failed payment), its accounts stop posting at once but stay on their phone for those three days: get_seat_billing lists them under restore_offers, and restore_account_seats returns a checkout that brings them all back at the price that subscription billed (an account the workspace had paused itself comes back paused); after that, resubscribing can't bring them back. An offer's payment_failed is true when Stripe ended the subscription because its renewal payment didn't go through; Pamba then voids that subscription's unpaid invoices, so the old bill can't be paid for nothing. get_seat_billing shows the next renewal's total per seat. recreate_social_account replaces a seat-funded Instagram account with a new one on the same phone and seat (once every 10 days per seat, admins only; the old account keeps counting in the workspace's analytics with nothing new read for it, flagged archived, its videos are not deleted and Pamba keeps its credentials in case they are ever needed; its pending posts move to the new account, or with move_scheduled_posts: false are cancelled when the new account lands and their videos stay to schedule again); get_account_recreation says whether an account can be recreated now and how a recreation is going. A TikTok account cannot be recreated by the workspace (unsupported_platform): only Pamba's team replaces one. While any recreation runs, list_accounts leaves the old account out and lists it under recreations_in_flight.
  • •import_social_account brings a TikTok or Instagram account the user already owns onto Pamba: a seat still waiting for an account takes it at the normal seat price, one bought to connect before one bought to create (no_open_seat means buy seats with purchase_account_seats and purpose connect first: the same prices and pair discount, no usernames asked, and each seat waits in being_set_up, purpose connect, until an import fills it, so ten accounts are paid for once and connected one by one, whenever the user is ready), and one of the workspace's phones signs it in with the handle, password and, when it is not the handle, the email or phone the user signs in with. Accounts that sign in only with Google or Apple need a password set in the app first. The sign-in usually stops on a verification code the platform sends the user: poll get_account_import_status every few seconds, and the moment its code_request is set (kind email, sms, authenticator or approve_on_device, the masked destination, wrong_code after a refused code, expires_at) ask the user for the code and pass it to submit_account_import_code; codes expire within minutes, and resend_account_import_code asks the phone to send a new one (after resend_available_at: the apps hold their own resend about a minute). For an email or text code the app also asks on the user's own phone whether the sign-in is theirs, and approving there works with no code. password_rejected true means the app said the password was wrong and sent the code to an address it chose: the user enters it only if the address is theirs, and if it is not, the handle reached someone else's account (cancel_account_import and start again with the right handle, or, once the code wait ends and the import reads needs_customer, retry_account_import with the right username). needs_customer carries a failure the user must fix (a wrong password, a code never given, too many tries, a suspended account, or passkey_required: TikTok only lets the account in with a passkey on the user's own phone, which they remove in TikTok under Settings and privacy, Account, Passkey), then retry_account_import with the new password, login or handle (username: a mistyped handle reads account_not_found; a retry after five sign-in attempts hands the import to Pamba's team instead). failure.code wrong_account means the user's login opened another of their accounts, named in opened_username, which the phone has already signed back out: ask whether it is the one they meant, and if so retry_account_import with username set to it (the same login and password), otherwise with the username and login of the account they meant (a reconnect leaves opened_username empty and keeps its handle); with_team means Pamba's team is finishing the sign-in by hand and settles any code it needs with the user directly. cancel_account_import stops an import and frees its seat; a reconnect is refused (reconnect_not_cancellable) while its account still fills its seat, and Pamba closes one 3 days after its seat ended, when the seat can no longer be bought back. Pamba changes nothing on an imported account by itself (no AI-comment filter, no AI-creator label, no rename); it warms like every account on a phone. If TikTok or Instagram later signs it out of its phone (a password change, or the app ending its session), its import reopens as needs_customer with failure.code signed_out and the user is emailed: until retry_account_import reconnects it (the stored password unless the user gives a new one, then the code the app sends, as on the first connect) it can't post or warm, schedule_post, schedule_instagram_post, schedule_slideshow_post and post_instagram_story refuse it with account_needs_reconnect, its scheduled posts fail with that code (retry them once it is reconnected), and list_announcements leads with a warning (kind warning, label Signed out) naming it. An import connects only the account it names: Instagram also signs in every account that shares a Meta Accounts Center with it, and Pamba never lists, warms or posts from those; connecting one takes its own seat and import.
  • •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.
  • •follow_accounts has a TikTok or Instagram account on a Pamba phone follow a list of usernames or profile links. An account's lists keep to a daily limit, because TikTok and Instagram can flag an account that follows too much: none in its first seven days on a Pamba phone (counted from its first warming session or follow list), so the first follow lands once the account is a week old, then one a day at most, however old the account. Warming's own follows are separate and do not count toward it. get_account_follows reports the limit today as daily_limit (with starts_at_utc while a new account's first days last). A list waits for the limit, and a waiting list's next session time says when it continues. The phone follows one account per session, at a different time each day between 8 AM and 1 AM Pacific, around the account's posts and warming, and at most fifty wait at once; a session that followed no one (already followed, not found) leaves the day's follow for the next session, at least three hours later (up to half an hour more). A request while a list is running joins it (up to a hundred accounts on one list in all; a list being stopped takes nothing new, so the request opens the next list), and a new list starts no sooner than three hours after the account's last session. A profile the account already follows is left alone, and the account never follows itself. It runs in the background: get_account_follows shows the running list with each account's outcome (followed, requested for a private account, already_following, not_found, failed (where a mistyped or deleted username usually lands, with a reason to check it), cancelled when a stop reached it first, or skipped when the request failed before reaching it), and cancel_account_follows stops what is still waiting; a session already on the phone finishes its one account first.
  • •delete_account_posts deletes published posts from an account on a Pamba phone, videos and slideshows alike (TikTok videos and photo posts; Instagram reels, carousels and pictures), up to twenty per request and fifty waiting per account: it removes them from the platform itself, so confirm the exact posts first. Name each by its post_id or by its link: a project's posted_tiktok_video_url or posted_instagram_video_url deletes that project's post, and a slideshow post's posted link deletes that slideshow post. list_account_posts lists the account's posts newest first with each one's post_id, its kind (video, slideshow or picture) and its deletion state (pending, deleting, deleted, not_found when the post was already gone, not_confirmed when the phone could not prove the post on screen was the one asked for and deleted nothing, delete_unconfirmed when the phone may have deleted it but its link did not show it gone (the delete was confirmed, or the post was in a session nobody heard back from), which is not final: Pamba checks the account's own listing about fifteen minutes after the session and settles the post deleted once the account no longer lists it, or failed when it still shows it (a post the listing cannot see, such as an Only-you TikTok post or an Instagram reel off the profile grid, is settled by Pamba's team instead), failed, cancelled or skipped). A post the phone reported deleted that the account still lists on both checks turns failed too. The phone deletes five per session, around the account's posts and warming; TikTok and Instagram keep a deleted post 30 days in Recently deleted, where only someone on the phone can restore it. With post_ids (ids or links) it returns only those posts, and still returns a deleted post the platform no longer lists, with its state. cancel_post_deletion stops what is still waiting; a session already on the phone finishes its own few first. In the app it is Delete from TikTok or Delete from Instagram on a posted post (the project's More menu, and the post's row and calendar card on the Schedule page, slideshow posts included).
  • •list_comments lists the comments on the workspace's own TikTok and Instagram videos from the last sixty days, each top-level comment with its replies and its video: newest thread first, or with sort the most liked first (most_liked) or in the order TikTok or Instagram itself ranks the video's comments as of Pamba's last read (top). Comments are read about every fifteen minutes once a video's comment count grows, so a new one shows within about an hour; synced_at says how fresh the list is. questions_only=true keeps the comments that ask the creator something or ask for something the video offered, mine=true keeps the threads the workspace's accounts took part in, and q searches the text, the authors and the replies. about_ai marks a comment that says or asks whether the video or the person in it is AI or not real, and each TikTok account on the workspace's phones carries ai_comment_block_state and auto_block_ai_comments: when such comments sit on an account whose filter is failed, or off with auto_block_ai_comments true (false means the user allowed AI comments), update_account_profile with disable_ai_comments and auto_block_ai_comments both true turns on TikTok's filter for them, as the Comments tab's Block AI comments line does. list_comment_videos gives the same comments per video, newest activity first, with how many match and how many are questions, for going through them one video at a time; it also lists every post from the last seven days before its first comment, so the account can leave that first comment with comment_on_video. reply_to_comment answers a top-level comment and comment_on_video adds a new comment to one of the workspace's own videos, from any of the workspace's accounts on that platform (account, the video's own by default); either is typed within minutes on that account's phone, or at scheduled_for (an ISO-8601 time with its offset, from a minute to thirty days ahead), so only an account on the workspace's phones can send (the accounts list says which can_reply), up to one hundred fifty characters on TikTok and two thousand two hundred on Instagram, and at most thirty in any rolling day per account. On TikTok an @username in the text is picked from TikTok's own list as the phone types it, so it posts as a tappable mention showing that account's display name; a username TikTok does not offer fails the comment unsent (failed, with a reason naming it), since typed as it is it would not link. Instagram links a typed @username by itself. TikTok and Instagram lock an account that comments on other accounts' videos too early or too often for suspicious activity, so on another account's video (another of the workspace's), an account Pamba created comments only after its first seven days (status new_account), and any account at most the workspace's cross_account_per_day in any rolling day (one unless the Pamba team raised it; cross_account_daily_limit); each account in accounts that can reply carries cross_account_blocked_reason and cross_account_available_at while it is held, and its own videos are never held. Every limit counts at the time the comment goes out, against the account's comments before and after it, so a refusal's available_at passed as scheduled_for queues it, and a workspace spreads comments across days by scheduling them. What an account is asked to do now on one video, comments, replies, likes and pins alike, goes out together in one phone session, as a creator does it in one sitting: each request waits two minutes for the next, the run goes out at most ten minutes after its first, and a session takes up to ten; requests given scheduled_for go out at their own times, so the same time groups them and different times keep them apart. comment_on_video with pin true has an Instagram reel's own account pin the new comment once it is live. like_comment likes a top-level comment from one of the workspace's accounts (the video's own by default on its own videos) and pin_comment pins one on the workspace's own Instagram reel from the reel's account (Instagram keeps up to two pinned comments per post; TikTok no longer lets creators pin comments, pin_not_available); both are typed on the phone like a comment, now or at scheduled_for, thirty a day per account together, never counted against the comment limits; a like from an account that does not own the video (another of the workspace's accounts' videos) keeps to the workspace's cross_account_likes_per_day in any rolling day (one unless the Pamba team raised it; cross_account_daily_limit, with available_at), each account in accounts carrying cross_account_like_blocked_reason and cross_account_like_available_at while it is held, so likes in bulk are spread across days by giving each its scheduled_for; and list_comments shows them in each thread's actions (list_outgoing_comments lists them with action like or pin; it lists comments alone by default). To reply in advance, pass reply_to_comment the outgoing id of one of the workspace's own comments that is not live yet: the reply waits for it, and up to three replies can wait behind one comment, going out one at a time in the order they were asked for, each once the one before it is live and fifteen to forty-five minutes after it was sent (reply_to_outgoing_id names the comment it answers and waiting_for_id the one it waits for). Once a comment has replies in advance behind it (on their way or live), another reply to it, by its outgoing id or by its comment id once it is live, joins the end of that line. If a comment before them fails, is cancelled or still does not show two days after it was sent, the replies behind it are cancelled too; a fourth reply answers chain_full, and a reply to a comment that will not go live answers parent_not_live. list_outgoing_comments follows each one: queued, sending, posted (the phone sent it, or Pamba lost the phone's answer; a read of the video settles which), live once a read of the video shows it, not_visible when no read has six hours later, or failed; cancel_outgoing_comment withdraws one while it is still queued, with the replies waiting behind it.
  • •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. On TikTok the phone picks each @handle in a bio from TikTok's mention suggestions, so it lands as a tappable mention that TikTok shows as @ and that account's display name; a handle TikTok offers no account for fails the bio field with the error code bio_mention_not_found, and a mention the phone could not show links the account asked for (a pick on a suggestion whose caption TikTok cut short, or a bio TikTok already held whose mentions are not drawn as links) with bio_mention_unconfirmed; an email address stays text. 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 (on TikTok, a read that is the bio Pamba set with each mention shown as @ and a display name still reports the bio as set, with the handles an edit sends again); 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. On TikTok the phone gets time for each field and each @handle an edit asks for; a field it had no time left to start comes back not applied with the error code field_out_of_time, beside the fields it did apply. An Instagram bio with a link, put on an account Pamba created less than seven days ago, still goes through, and the result carries a warning: Instagram often restricts link sharing on accounts that add a link this early. A queued answer, from this tool or from enable_instagram_ai_creator and disable_instagram_ai_creator, carries the edit's job_id, which get_account_profile_edit reads to its outcome.
  • •update_account_profile also switches an Instagram account between personal and creator with instagram_account_type, sets its profile category with instagram_category (any case, one of Digital creator, Artist, Musician/band, Blogger, Clothing (Brand), Community, Education, Entrepreneur, Health/beauty, Writer, Personal blog, Product/service, Gamer, Restaurant, "Beauty, cosmetic & personal care", Grocery Store, Photographer, Shopping & retail, Reel creator, Gaming video creator, Social Media Agency, Camera/photo, Computer Company, Makeup Artist or Creator) and whether the profile shows it with instagram_show_category, sent in a call of their own with no other field: the account type is its own job on the phone, answered queued with a job_id that get_account_profile_edit reads (its kind is instagram_account_type, its fields account type, category and show category). Pamba never switches an account to business, since Business accounts can't use most of Instagram's licensed music: business is refused with the error code instagram_account_type_invalid, and an account that is a Business account already is never switched to creator (instagram_account_business) but can go to personal or change its category; when the phone finds a Business account Pamba had not read yet, the job fails with ig_account_business and nothing changes. creator on an account that is creator or business while its switch to personal still waits for the phone, never yet sent to it, withdraws that switch instead: the call answers withdrawn with withdrawn_job_ids and the account stays as it is (a category or display in the same call is queued as its own change); a switch to personal already sent to the phone is refused with change_in_progress, and on a creator account after an earlier type switch of it ended unread the call is queued over the waiting switch instead (it answers queued), for the phone to read the type the account has. A personal account, or one list_accounts gives no instagram_account_type for, switched to creator takes the category Digital creator unless the call (or a change already queued) names one, and a hidden display unless it names one; an account that is creator or business already changes only the fields the call names. A category or its display alone needs an account that is creator or business, or has a switch to creator queued (one Pamba knows is personal, or with a switch to personal queued, is refused with the error code instagram_account_not_professional; one whose type Pamba has not read goes to the phone, whose job fails with ig_account_not_professional when the account is personal), a category Pamba does not offer answers instagram_category_invalid with the list, and a call for TikTok is refused, since TikTok's only switch is to a Business account, which limits it to TikTok's commercial music library. Switching makes the profile public, and Instagram also turns on stricter filtering for professional accounts, which hides more comments and message requests; going back to personal turns its insights off. list_accounts reports each Instagram account's instagram_account_type, instagram_category and instagram_show_category, the newer of what Pamba last set and what the hourly analytics refresh last read. Instagram trial reels need a creator or business account with at least two hundred followers.
  • •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 and disable_instagram_ai_creator turn the label on and off; each leaves a label already where it asks untouched, and a change still queued for a busy phone takes the newest call's direction. While the phone is applying one direction, a call for the other is refused with change_in_progress; call again once it settles. list_accounts reports each Instagram account's ai_creator_label_state: turning_on or turning_off while the phone is queued or running to change it, then on or off, failed when the newest attempt to turn it on failed, or turn_off_failed when the newest attempt to turn it off failed (the label is still on); each TikTok account carries its AI-comment filter as ai_comment_block_state. Both are empty for an account on none of the workspace's phones.
  • •Warming (human-like feed browsing) is always on for every device-hosted account and cannot be turned off from the tools. Every account warms once a day under its own config (get_account_warming_config, set_account_warming_search_terms), whichever avatars link it. An avatar's config (get_warming_config, terms via update_avatar_settings) is the one its accounts start from: an edit there is written onto every account the avatar links, shared ones included, and an account linked to its first avatar takes that avatar's settings. A linked account warms while every link of it posts and at least one linking avatar is active. Terms cap at twenty-five of eighty characters, and a save carrying a term with "porn" is refused with a message naming it, since TikTok and Instagram show no results for it; without explicit terms, sessions fall back to terms generated from the workspace niche (search_terms_source niche), while an empty list saved with use_niche_terms false is kept as the owner's own and its sessions search nothing (search_terms_source none; use_niche_terms true with an empty list goes back to the niche terms). A term TikTok answers with its guidelines page on the phone leaves the account's terms, the terms of the avatars linking it and of every account those avatars link, and the niche terms by itself, and the workspace gets a Home item at once and an email about two hours later; a "No results found" on either app removes nothing. TikTok and Instagram sessions both search those terms and judge what they watch against the workspace niche, and a session that browses the feed follows the creator of an in-niche video now and then: at most one follow per account per day. A session that searches gives at most 3 likes in all, follows no one and shares nothing. The workspace's defaults, which a new avatar and a new TikTok or Instagram account with no avatar start with (an account added to an avatar takes the avatar's setting), are update_workspace_settings warming_searches and warming_follows (list_workspaces reports both); either needs apply_warming_to_existing_accounts: true also writes it onto every existing account and avatar, false changes the default for new ones only, and without it the call changes nothing and answers needs_choice. Warming keeps running either way. set_account_warming_switches sets one account's own switches: follows_enabled false and its sessions follow no one, searches_enabled false and they never search (the first week's search included) while its terms are kept for when searches are back on; get_account_warming_config reports both. update_avatar_settings with warming_searches_enabled or warming_follows_enabled sets them for every account an avatar links, shared ones included, and for an account it links later that no other avatar links; get_warming_config reports what the avatar's last edit set, as it does its terms. An account searches in one session per warming day at most (a warming day starts at 8 AM Pacific), whatever its search chance or a boost's: once a session has started a search, even one the app answers with nothing, the day's other sessions browse without one, and a day may have none. For seven days from an account's first completed session, the longest session of each warming day opens with that search whatever the search chance, and the day's other sessions do not search. By default an account warms three five-minute sessions a day. start_account_warming_boost warms one account harder for duration_hours (1 to 336), then it goes back to its own pace by itself: sessions_per_day (1 to 5; needs at least 24 hours) applies from the next daily booking at 10:00 UTC, search_probability (0 to 1) from the next session, still one search a day at most, and you pass either or both. A new boost replaces a running one, get_account_warming_boost reads it and end_account_warming_boost ends it early. It works for assigned and unassigned accounts alike, never changes the config or its terms, and renaming the account ends it. 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, creators followed, the search term, a few sample screenshot URLs), daily TikTok health checks, posts, profile edits, finished follow requests and post deletions, and the comments and replies the account posted with every screenshot the phone took of each; 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 and surfaced on Home, and emailed to the workspace about two hours later; these kinds never pause the account. get_tiktok_account_health returns the latest screenshots and open issues with each appeal's progress; request_tiktok_appeal records the decision to appeal a removed or restricted video or an account warning, one appeal per issue, and nothing is appealed without it unless the account's auto-appeal is on (set_tiktok_auto_appeal, reported by get_tiktok_account_health as auto_appeal): then every open issue is appealed when it goes on, and each new one as the daily check finds it. The account's phone then files the appeal itself, submitting only once a TikTok screen prints that video's posted time (a video it cannot pin down is filed by the Pamba team by hand; an ACCOUNT_WARNING is appealed through the video TikTok removed with it: appealing it also appeals the one removed video the same daily check found, and one verdict decides both; with none or several, the Pamba team files it by hand), and reads TikTok's verdict off the account's notifications once a day until it answers: appeal_verdict APPROVED closes the issue as restored, REJECTED keeps it open. An appeal TikTok has not answered four days after filing sends the phone back to the video to read TikTok's answer on its page. A removed video reappearing also closes its issue and approves its appeal, a video the phone finds no longer restricted when it goes to file closes its issue as restored, other issues close after two clean daily checks, except one whose appeal is still being filed or still waits for TikTok's verdict (up to 21 days after filing). A video the workspace deletes through Pamba (delete_account_posts) takes the open issues that predate the deletion with it once the deletion confirms it gone (closed as DELETED): its appeal stops wherever it stood, and nothing is filed, read or rechecked for it. An ACCOUNT_WARNING TikTok gave for that video stays open; if its appeal was not filed yet, the Pamba team checks whether TikTok still takes it. When Pamba replaces an account with a new one, every open issue of the old account closes (closed_by MANUAL) and its appeals stop. 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. An account Pamba confirmed TikTok or Instagram banned carries a BANNED health flag and incident: it can no longer post (Pamba still accepts posts for it, so never schedule one there), Pamba replaces it with a new account on its seat at no extra cost, and only Pamba closes the incident (close_account_incident answers pamba_closes), with closed_by RESTORED when the account comes back or MANUAL once a new account replaces it. While any account is banned, list_announcements leads with a warning (kind warning, label Banned) naming them, the strip shown across the top of the app; a Signed out warning about accounts to reconnect comes after it. A ban outranks a SUSPENDED incident on the same account: don't suggest an appeal or close the suspension. Instagram can also restrict one feature of an account that keeps working ("You can't share links" until a date): the phone closes Instagram's restriction page and Pamba opens a FEATURE_RESTRICTED incident whose detail carries the restriction and its ends_on. It never pauses posting or warming, shows through its end date and closes the day after. 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 accounts by default (avatar-linked, signed in on its phones, or with a post it scheduled); pass include_research_accounts=true for the inspiration accounts. Ground recommendations there rather than in memory. generate_script_from_idea writes a fresh script, thirty to forty-five seconds unless the idea asks for another length and in the idea's language, 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. An inactive avatar's accounts stay in it as an archived account does, marked archived: their old views keep counting and nothing new is read for them until the avatar is active again. 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.
  • •An Instagram slideshow or picture (media_kind photo) has no public view count: Instagram shows its views only to the account that posted it, so the account's Pamba phone reads them off the post's own insights, about three days after posting and a final time about four weeks after, and views_read_at says when. Every post list_social_videos, search_tracked_videos and get_post_analytics return carries views_source, where its views come from: public (the count the platform shows publicly, read by Pamba's hourly scrape: every TikTok and Facebook post, an Instagram reel, a slideshow posted as one included) or insights (read off the post's insights by the account's phone: every other slideshow, and every picture). An insights post with no views_read_at has not been read yet: its views, 0 in a list and null on get_post_analytics, mean not known yet rather than zero views (the app shows a ? there). Its likes and comments come from the hourly read like a reel's.
  • •get_post_analytics (platform tiktok or instagram, video_id the TikTok video id or the Instagram shortcode) is one post's page: its counts with their daily history and its audience (gender, age from 18 (no age under 18 is ever served), top locations, new vs returning viewers, followers vs not, watch time, finish rate, and its retention curve: the percent still watching at each second, with TikTok's average share watched and the moment most viewers stopped, or Instagram's skip rate) and its engagement (the likes, comments, shares, saves and Instagram's reposts as the app counts them; when in the video viewers liked it, at each second, on the app's own scale: TikTok's chart runs to 100% at its most liked moment, Instagram's gives each moment's share of the likes; TikTok's most liked moment and the words its comments use most; the like, comment, share, save and repost rates, Instagram's own, or for TikTok, which prints none, the read's counts over its views with rates_from_counts true), and retention_moments, the percent still watching at 5 s, 10 s, halfway and the end with the skip rate, as list_post_analytics gives them, read by the account's phone off TikTok's Video analytics or Instagram's reel insights, with the screenshots behind each read. A post is first read once it has 200 views, at any age (or three days after posting with 100), and again as it passes 1,000, 5,000, 10,000 views and every doubling after, at most ten posts a day per account; next_read says when. list_post_analytics (platform all, tiktok or instagram, days 1-365 (default 30), sort newest, retention_5s, retention_end, skip_rate, like_rate, comment_rate, share_rate or save_rate, limit 1-100) lists the posts with a read, each read at a glance: the percent still watching at 5 s, 10 s, halfway and at the end (one second before it on a post up to 20 seconds long, the curve's second-to-last measured point on a longer one, since TikTok's chart falls to about zero in its last second), the skip rate (the app's own where it prints one, Instagram; otherwise the share gone in the first 3 seconds of the curve, with skip_rate_from_curve true), the share watched and the finish rate, and its engagement: the likes, comments, shares, saves and (Instagram) reposts the read counted, the share of viewers who did each (Instagram's own rates; TikTok prints none, so there each is the read's count over its views, with rates_from_counts true) and TikTok's most liked moment. Each post also carries the listing's own likes, comments, shares and saves. These are the Retention and Engagement columns of All Videos. Each row's retention is left empty there (get_post_analytics has the curve), and total says how many matched. get_account_audience is the account's own viewers (TikTok Studio, 28 days) and followers breakdowns, read about once a week, with its latest daily health checks' screenshots; both apps show a followers breakdown only from 100 followers. get_audience_summary (segment viewers or followers, platform all, tiktok or instagram) is the workspace's audience across all its accounts on Pamba phones, each account counted by its size: viewers are TikTok Studio's (28 days) plus Instagram reels read in the last 28 days, followers are each app's followers breakdown; it lists the accounts folded in and when each was read, and is rebuilt as reads land. These are the platforms' own estimates, as printed.
  • •The numbers are snapshots and the surfaces end their windows and total differently, so do not expect them to reconcile to the view. The summary tool and the app's dashboard read the same daily rows, each dated on its workspace's calendar; the summary ends its window on today in UTC, the dashboard on today in the viewer's timezone. get_tracked_account_metrics dates its per-day series on the workspace's calendar too. The dashboard sums daily deltas, while per-account metrics sum lifetime view counts. A TikTok video's first snapshot carries everything it earned before Pamba first read it, and those counts land on the day the video was posted when Pamba first read it within 90 days of posting; a video it starts tracking later adds only its later growth. On every platform, the views a reading finds two or more days after a post's previous one (an account not read while its avatar was inactive, a scrape outage) are spread evenly over those days rather than landing on the day the reads resumed. Accounts assigned to a deactivated avatar keep posting and accruing views but do not appear in avatar-scoped dashboards. A TikTok rename keeps one account row: the hourly refresh follows the account through its stable id (or one of its own videos) and moves the row to the new handle, so both handles' videos stay together; a rename it cannot follow leaves the old row failing until it is reconciled by hand. 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, what_they_do and name instead when there is no site (without what_they_do the brand stays unconfirmed and the result says how to finish it). 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 posts into the user's private support conversation with the Pamba team (up to five thousand characters, plus up to four public image links in screenshot_urls, PNG, JPEG or WebP, up to ten megabytes each); a user can send ten messages a minute and a hundred a day across every door, emails to support or to a team member included, and upload fifty screenshots a day. A contact_email stays on the conversation for later replies until another replaces it, and the result names the address replies go to. The team answers in the app's Support tab and by email, and get_support_messages reads the conversation: who wrote each message, the text, screenshot links valid for an hour, and unread_count. Reading the newest page counts as the user seeing the replies, so the reply email is skipped. Over this transport no chat transcript is shared with the team, so put the context in the message. 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. That includes the key create_api_key makes with key_workspace_id for a workspace the connection's key created: it lands in the transcript too, so it suits a system integration rather than a person's own connection.
  • •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 workspace API key resolves to one user in one workspace, where workspace-bound tools run. It makes keys only there and in the workspaces it created itself (create_api_key with key_workspace_id); a chat app's connection makes keys only in the workspace the call runs in. What acts on the person reaches past the key's workspace, as it does in the app: list_api_keys and revoke_api_key cover their keys everywhere, and delete_workspace and the REST /workspaces/{workspaceId}/… routes (settings, members, invitations) act on any workspace they administer. An OAuth token resolves to one user and an explicit set of approved workspaces; each tool call resolves one approved workspace and verifies current membership before execution. 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. Revoking a key leaves the keys it made working, and the new key cannot make keys for the workspaces the old one created: keep a key made inside each of those, or make one later in that workspace's settings.
  • •If a key leaks, revoke it, then clear what it made, since revoking removes none of it. Keys: GET /api-keys lists every key you own with its workspace_id and when it was made and last used (list_api_keys shows the fifty newest and counts them all; a workspace's settings list only that workspace's keys). Revoke each one made since the leak, judging by date rather than name, since a key's name is whatever its maker typed, or revoke them all and make new ones. Members: a key can invite people into any workspace you administer, and an invitation link admits whoever accepts it, so check the members and pending invitations of each of those in its settings in the app, and remove what you did not add (over MCP, list_workspace_members and list_workspace_invitations read only the workspace the connection runs in); check the key's own workspace's share links too (list_share_links). Workspaces: GET /workspaces lists every one you belong to with its owner (list_workspaces shows twenty and counts them all); delete any you own and do not recognize, and ask the admins of one you do not own to remove you. A leaked key could also have revoked your other keys or deleted a workspace you administer, never your default one.

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.
Was this helpful?

Still stuck? Message the team