Creator Harbor

API reference

Creator Harbor makes vertical (9:16) UGC-style video ads: an AI actor talks to a phone camera about a product, with captions burned in. Give it a product page link; it writes the script, renders the video and returns an MP4.

Pricing: per second of video by engine, 1 credit = $0.01: natural 45 credits (a 10 s ad is 450), precise 15 credits (a 10 s ad is 150), seedance 40 credits (a 10 s ad is 400). Credits are charged when an ad starts and refunded automatically if it fails; only failures are refunded, not results someone doesn't like. New accounts get 400 free credits for a first ad once they confirm their email address (not for throwaway inboxes); free takes are a watermarked preview on the precise engine and don't represent the quality of paid ads (natural engine). More credits come in packs bought by the user at creatorharbor.app/app/account ($10 = 1,000, $29 = 3,000, $79 = 8,500, $199 = 22,000; they never expire); agents can't buy them.

Auth

Two options:

  • OAuth 2.1 (for MCP clients). Discovery starts from the 401 on creatorharbor.app/api/mcp (RFC 9728 metadata at creatorharbor.app/.well-known/oauth-protected-resource/api/mcp). Client ID Metadata Documents and dynamic registration are both supported. The user signs in and approves on creatorharbor.app. Scopes beyond reading and the free tools: spend (make_ad, make_ads_batch, make_static_ads, make_short, make_clips, translate_video, run_story_step, approve_short, discard_short, retry_video), post (post_video, cancel_post, reschedule_post, share_video), series (create_series, update_series, create_autopilot, update_autopilot); a tool outside the sign-in's scopes fails with scope_missing; ask for none and you get all, the user approving. The access token also works as a Bearer for GET on /api/v1 (video_url, download_url and the like), not for writes.
  • API keys (for scripts and headless agents). Create one at creatorharbor.app/app/keys and send Authorization: Bearer <key>. Works for MCP and REST.

MCP

Remote MCP server (Streamable HTTP, 2025 and 2026-07-28 protocols): creatorharbor.app/api/mcp

Claude Code: claude mcp add --transport http creatorharbor https://creatorharbor.app/api/mcp, then run /mcp to sign in. Headless: add --header "Authorization: Bearer <key>".

