ClipTaps for developers & AI agents
ClipTaps is drivable programmatically. Developers use the REST API to generate and publish faceless short-form videos — and, on paid plans, to render with a recurring AI persona. A machine-readable plan catalogue, a model catalogue and an A2A agent card make pricing and capabilities discoverable without a human. The OpenAPI spec is served at /api/docs.json (Swagger UI at /api/docs). An MCP server is planned — see Roadmap.
Authentication
Authenticated requests use a Bearer token. Create a long-lived API key under Settings → API Keys (recommended for scripts and agents) — or use the short-lived session JWT the web app issues. A key acts as your account, so plan quotas and capability gates apply unchanged. You may hold up to 10 active keys, each with an optional expiry of 1–730 days; the plaintext key is shown once. Keys are minted and revoked only from a logged-in browser session — an API key calling those endpoints gets 403. Public endpoints — the plan catalogue, the model catalogue, the stock voice list and the agent card — need no auth. OAuth 2.1 is on the roadmap.
Authorization: Bearer ct_live_… # API key (valid until revoked or expired)
POST /api/api-keys { "name": "ci", "expiresInDays": 90 } # session JWT only → 201, key shown once
GET /api/api-keys # masked list (key or JWT)
DELETE /api/api-keys/{id} # session JWT only
# Error envelope on every 4xx/5xx from the API:
# { "message": "…", "code": "PLAN_UPGRADE_REQUIRED", ...details } # code + details only when presentPlans & entitlements
Every tier’s limits come from one source of truth. Each plan defines videos per period (Free resets daily; paid tiers reset monthly), a scene cap per video, whether output is watermarked, three capability flags — socialPublishing, premiumVideoModels, aiPersonas — and whether an AI agent may purchase it. Read the live catalogue:
GET https://cliptaps.com/api/payments/plans # public, no auth
# → { "plans": [ { "id": "free", "tier": "FREE", "label": "Free", "priceUsd": 0,
# "currency": "usd", "interval": null, "period": "day", "videosPerPeriod": 1,
# "maxScenesPerVideo": 3, "watermark": true, "socialPublishing": false,
# "premiumVideoModels": false, "aiPersonas": false, "agentPurchasable": false,
# "stripePriceId": null }, ... ] }| Tier | Videos | Scenes | Watermark | Social publishing | Paid-only models | AI personas | Agent buy |
|---|---|---|---|---|---|---|---|
| Free | 1 / day | 3 | Yes | No | No | No | No |
| Starter | 10 / mo | 5 | No | Yes | Yes | Yes | Yes |
| Pro | 30 / mo | 10 | No | Yes | Yes | Yes | Yes |
| Enterprise | 50 / mo | 50 | No | Yes | Yes | Yes | No |
A paid feature the plan lacks is refused with 403 and a machine-readable code, so a client branches on code and never on message text. A lapsed Stripe subscription (cancelled / unpaid / incomplete) falls back to Free; a past-due one keeps its video limits and paid-only models but loses social publishing.
# 403 on any gated endpoint:
{ "message": "AI personas are available on paid plans. Upgrade to Starter, Pro or Enterprise …",
"code": "PLAN_UPGRADE_REQUIRED",
"capability": "personas", # or "socialPublishing" | "premiumVideoModels"
"upgradeUrl": "/settings?tab=subscription",
"modelLabel": "Kling 2.6" } # only for premiumVideoModelsQuota & usage
Check how much of the current window is left, and which capabilities the account holds right now (these reflect the entitled plan, not the tier string on the row). Generation is charged one video credit per project — re-generating the same project is free, and mock runs are free — and enforced server-side; when the window is exhausted, generation returns 402.
GET https://cliptaps.com/api/payments/status # auth
# → { "tier": "PRO", "status": "active", "currentPeriodEnd": "2026-10-01T00:00:00.000Z",
# "videoCreditsUsed": 4, "videoCreditsLimit": 30, "videoCreditsRemaining": 26,
# "quotaResetAt": "2026-10-01T00:00:00.000Z", "quotaPeriod": "month",
# "maxScenesPerVideo": 10, "watermark": false,
# "socialPublishing": true, "premiumVideoModels": true, "aiPersonas": true }
# 402 from POST /api/generate/video-start when exhausted (message only, no fields):
# { "message": "Monthly video limit reached (30/30). Resets 2026-10-01. Upgrade your plan for more videos." }Video models & cost
One catalogue drives the UI selectors, the API validation and the render workers. Read it to build your own model picker; every model lists its modes, durations, supportedAspects, resolutionsByDuration and pricing. Models flagged paidOnly need a paid plan (see the 403 above); speaksScript marks a model that voices the narration itself; refusesRealFaces marks one that renders from text only and rejects face pictures.
GET https://cliptaps.com/api/video/models # public, no auth
# → { "version": 16, "defaultProvider": "veo",
# "providers": [ { "id", "label", "models": [ { "id", "label", "modes", "durations",
# "resolutionsByDuration", "supportedAspects", "pricing", "paidOnly"?, "speaksScript"?, "refusesRealFaces"? } ] } ] }Catalogue v16, rendered from the same file the API serves. Prices are the catalogue’s pricing values (per second of output unless noted). The last column is how a talking / interview persona keeps one face on that model.
| Provider / model | Plan | Pricing | Durations · aspects | Flags | Persona face |
|---|---|---|---|---|---|
| Google Veo 3.1 veo / veo-3.1-fast-generate-preview Veo 3.1 Fast | all | $0.1 (720p) · $0.12 (1080p) · $0.3 (4k) / s | 4–8 s 16:9, 9:16 | default | Reference images: carrier + wardrobe / sheet ¾ + location (≤ 3) |
| Google Veo 3.1 veo / veo-3.1-lite-generate-preview Veo 3.1 Lite | all | $0.05 (720p) · $0.08 (1080p) / s | 4–8 s 16:9, 9:16 | — | Image-to-video from the carrier |
| Google Veo 3.1 veo / veo-3.1-generate-preview Veo 3.1 Standard | all | $0.4 (720p, 1080p) · $0.6 (4k) / s · with references $0.4 (720p, 1080p) · $0.6 (4k) / s | 4–8 s 16:9, 9:16 | — | Reference images: carrier + wardrobe / sheet ¾ + location (≤ 3) |
| MiniMax Hailuo hailuo / MiniMax-H3 MiniMax H3 — T2V/I2V/FL2V/references | all | $0.08 (720p, 768p) · $0.13 (1080p, 2k, 4k) / s | 4–15 s 16:9, 9:16, 1:1 | default | Reference images: carrier + wardrobe / sheet ¾ + location (≤ 3) |
| MiniMax Hailuo hailuo / MiniMax-Hailuo-2.3 Hailuo 2.3 — T2V/I2V | all | $0.28 – $0.49 / video | 6–10 s 16:9, 9:16, 1:1 | — | Image-to-video from the carrier |
| MiniMax Hailuo hailuo / MiniMax-Hailuo-2.3-Fast Hailuo 2.3 Fast — I2V only | all | $0.19 – $0.33 / video | 6–10 s 16:9, 9:16, 1:1 | — | Image-to-video from the carrier |
| MiniMax Hailuo hailuo / MiniMax-Hailuo-02 Hailuo 02 — T2V/I2V/FL2V | all | $0.1 – $0.56 / video | 6–10 s 16:9, 9:16, 1:1 | — | Image-to-video from the carrier |
| MiniMax Hailuo hailuo / S2V-01 S2V-01 — subject-reference | all | no catalogue price | 6–6 s (fixed) 16:9 | fixedDuration | Cannot hold a face → 422 for talking / interview |
| Kling AI kling / kling-2.6 Kling 2.6 | paid | $0.042 (720p) · $0.07 (1080p) / s | 5–10 s 16:9, 9:16 | default, paidOnly | Image-to-video from the carrier |
| Kling AI kling / kling-3.0 Kling 3.0 | paid | $0.084 (720p) · $0.112 (1080p) / s | 4–15 s 16:9, 9:16 | paidOnly | Image-to-video from the carrier |
| Kling AI kling / kling-avatar Kling Avatar — talking head | paid | $0.056 (720p) · $0.112 (1080p) / s | 4–15 s 16:9, 9:16, 1:1 | paidOnly, speaksScript | Carrier still + narration audio — the model lip-syncs the line |
| ByteDance Seedance seedance / dreamina-seedance-2-5-260628 Seedance 2.5 | paid | $0.231 (720p) / s | 4–15 s 16:9, 9:16 | default, paidOnly, refusesRealFaces | Cannot hold a face → 422 for talking / interview |
| Image scenes (Gemini 3.1 Flash Lite Image) image / gemini-3.1-flash-lite-image Gemini 3.1 Flash Lite Image | all | $0.0336 / still (one per scene) | 4–12 s 16:9, 9:16, 1:1 | default | Cannot hold a face → 422 for talking / interview |
Set the model on the project with PATCH /api/projects/{id}/settings { "video_provider": "kling", "video_model": "kling-2.6" }. A model that cannot produce the project’s video_format is refused with 400 before any credit is charged.
Generate & publish a video
Generation is a short pipeline. Create a project (it must belong to the caller — 401 without a bearer), set its settings, attach source text, build the storyboard (which writes the scenes and, on a persona project, runs the sensitive-script guardrail), queue the scenes and poll until they complete, then assemble the final video and publish it.
POST /api/projects → 201 { "projectId" }
PATCH /api/projects/{projectId}/settings { "video_format": "9:16", "video_quality": "720p",
"num_scenes": 5, "scene_duration": 6,
"video_provider": "veo", "video_model": "veo-3.1-fast-generate-preview" }
# partial merge — send only the keys you change
POST /api/projects/{projectId}/source-data/text { "text_prompt": "..." }
POST /api/projects/{projectId}/storyboard/script { "num_scenes": 5, "scene_duration": 6, "use_mock": false }
→ { scenes: [{ id, orderIndex, scriptText, promptVisual, promptAction, promptAudio }],
meta, sceneClamp?, durationSnap?, guardrail? }
# 422 PERSONA_GUARDRAIL_BLOCKED on a persona project
POST /api/generate/video-start { "sceneIds": ["…","…"], "use_mock": false }
→ 202 { "status": "QUEUED", "queuedCount": 5 }
# use_mock defaults to false; true renders placeholders and
# charges no credit. 400 aspect · 403 PLAN_UPGRADE_REQUIRED ·
# 422 PERSONA_IDENTITY_UNAVAILABLE · 402 quota — all before the credit
GET /api/generate/video-status/{projectId} → { "status", "scenes": [{ id, status, videoUrl, thumbnailUrl, error }] }
POST /api/generate/video-stop/{projectId} # cancel queued scenes
POST /api/projects/{projectId}/assemble { "selectedSceneIds": ["…"], "tts": { "enabled": true },
"music": { "enabled": true }, "hookText": "…" } → 202
GET /api/projects/{projectId}/assemble/status → { "status": "IDLE|PROCESSING|COMPLETED|FAILED", "finalVideoUrl", "error", "voiceFallbackScenes" }
POST /api/posting/create { "projectId", "platform": "tiktok|instagram|youtube",
"mode": "manual|auto", "title", "description",
"hashtags"?, "scheduledTime"?, "accountId"?,
"platformSettings"?: { "tiktokSettings": { "privacy_level", "express_consent_given": true, "content_preview_confirmed": true } } }
# paid plans only (403 PLAN_UPGRADE_REQUIRED · socialPublishing)GET /api/projects lists your projects with status, title, thumbnailUrl, finalVideoUrl and per-scene status. A title is required to publish; TikTok additionally requires a privacy level and both consent flags. Connected accounts come from GET /api/social/accounts. When the project renders a photoreal persona, publishing appends the AI-disclosure line to the description and sets the platform’s AI-content label (see below) regardless of what the request sends.
AI personas
A persona is a recurring on-camera character with a locked look and a voice. Every /api/personas endpoint except GET /api/personas/niches is behind the plan gate (403 PLAN_UPGRADE_REQUIRED, capability personas). Reads, updates and deletes are owner-scoped: another account’s persona answers 404. Up to 50 personas per account. Health, finance, legal and political niches are marked sensitive because YouTube will not monetise an AI persona presenting as an expert on them; the niche catalogue returns the policy text.
1. Create the profile
GET /api/personas/niches → { niches, sensitive, policy: { url, heading, quote, examples } }
POST /api/personas { "name": "Mara", "age": 34, "niche": "nature",
"backstory"?, "usp"?, "pov"?, "seriesFormats"?: ["Five levels of …"],
"signatureMoves"?: [{ "name", "description", "maxPerVideo", "allowedBeats" }],
"cameraStyle"?: "propped|handheld_selfie|placement_open|companion|vehicle",
"imageQuality"?: "clean_flagship|retro_phone",
"forbidden"?: [...], "photoreal"?: true,
"voiceNotes"?, "stockVoice"? } → 201 PersonaDTO (409 at the limit)
GET /api/personas · GET|PUT|DELETE /api/personas/{id}
PUT /api/personas/{id}/references { "references": [{ "url", "title"?, "platform"?, "views"? }] } # ≤ 30, proven-demand refs
DELETE /api/personas/{id}/references/{referenceId}2. Lock the look
Render portrait candidates until one is right, approve it — that locks the persona’s identityAnchors — then optionally render the three-angle sheet, wardrobe variants and locations from the locked portrait. Each render is billed per still from the image model and counted against a per-persona image budget (402 when reached).
GET /api/personas/{id}/budget → { spentUsd, capUsd, perImageUsd }
POST /api/personas/{id}/portrait { "appearance": "…10–2000 chars…" } → 201 { candidateId, url, prompt, mock, costUsd }
POST /api/personas/{id}/portrait/approve { "candidateId" } → PersonaDTO (409 if already locked)
POST /api/personas/{id}/sheet → 201 { persona, assets: [frontal, front_three_quarter, side_profile] } (400 without a locked portrait)
POST /api/personas/{id}/wardrobe { "outfit": "a camel wool coat over a black turtleneck" } → 201 { persona, asset }
POST /api/personas/{id}/locations { "description": "a cluttered Georgian drawing room at dusk" } → 201
POST /api/personas/{id}/locations/upload multipart file → 201
DELETE /api/personas/{id}/wardrobe/{assetId} · DELETE /api/personas/{id}/locations/{assetId}3. Give it a voice
POST /api/personas/{id}/voice multipart: file=<mp3|wav|m4a clip>, consent=true, voiceNotes?
# 400 VOICE_CONSENT_REQUIRED without consent=true
DELETE /api/personas/{id}/voice
# or a stock voice instead of a clone: PUT /api/personas/{id} { …, "stockVoice": "<id from GET /api/tts/voices>" }4. Link a project and pick the format
Linking copies the persona’s voice into the project’s TTS settings and stamps the render defaults (captions on; music off for the spoken formats). Formats: talking (direct to camera), interview (eyeline on an off-camera interviewer; the first line is their question) and broll (silent b-roll with upper-third text). Default talking. The storyboard then writes the persona’s own lines, and the hook stage produces variants you can choose between.
PATCH /api/projects/{projectId}/settings { "personaId": "<persona id>", "personaFormat": "talking",
"seriesFormat"?: "Five levels of …" } # personaId: null unlinks
POST /api/projects/{projectId}/storyboard/script # runs the sensitive-script guardrail
→ { …, "guardrail": { "verdict": "accept|rewrite", "reasons", "flagged_sentences", "rewritten" } }
# 422 PERSONA_GUARDRAIL_BLOCKED when a first-person expert line still fails after one third-person rewrite
POST /api/projects/{projectId}/hook/select { "index"?: 1, "text_hook"?: "…≤200 chars" }
→ { projectId, selected, selected_index }
# 400 PERSONA_HOOK_TEXT_MISMATCH: on a persona project the burned text must be the persona's
# spoken line — after normalisation, equal to it, its opening words, or an ordered subset5. The identity-carrier rule
A talking or interview video must show one face in every scene, so POST /api/generate/video-start checks two things before charging the credit: the model can hold a face across scenes, and something carries that face. The carrier is resolved in this order — locked portrait → sheet frontal → the project’s attached start image → its first reference image → an identity frame the worker synthesizes once per project. A model flagged refusesRealFaces renders from text only, so it is refused for spoken formats outright, as is any model with no picture input; the Persona face column of the model table above shows the route per model. B-roll is never refused.
# 422 from POST /api/generate/video-start
{ "message": "<model> cannot hold Mara's face: it renders every scene from text, so each scene would show a different person. Pick … on the model step, or lock Mara's look on the Personas page …",
"code": "PERSONA_IDENTITY_UNAVAILABLE",
"reason": "model_cannot_hold_identity", # or "no_identity_carrier"
"modelLabel": "<model label>", "personaName": "Mara" }6. AI disclosure on publish
A persona that is photoreal (or whose disclosure is always) is always disclosed. Assembly burns the footer “AI character — synthetic video” into the safe zone; POST /api/posting/create and POST /api/zernio/publish append “This video features an AI-generated character. #AIcharacter” to the description and force the platform label on — TikTok tiktokSettings.video_made_with_ai, YouTube containsSyntheticMedia, Instagram isAiGenerated. A request that sets them false is overridden. Faceless projects are untouched.
7. Measure it
GET /api/analytics/personas
# → { "personas": [{ key, personaId, name, niche, posts, scoredPosts, totalViews, medianViews,
# avgCompletion, totalShares, likeRate, topHookType, registers: { talking, interview, broll, faceless } }],
# "total_posts": 42 } # one row per persona (zero posts included) + the "faceless" bucket
GET /api/analytics/posts?personaId=all|faceless|<persona id> # 400 on anything else
# → { "posts": [{ postingId, projectId, platform, status, title, postUrl, persona: { id, name, niche } | null,
# hookType, register, metrics: { views, likes, comments, shares, engagementRate, completionRate } | null,
# metricsSyncStatus, metricsSyncedAt }], "personaId": "all" }
GET /api/analytics/history?personaId=<id> # the activity log, filtered the same wayError codes
| Status | code | Meaning and extra fields |
|---|---|---|
| 402 | — | Video quota exhausted for the plan window. Bare `{ message }`. |
| 403 | PLAN_UPGRADE_REQUIRED | Feature not on the plan. `capability`: socialPublishing | premiumVideoModels | personas; `upgradeUrl`; `modelLabel` for a paid-only model. |
| 422 | PERSONA_IDENTITY_UNAVAILABLE | Talking / interview persona project cannot hold one face on this model. `reason`: model_cannot_hold_identity | no_identity_carrier; `modelLabel`; `personaName`. |
| 422 | PERSONA_GUARDRAIL_BLOCKED | Persona script gives first-person expert advice on a sensitive topic and still failed after one rewrite. `reasons`, `flagged_sentences`, `policy_quote`, `scenes`. |
| 400 | PERSONA_HOOK_TEXT_MISMATCH | On-screen hook is not the persona’s spoken line. `text_hook`, `spoken_hook`. |
| 400 | VOICE_CONSENT_REQUIRED | Voice reference upload without `consent=true`. |
| 402 / 409 | — | Persona look: image budget reached (402); portrait already locked (409). |
| 400 | — | Validation error (`{ message, details }`), TikTok consent missing, or the chosen model cannot produce the project’s aspect ratio. |
| 401 | — | Missing, expired or revoked bearer credential. |
| 404 | — | Not found — including another account’s project or persona (never 403). |
Agent card (A2A)
For agent-to-agent discovery, ClipTaps publishes an A2A agent card describing its skills, its bearer security scheme, the public endpoints, the plan and model facts and the error codes above. It is discovery-only: the card carries status: "planned" and no JSON-RPC url, because the A2A execution endpoint is not live yet — drive ClipTaps through the REST API it describes.
GET https://cliptaps.com/.well-known/agent-card.json # alias: /.well-known/agent.json GET https://cliptaps.com/llms.txt # plain-text summary for LLM crawlers
Roadmap
- •MCP server: planned. The
@cliptaps/mcp-serverpackage is not published to npm —npx @cliptaps/mcp-serverdoes not work. Its source lives in the ClipTaps repository (orchestration/mcp, a private package, stdio transport, dev-grade) and can be run from a checkout; the tools it exposes today are listed below. - •A2A execution endpoint — the JSON-RPC
urlthe agent card will point at once tasks can be delegated directly. - •Per-key rate limits & scopes — per-tier throttling and scoped keys (API keys themselves are available now under Settings → API Keys).
- •Agent action tools —
create_project,start_generation,publish_to_socialas MCP tools (the REST endpoints behind them are live). - •Delegated purchases — real
purchase_planvia scoped mandates for agent-purchasable tiers. - •OAuth 2.1 — scoped, short-lived tokens with audience validation.
Running the in-repo MCP server from a checkout (not an install):
cd content_factory/orchestration/mcp && npm install && npm run build CLIPTAPS_API_BASE=https://cliptaps.com/api \ CLIPTAPS_API_KEY=ct_live_… \ node dist/index.js # stdio transport; register this command with your MCP client
| Tool | Backed by |
|---|---|
| list_plans | GET /api/payments/plans — tariff catalogue (public) |
| get_subscription_status | GET /api/payments/status — entitled plan, credits, reset, capability flags |
| list_projects | GET /api/projects — the user’s projects with status, thumbnail, finalVideoUrl, per-scene status |
| get_generation_status | GET /api/generate/video-status/{projectId} — project + per-scene status |
| get_analytics | GET /api/analytics/dashboard — videos created, minutes, posts per platform |
| create_checkout | POST /api/payments/checkout — Stripe Checkout URL for a human to subscribe |
| purchase_plan | Delegated agent purchase — stub, returns guidance until the mandate flow ships |
Changelog
- 2026-09-25A partial PATCH /api/projects/{id}/settings no longer resets the video provider to Veo — request-body defaults were removed, so send only the keys you want changed.
- 2026-09-25One identity carrier per persona video: the locked portrait, the sheet, the project’s attached picture or one synthesized frame holds the face across every scene; talking / interview projects that cannot are refused with 422 PERSONA_IDENTITY_UNAVAILABLE before the credit is charged.
- 2026-09-24Persona follow-ups on the speaking-model and guardrail paths; brainstorm retries a truncated idea instead of returning a placeholder.
- 2026-09-23AI personas (paid plans): persona profiles, locked look (portrait, angle sheet, wardrobe, locations), voice clone with consent or a stock voice, talking / interview / b-roll formats, series formats and proven-demand reference collections.
- 2026-09-23Persona analytics: GET /api/analytics/personas and GET /api/analytics/posts?personaId= report views, completion, shares, like rate, top hook type and register per persona.
- 2026-09-23AI disclosure: every photoreal persona video carries a burned footer, a description line and the platform AI-content label on publish; a client cannot switch them off.
- 2026-09-23Sensitive-script guardrail: persona scripts that give first-person expert advice on health, finance, legal or political topics are rewritten to third person or refused with 422 PERSONA_GUARDRAIL_BLOCKED; the burned hook must be the persona’s own line (400 PERSONA_HOOK_TEXT_MISMATCH).
- 2026-09-23Kling Avatar — a paid-only talking-head model that speaks the narration itself; word-level karaoke captions for cloned and Kokoro voices.
- 2026-09-23Kling 2.6, Kling 3.0 and Seedance 2.5 as paid-only video models; every plan refusal now carries the machine-readable code PLAN_UPGRADE_REQUIRED.
- 2026-09-19Social publishing (POST /api/posting/create, /api/social/*, /api/zernio/*) restricted to paid plans.
See also: FAQ · Use cases · Pricing · OpenAPI (Swagger)