Tools: list_options, quote (any paid job's price: kind ad, ads_batch, short, clips, translate, story_step or statics, with that tool's args), quote_ad, draft_script (free: the words to agree on before paying, optionally to a template), make_ad, make_ads_batch (variants to test), get_ad (wait=true long-polls), list_ads, make_static_ads, get_static_ads, draft_short (free: a Short's plan), make_short, get_short, list_shorts, redo_short_picture, approve_short, discard_short, retry_video, share_video, get_brand_kit, set_brand_color, create_story, draft_story_concepts, pick_story_concept, run_story_step, get_story, list_stories, create_series, list_series, update_series, create_autopilot, list_autopilots, update_autopilot, find_trending_shorts, explain_video, save_video, list_saved_videos, explain_saved_video, import_video, get_import, make_clips, get_clips, edit_clip (re-edit a clip by plain instruction, free), get_clip_edit, translate_video, list_social_accounts, post_video, list_posts, cancel_post, reschedule_post, open_upload_panel (hosts with MCP Apps: the user picks a local MP4/MOV and it uploads, free; the panel tells you the key), get_uploaded_video (did an upload finish, its duration and clip quote), preview_image (a small private picture of your own ad, Short scene or clip, returned as an image), rate_video (good or bad, and what was off: we read ratings when we review models and fixes), get_balance.

The flow (also in the server's instructions): list_options, then a free draft (draft_script, draft_short), then quote and tell the user the price; create with expected_credits set to the quoted credits (a changed price fails with price_changed, never charged) and a request_id; poll get_* with wait=true; give the user video_url. Tool errors carry the same JSON as REST errors, { "error": { "code", "message" } } (codes below, plus scope_missing and internal).

REST API

Machine-readable OpenAPI 3.1 specification: creatorharbor.app/openapi.json (public, no key needed). Import it into an API client or use it to generate a client. It documents the REST endpoints below, including upload parts and file downloads. MCP tools have their own schemas on the MCP server.

  • GET/api/v1/catalog: actors, formats, caption styles, lengths and price. No key needed.
  • GET/api/v1/me: { credits, watermarked, rendering_open }
  • GET/api/v1/me/credits: { credits, history: [{ at, amount, what }] }, the last 100 entries newest first: each render (negative), refund, pack and gift
  • POST/api/v1/scripts: draft the script from the page, free (limited per hour), to show the user before paying. Body: product_url, product_name, actor, format, duration_s (as below). Returns { hook, script, words, fits_words: [min, max], duration_s, from_page, policy_warnings: [{ phrase, why }] }; from_page false means the page couldn't be read and the script came from product_name. policy_warnings: phrases Meta, TikTok and YouTube usually reject (cures, guarantees, weight-loss or income claims, personal attributes); show them and offer a rewrite. Pass the agreed text as script below.Hooks to test: add hooks: 2 or 3 (and optionally your own script) to also get alt_hooks, other first sentences, and scripts, each full script with one. Make one ad per script (and per actor) and compare.
  • POST/api/v1/ads: start an ad. JSON body:
    • product_url (required): the product page
    • product_name: optional; without it the finished ad is named after the product as read from its page (the domain until then)
    • actor: maya | dan | renee | tom | wei | ama | helen | callum | priya | marcus | sofia | kenji | grace | omar | zoe | jamal | linh | frank | aisha | diego | emma | raj | nia | lucas, or one of your own (my-…) from GET creatorharbor.app/api/v1/actors (default maya)
    • voice: one of your own cloned voices (my-voice-…, in GET creatorharbor.app/api/v1/actors as voices), spoken on the precise engine with the actor lip-synced to it, in any language (omit for the actor's voice)
    • format: selfie | product | unboxing | review | problem | app (default selfie)
    • duration_s: 5 | 10 | 15 | 20 | 30 (default 10). 20 and 30 s ads are cut from several shots of the actor (a 3-second hook, the middle, the call to action, each its own take, cut where the words end): natural engine and paid accounts only. They're priced at 55 credits a second of what the video model bills, every shot at least 5 s (20 s: 1210 credits, 30 s: 1760 credits); a new hook on one (only its opening shot made again) is 275
    • captions: hormozi | minimal | box | yellow | karaoke | marker | pop | neon | subtle | fire (default hormozi)
    • engine: natural | precise | seedance (default natural). natural moves like a real person, but the model sets the timing and voice; precise says the script word for word in the actor's fixed voice and ends cleanly, with less body movement; seedance makes a new person each take from the actor's description, not their photo (English, stock actors only, not format app)
    • language: en (English) | es (Spanish) | pt (Portuguese) | fr (French) | de (German) | it (Italian) | nl (Dutch) | pl (Polish) | tr (Turkish) | sv (Swedish) | id (Indonesian) | ro (Romanian) | ru (Russian) | uk (Ukrainian) | vi (Vietnamese) | ja (Japanese) | ko (Korean) | zh (Chinese (Simplified)) | hi (Hindi) | ar (Arabic) | th (Thai) (default en): the script, voice and captions. Other languages than English use the precise engine (the default for them; natural is refused)
    • ai_label: true | false (default true): a small "AI-generated" label in a top corner, in the ad's language, for places that require one on ads with AI actors (e.g. New York)
    • product_image_url: https link to a product photo, or for format app a screenshot of the app (optional; otherwise the page's own image)
    • script: exact words to say (optional; otherwise written from the page)
    • say_as: how to say tricky words, e.g. {"Hatoba": "Ha-TOE-ba"} (up to 5). Only the voice hears the respelling; captions keep the real spelling
    • music: none | upbeat | chill | warm | tech | luxe (default none): a music bed under the speech, quieter while the actor talks, made for the ad by an AI music model (Lyria 3), never a scraped track; 11 credits more. If it can't be made, the ad comes without it and those credits are refunded (refunded_credits)
    • lower_half_video: your video's key (from the video upload under Clips below, or imported from a link), with lower_half_rights: true to confirm you own it or have the rights to use it: B-roll or a screen recording fills the bottom half under the talking actor, captions on the seam (the split look); whole over a blurred fill, looped if shorter, its sound left out. Free
    • webhook_url: optional https URL that gets a POST when the ad finishes (see Webhooks)
    • template: offer_demo | problem_demo | letter | asmr | unboxing | before_after | three_reasons | comparison | founder (optional): a proven ad structure (GET /api/v1/catalog templates lists each with its format, captions, hook type and questions). Without a script, the one written follows it; draft it first with POST /api/v1/scripts (same template and answers) to read it free
    • answers: { question id: answer } for the template's questions (each optional, up to 200 characters); what's left out comes from the pageSend Idempotency-Key: <your id> to make retries safe: same key and body returns the original ad (200), a different body is 409. It works the same on translate, story steps, series, autopilots and posts (a retry returns what the first try made, charged and posted once); MCP tools that charge or post take it as request_id.expected_credits: the price you showed the user (optional). If it isn't the price now, nothing is charged and the answer is 409 price_changed. Works the same on shorts, clips, translate and story steps, and on the MCP tools that charge.Returns 201 with the ad object.Paid ads are checked before captions, at no cost to you: the words heard against the script, and the picture by an AI watcher (a warped face or hands, garbled writing or logos, the wrong product, a broken picture). A take that fails either gets one more take; one that doesn't say the script twice fails and is refunded. Free previews aren't checked.
  • GET/api/v1/ads/{id}?wait=25: the ad. wait long-polls up to 25 s until it finishes.status: queued | running (step: script | video | captions) | succeeded (hook, hook_type, script, video_url) | failed (error, refunded: true)hook_type: the kind of hook the script opens with, offer | new | price | problem | detail | question (null on ads from before it was recorded)
  • GET/api/v1/ads?limit=20: recent ads, newest first.
  • POST/api/v1/ads/batch: up to 20 variants to test against each other. The body of POST /api/v1/ads with lists for what to vary: actors (1 to 4), hooks (other opening lines, up to 4; they need a script), formats, engines; every actor × hook × format × engine is its own ad. The whole batch is checked and priced before any ad starts, with one balance check; expected_credits is the batch's total. ?dry_run=1 only prices it: { credits, takes, warnings }. Returns 201 { batch, ads: [...], takes, credits, warnings }. warnings say where variants look alike (hooks sharing most of their words, takes differing only in their words, the same actor and hook on several engines): Meta and TikTok treat near-duplicates as one ad, so vary actors and formats, not just the copy. Renders run one at a time per account. Idempotency-Key replays ad by ad (charged once). If the balance runs out partway, the ads already started stay: 201 with them and error. MCP: make_ads_batch (quote kind ads_batch).
  • GET/api/v1/ads/export?batch=<batch> (or ?ids=<id>,<id>, or nothing for the latest): up to 20 finished ads as one ZIP for Meta or TikTok Ads Manager: each ad's MP4s named CH_<date>_<product>_<template or format>_<hook type>_<actor>_<engine>_<length>s_<id> (9:16, plus _4x5 and _1x1 for feeds), and ads-manager.csv with file_name, ad_name, variant_id, batch, product, concept, format, hook_type, hook, actor, engine, duration_s, language, primary_text, headline, call_to_action, website_url, feed_files (primary_text is the script, headline the product's name, call_to_action the end card's).
  • GET/api/v1/ads/{id}/captions: the captions as an SRT file (ads and Shorts made since SRTs were kept; captions_url is null otherwise).
  • GET/api/v1/ads/{id}/video: the MP4 (supports Range). Same Bearer key or MCP access token. ?shape=4:5 or ?shape=1:1: the same ad for feed placements (the whole vertical ad over a blurred fill); the ad's shape_urls lists them.
  • POST/api/v1/ads/{id}/translate { languages: ["es", "de"] }: the finished ad made again in each language (up to 5): same actor and settings, the script translated to fit the length, on the precise engine, at the usual price each. Returns { ads: [...] }.
  • POST/api/v1/statics { ad_id }: static image ads from a finished ad, at 1:1 (1080x1080), 4:5 (1080x1350), 9:16 (1080x1920), in the brand kit: its hook as the headline and its call to action, over its first frame ("photo-…"; the product photo for ads from before frames were kept) and on a brand-colour card ("text-…"). Or on their own: { product_name, headline, cta?, product_image_url? } (no photo: the cards only). 2 credits an image (12 with a picture, 6 without), charged now, refunded if they fail; Idempotency-Key and expected_credits as above. Returns 201 with { id, status, image_urls }.
  • GET/api/v1/statics/{id}?wait=25: status and image_urls by name (e.g. photo-4x5). Made from an ad, they also show on it as static_urls (a remake replaces them).
  • GET/api/v1/statics/{id}/{name}: the JPEG (id: the statics or the ad). ?download saves it.

MCP: make_static_ads, get_static_ads.

Shorts (faceless narrated videos)

A narrator reads a script over AI images or your own photos (one per scene, slowly zooming), or over background footage, with word-timed captions and background music, in 1080p: vertical, square or wide. 5 credits a second (3 over footage); the video lasts as long as the narration (plus a breath).

  • POST/api/v1/shorts/script: free draft from topic, source_url or pictures (+ format, duration_s, speed; with background, the scenes' visuals are left empty). Returns { title, script, scenes: [{ line, visual }], art_direction, music, keywords (words highlighted in the captions), words, fits_words, duration_s }. Edit it and send it back as plan.
  • POST/api/v1/shorts: start a Short. JSON body:
    • topic, source_url (an article or page, a post on X, a Reddit post or a PDF up to 5 MB; Reddit often refuses us, so paste its text if it fails), script (exact narration: a blank line starts a new scene, [pause] is 0.7 s of silence, 10 at most), plan (the draft, edited: { title, art_direction, music, scenes: [{ line, visual }] }, rendered as is) or pictures: at least one
    • pictures: by scene, https links to your own photos (JPEG, PNG or WebP, 5 MB, 8192 px a side), or a finished Short's scenes[n].picture_key to keep that picture; null makes one. Without a plan, the narration is written to go with your photos, in order.
    • aspect: 9:16 | 1:1 | 16:9 (default 9:16), all 1080p
    • speed: 0.9 | 1 | 1.15 | 1.3 (default 1): how fast the narrator reads; faster fits more words (not with a presenter)
    • transition: cut | fade | slide | zoom (default cut): how one scene gives way to the next; whoosh: true adds a soft whoosh under each change
    • language: en | es | pt | fr | de | it | nl | pl | tr | sv | id | ro | ru | uk | vi | ja | ko | zh | hi | ar | th (default en): the narration, voice and captions are in it (picture descriptions stay English)
    • format: free | story | list | facts | explainer | motivation | myth | reddit | quiz | pov | news | scary (default free): story = a first-person story with a twist; list = a countdown, one item a scene; facts = surprising facts, best one last; explainer = how or why, step by step; motivation = short lines that build; myth = myths, each one corrected; reddit = a post read out, with the update; quiz = questions, a beat, then the answer; pov = you're in the scene; news = what happened and why it matters; scary = short horror, a chilling last line
    • headline: true | false: the title across the top for the first 3 seconds (default true)
    • review: true: make the pictures first and wait (step "review") so you can check them; see below (default false)
    • background: pictures (default) | a built-in loop's id | your video's key: background footage under the whole Short instead of a picture a scene (the Reddit-story look), slightly dimmed, captions on top, the title as a white card; 3 credits a second. Built-in loops are our own AI-made footage, listed with a preview_url in GET creatorharbor.app/api/v1/catalog under shorts.backgrounds: only those listed can be used (none listed: none yet). We have no gameplay footage. Your own: a key from the video upload under Clips below (steps 1-3, or imported from a link), with footage_rights: true to confirm you own it or have the rights to use it; it starts at a random point and loops if it's shorter than the Short. Not with pictures, presenter, motion: moving or review.
    • motion: still (pictures with a slow zoom) | moving (each picture becomes an AI video clip, +15 credits a second; a scene whose clip fails uses the zoom and its share comes back as refunded_credits) (default still)
    • presenter: an actor id (stock, or your own my-… from GET creatorharbor.app/api/v1/actors) who speaks it on camera in their voice (+13 credits a second in either presenter_mode; voice is then ignored)
    • presenter_mode: open_close | whole (default open_close): open_close = on camera for the first and last scenes; pictures fill the middle; whole = on camera the whole way, with a jump-cut punch-in on every other sentence. Captions are timed from the presenter's own take.
    • broll: true | false (default false; presenter_mode whole only): cut away to the scene's picture on every other scene. Without it no pictures are shown, so motion, review, transition and whoosh don't apply.
    • duration_s: 15 | 30 | 60 (default 30)
    • voice: marin | cedar | coral | ash | sage | verse | george | brian | daniel | bill | adam | callum | charlie | chris | eric | harry | liam | roger | will | sarah | alice | bella | jessica | laura | lily | matilda | river (default marin; samples at creatorharbor.app/voices/<voice>.mp3; descriptions in GET /api/v1/catalog), or your own cloned voice (my-voice-…), which a presenter speaks in too
  • Cloned voices: POST creatorharbor.app/api/v1/voices?name=<name>&consent=true with an MP3 or WAV recording as the body (up to 10 MB; at least 15 seconds of one person talking, no music). consent=true confirms it's the user's own voice or they have the speaker's written permission. Returns { id, name }. GET lists them, DELETE /api/v1/voices/{id} removes one. Up to 10. "Make it in other languages" keeps the voice: a dub, lip-synced.
    • visual_style: photo | cinematic | illustration | anime | 3d (default cinematic)
    • captions: hormozi | minimal | box | yellow | karaoke | marker | pop | neon | subtle | fire (default hormozi)
    • music: true | false (default true)
    • say_as, webhook_url and the Idempotency-Key header work as for adsShorts carry the account's brand kit, set in Account: its caption colour, and its logo (top right) once the account has bought credits.
  • GET/api/v1/shorts/{id}?wait=25: status (step: script | voice | visuals | edit), title, script, video_url, thumbnail_url, captions_url (SRT), and once finished its scenes [{ n, line, visual, picture_key, image_url }].
  • Edit a finished Short and make it again: send its scenes back as plan (changed as you like) with pictures set to the picture_key of each scene to keep (null for a new picture). Charged as a new Short.
  • Picture review (review: true): when step is "review", the Short's review lists each scene's line, visual, image_url and redos_left. POST creatorharbor.app/api/v1/shorts/{id}/scenes/{n} { visual } redoes a picture (free, 3 times a scene and the scene count plus 2 for the whole Short); POST creatorharbor.app/api/v1/shorts/{id}/render { lines? } renders the rest; POST creatorharbor.app/api/v1/shorts/{id}/cancel discards it (not refunded). A review left 7 days renders with the pictures as they are.
  • GET/api/v1/shorts?limit=20 and GET creatorharbor.app/api/v1/shorts/{id}/video.
  • POST/api/v1/shorts/{id}/translate { languages }: the same for a finished Short (an edited plan keeps its pictures' descriptions). Returns { shorts: [...] }.

MCP: draft_short, make_short (plan, review), get_short, list_shorts, redo_short_picture, approve_short, discard_short; translate_video for either.

Story ads (multi-shot ads with AI actors and your own footage)

A brief becomes a 15, 30 or 45 s vertical ad: AI actors with a hook in the first frame, your own product screenshots or screen recordings cut under their lines, captions, an order bar with the brand and real price, music, an end card, "AI-generated" and "Dramatization" labels, mastered to -14 LUFS, plus 1:1 and 16:9 versions. Only the brief's price, offer and facts may appear. Concepts are free; you see each paid step's price first (next.credits). Prices: 35 credits a still, 45 a second of speaking clip (30 silent), 60 per render; a 30 s ad is usually 1,000-1,600 credits. Each story has a budget (default 3000, at most 5000).

  • POST/api/v1/stories { product, main_feature, audience, price?, offer?, url?, facts?, platforms?, length_s, style: loud|calm, budget?, screenshot_urls?: [https], assets?: [{ key, label }] (uploaded videos), reference_notes? }: the brief, free. The url's page is read once (its words go to the concept writer, so the story is about what the product does). With no screenshot_urls or assets, up to 4 pictures from that page (its share picture, then its photos) become the product's screenshots; a concept with any must cut to one.
  • POST/api/v1/stories/{id}/concepts { feedback? }: three checked concepts (format, hook, oneIdea, rationale, shots [{ id, kind: live|product|card, seconds, still, video, line, caption }], endCard, copy). Free, capped per day; about a minute.
  • POST/api/v1/stories/{id}/pick { index } or { concept } (edited, checked again).
  • POST/api/v1/stories/{id}/steps { step: cast|still|render|reroll|compose, shot? }: a paid step (compose is free). 202 with the job; failed steps are refunded.
  • GET/api/v1/stories/{id}: shots with still URLs, next { step, credits }, jobs, and once rendered: video, shapes, qa (checks), flaws, copy. DELETE removes it.

MCP: create_story, draft_story_concepts, pick_story_concept, run_story_step, get_story, list_stories.

Series (a Short every day, by itself)

A series makes one Short a day at hour_utc, from a theme (a new topic each day, never one it made before), a list of ideas (in order; it pauses when they run out) or a feed (each new RSS or Atom post). Each Short is charged like POST /api/v1/shorts when it starts; without the credits the series pauses (last_error says why; waiting: true when it only had nothing new today). At most 5 series an account.

  • POST/api/v1/series { name, source: { kind: "theme", theme } | { kind: "ideas", ideas: [...] } | { kind: "feed", url }, settings: { format, language, duration_s, voice, speed, visual_style, captions, music, aspect, motion, headline, transition, whoosh }, hour_utc (default 14), post?: { account_ids, caption, youtube_privacy } }. post: YouTube and Instagram accounts from GET /api/v1/social-accounts; each Short is posted when it's done, captioned with its title and then caption. TikTok asks people to approve each post, so a series doesn't post there.
  • GET/api/v1/series, GET/PATCH { active?, ideas? }/DELETE creatorharbor.app/api/v1/series/{id} (ideas: more at the end of a list of ideas), POST creatorharbor.app/api/v1/series/{id}/run (one now, besides the daily one; not while its last is being made). Each series lists its latest Shorts (episodes: short_id, topic, title, status).

MCP: create_series, list_series, update_series (pause, resume, delete, run_now).

Ad Autopilot (batches of ads on a schedule)

An autopilot makes ads_per_run ads (1 to 10) every every_hours (24 is daily, 1 hourly, the most often) from one ad's settings, rotating its actors and its hooks (the script's own, then each other opening line), one combination per ad in turn. Credits only, no subscription: each ad is charged like POST /api/v1/ads when it starts and refunded only if it fails; each ad of a run has its own idempotency key, so a retried run never charges twice. Each run is priced first: if the balance or its budget (credits a day or a week; what its ads cost in the last day or seven, failed ones not counted) doesn't cover it, it pauses, says why in last_error and emails the user. The first run starts within 5 minutes. At most 5 autopilots an account.

  • POST/api/v1/autopilots { name, from_ad_id? (an ad of yours: its product, script and settings, and its actor), ad?: { product_url, product_name, product_image_url, script, say_as, engine, language, ai_label, format, duration_s, captions } (as POST /api/v1/ads; given fields win over from_ad_id's), actors: [...] (1 to 4, default from_ad_id's), hooks?: [other opening lines, up to 2; needs a script], ads_per_run (default 1), every_hours (default 24), budget: { credits, per: "day" | "week" }, post?: { account_ids, caption, youtube_privacy } }. Without a script, each ad gets a new one written from the page. post: YouTube and Instagram accounts from GET /api/v1/social-accounts; each ad is posted when it's done. TikTok asks people to approve each post, so an autopilot doesn't post there.
  • GET/api/v1/autopilots, GET/PATCH { active?, any field above }/DELETE creatorharbor.app/api/v1/autopilots/{id}. Each lists run_credits (what its next run costs), budget.spent, next_run_at and its latest ads. PATCH changes apply from the next run; active: true finishes a run it paused in, no sooner than an hour after the last.

MCP: create_autopilot, list_autopilots, update_autopilot (fields, or action pause, resume, delete).

When the account's site has it switched on: Shorts that are working right now, from YouTube's Data API, the ones that most outran their channel first (outlier: views per subscriber).

  • GET/api/v1/trends?q=topic&window=week|month|year: { shorts: [{ id, title, channel, url, views, likes, comments, seconds, published_at, subscribers, outlier }] }. Cached six hours; 20 new searches an hour.
  • POST/api/v1/trends/explain { video_id }: { hook, format, why, remix_topic, short_format }: why it worked, and a topic and format for an original Short on the same idea (POST /api/v1/shorts with topic: remix_topic, format: short_format). Never a copy of the video. 30 an hour.
  • The user's library: POST creatorharbor.app/api/v1/library { url } saves a TikTok, YouTube Short or Instagram Reel (60 an hour); the worker reads its numbers and, for TikToks, a transcript (status pending, then ready). GET creatorharbor.app/api/v1/library lists them; POST creatorharbor.app/api/v1/library/{id}/explain gives { hook, why, structure, remix_topic, short_format }, the remix_topic carrying the video's beats (never its words); DELETE creatorharbor.app/api/v1/library/{id}. A browser extension for saving while watching: creatorharbor.app/extension.zip.
  • Posts made through Creator Harbor carry stats { views, likes, comments, at } (read every six hours for a month) in GET /api/v1/posts.

MCP: find_trending_shorts, explain_video, save_video, list_saved_videos, explain_saved_video.

Clips (long video to short clips)

Upload a long video (podcast, interview, talk, stream); get its best moments back as captioned 9:16 clips, ranked, each with a title. 5 credits per started minute of the video, or of the part you pick (up to 120 minutes), known before you pay.

  1. POST/api/v1/uploads { "name": "talk.mp4", "size": <bytes> }: MP4 or MOV, up to 2 GB. Returns { key, upload_id, part_bytes, parts }.
  2. For n = 1..parts: PUT creatorharbor.app/api/v1/uploads/part?key=<key>&upload_id=<upload_id>&n=<n> with the next part_bytes of the file as the body. Returns { etag }. Parts can go in parallel and be retried.
  3. POST/api/v1/uploads/complete { key, upload_id, etags: [in order] }: returns { key, duration_s, clip_credits }.

Or import it from a link instead of steps 1-3: POST creatorharbor.app/api/v1/uploads/import { "url": "<Google Drive or Dropbox share link, or a direct MP4/MOV/WebM URL>" }, then GET creatorharbor.app/api/v1/uploads/import/{id}?wait=25 until status is succeeded; it returns { key, duration_s, clip_credits }. Free; up to 2 GB and 2 hours; YouTube links aren't supported.

  1. POST/api/v1/clips. JSON body:
    • upload: the key (required); name: what it is, for your list
    • count: 3 | 5 | 10 | 20 (default 5, at most this many)
    • length: short (15-30 s) | medium (30-60 s) | long (60-90 s) | xl (90-180 s) (default medium)
    • layout: speaker (crop follows whoever talks) | split (two people in a wide shot, stacked top and bottom; vertical and square clips) | fit (whole frame over a blur) (default speaker)
    • aspect: 9:16 (vertical) | 1:1 (square) | 16:9 (wide) (default 9:16)
    • cut_pauses: true | false: shrink silences over 0.6 s between words to a breath and cut "um"/"uh" (default true)
    • zooms: true | false: subtle punch-in zooms that cut in and out on every other sentence (default true)
    • headline: true | false: each clip's title across the top for its first seconds (default true)
    • from_s, to_s: clip only this part of the video, in seconds (at least a minute); you pay for that part only
    • focus: what to look for, e.g. "advice on hiring" (optional)
    • captions: hormozi | minimal | box | yellow | karaoke | marker | pop | neon | subtle | fire (default hormozi)
    • captions_language: en | es | pt | fr | de | it | nl | pl | tr | sv | id | ro | ru | uk | vi | ja | ko | zh | hi | ar | th | cs | da | nb | fi | el | hu | he | bg | hr | sr | bs | mk | sk | sl | lt | lv | et | sq | ca | is | cy | af | ms | fil | sw | az | kk | uz | fa | ur | mr | ne (optional): translate the captions and titles into it, timed to the speech; omit to caption in the language spoken
    • webhook_url and the Idempotency-Key header work as for adsClips carry the account's brand kit, set in Account: its caption colour, and its logo (top right) once the account has bought credits.
  • GET/api/v1/clips/{id}?wait=25: status (step: transcribe | moments | cut), then clips[]: { n, title, score (0-100), why, keywords, start_s, end_s, seconds, video_url, thumbnail_url }, best first.
  • GET/api/v1/clips?limit=20, GET creatorharbor.app/api/v1/clips/{id}/{n}/video (?download) and /thumb.
  • POST/api/v1/clips/{id}/{n}/edit { instruction }: re-edit one finished clip by plain instruction, free (40 an hour per account, re-cuts 60 an hour), e.g. "cut the dead space, 3 words per caption, a bit bigger, add zooms". It can change words per caption (1-6), caption size (0.6-1.6x), subtle punch-in zooms, cutting pauses and um/uh, trimming the start or end to a word, cutting words out, the shape (9:16, 1:1, 16:9), correcting caption text, and text boxes (top, middle or bottom) over chosen words; anything else it says it can't do yet. Returns { reply, steps, recutting }.
  • GET/api/v1/clips/{id}/{n}/edit: { chat: [{ say, reply, steps, at }], look: { words_per_caption, caption_size, zooms, cold_open, cut_pauses, aspect, caption_fixes: [{ start_s, text }], texts: [{ start_s, end_s, text, position }], framed_by_hand }, recutting, error, start_s, end_s, seconds, video_url }. Poll until recutting is false; video_url then serves the new cut.

MCP: open_upload_panel (a local file, where the host shows MCP Apps; get_uploaded_video checks it) or import_video (from a link) and get_import, then make_clips and get_clips.

Posting to social accounts

Post finished videos to the user's own TikTok, Instagram (Reels, from a Business or Creator account) and YouTube (vertical videos up to 3 minutes show as Shorts), now or scheduled up to 30 days ahead. Paid accounts only (free videos carry a watermark).

  • GET/api/v1/social-accounts: { accounts: [{ id, platform, name, avatar }], connect: [{ id, label, connect_url }] }. Connecting is a browser step: the user opens connect_url while signed in.
  • POST/api/v1/posts: { video_id (an ad, Short or clips job), clip (for clips: 0 = the first), account_ids (Instagram and YouTube), caption (up to 2,200 characters), scheduled_at (ISO 8601; omit for now), instagram: { share_to_feed (default true) }, youtube: { title (up to 100 characters; default the video's title), privacy: public | unlisted | private (default public), made_for_kids (default false) } }. The caption is the YouTube description. Returns { posts: [one per account] }. Ads and Shorts are labelled altered or synthetic on YouTube. Up to 25 posts a day.TikTok: TikTok asks the user to see and confirm each post themselves, so TikTok accounts can't be posted to from the API. A request naming one is refused (400) with a link to Creator Harbor's post screen for that video; give the user the link.
  • When the account's owner turned on approval (a team setting in Account), every post except the owner's own, and Series' and autopilots' posts, is made with status awaiting_approval and goes out once the owner approves it in the calendar.
  • GET/api/v1/posts?limit=20: status (awaiting_approval | scheduled | posting | posted | failed | cancelled), url once posted, error when failed. DELETE creatorharbor.app/api/v1/posts/{id} cancels a scheduled one; DELETE creatorharbor.app/api/v1/social-accounts/{id} disconnects an account.

MCP: list_social_accounts, post_video (Instagram and YouTube; for TikTok it returns the post-screen link), list_posts, cancel_post.

Webhooks

Subscribe once for the whole account with PUT creatorharbor.app/api/v1/webhooks and JSON { "url": "https://your-receiver.example/hook", "events": ["ad.succeeded", "ad.failed", "short.review_ready"] }. A full API key or browser session is required for changes. GET returns { "webhook": <subscription> | null }; DELETE unsubscribes. PUT replaces the URL and event list. Repeating identical settings is safe; changing them or unsubscribing skips pending account deliveries for the earlier settings. An HTTP request already in flight may still arrive. A password reset revokes subscriptions, pending deliveries and existing per-job callbacks.

Supported event names:

  • ad.succeeded, ad.failed: data is the ad, as returned by GET /api/v1/ads/{id}.
  • short.succeeded, short.failed, short.review_ready: data is the Short. review_ready means the pictures are ready for the owner to check; it is still running, step=review. GET the Short for its current review and picture links, then ask the user before approving.
  • clips.succeeded, clips.failed: data is the clip job.
  • story.step_succeeded, story.step_failed: data contains id (the step's job ID), story_id, step, shot (or null), status, credits, error and story_url. These describe individual cast/still/render/reroll/compose jobs. GET story_url for the current project and files; a completed step does not mean every Story step is finished.
  • post.posted, post.failed: data is the post, including its video_id, platform, account name, status, published url or error. Events report posting results and never initiate another post.

An optional per-job webhook_url still receives ad, Short or clip success/failure, independently of the account subscription. If both URLs are identical, one delivery covers both; deleting the subscription keeps the explicit per-job callback.

Each POST body is { "id": "<event UUID>", "type": "<event name>", "created_at": "<ISO timestamp>", "data": <snapshot> }, with the same ID in the CreatorHarbor-Event-Id header. The event is stored with the status transition, so a worker restart cannot lose it. The ID, timestamp and snapshot remain unchanged on retry; signature timestamps are fresh. A receiver should durably deduplicate by id and answer 2xx promptly. Deliveries are at least once, may arrive out of order, and retry six times with backoff after the first failure (non-2xx or timeout); there is no manual replay endpoint. Outbox records expire after 30 days. HTTPS is required in production; credentials, fragments, redirects and private network destinations are refused. For local development only, HTTP/private receivers are allowed.

Every delivery has a CreatorHarbor-Signature: t=<unix seconds>,v1=<hex> header, where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). Get the secret from GET creatorharbor.app/api/v1/webhook-secret or the API page. Verify by recomputing v1 over the raw body, comparing in constant time, and rejecting t older than 5 minutes.

Errors are JSON: { "error": { "code", "message" } }. Codes: unauthorized (401), rate_limited (429: 120 requests a minute per key, 600 for a signed-in browser, or an hourly limit on free calls: drafts, edits, re-cuts, picture redos), bad_input (400), bad_json (400), insufficient_credits (402), not_found (404), idempotency_conflict (409), price_changed (409: expected_credits isn't the price now; nothing was charged), conflict (409: e.g. a Short that changed while you reviewed it), length_required (411), too_large (413: a JSON body over 1 MB, or an upload part over the part size), unprocessable (422: e.g. the product page can't be read and no product_name was given), rendering_closed (503).