# Who am I Source: https://docs.creatorline.io/api-reference/account/who-am-i /openapi.json get /v1/me The workspace this key belongs to, its plan, the live credit balance, and the key's own name and scope. The cheapest way to verify a key works. # Finish an upload Source: https://docs.creatorline.io/api-reference/assets/finish-an-upload /openapi.json post /v1/uploads/{id}/complete Tell us the bytes landed. Flips the asset to `ready` and kicks off thumbnailing/transcoding. Idempotent: completing a finished asset returns it again. # Get one asset Source: https://docs.creatorline.io/api-reference/assets/get-one-asset /openapi.json get /v1/assets/{id} # List media Source: https://docs.creatorline.io/api-reference/assets/list-media /openapi.json get /v1/assets Generated creations, uploads and references. URLs are resolved on read and are time-limited for stored objects. # Start an upload Source: https://docs.creatorline.io/api-reference/assets/start-an-upload /openapi.json post /v1/uploads Uploads are two-step and the bytes never pass through this API: 1. `POST /v1/uploads` → a short-lived presigned `PUT` and the asset id it will become 2. `PUT` the bytes to `upload_url` with the returned headers 3. `POST /v1/uploads/{id}/complete` → the asset is ready to use as a generation input Limits: images 25 MB, video 100 MB, audio 50 MB. # List tools and models Source: https://docs.creatorline.io/api-reference/catalog/list-tools-and-models /openapi.json get /v1/models The live capability catalog: which tools exist, which models each can run, and the exact inputs and parameters those models accept. **This endpoint is the schema oracle.** Read it instead of hardcoding ids — a new model becomes usable the moment it ships. A tool that cannot be run honestly (a workflow-only stage, or a model whose provider workflow is still pending) is absent from this list and rejected with an explicit message if you ask for it. # List AI creators Source: https://docs.creatorline.io/api-reference/creators/list-ai-creators /openapi.json get /v1/accounts Pass an account id as `account_id` on a generation and the run inherits that creator's look, voice and persona, and the output lands on their wall. Posts go to that creator's connected channels. # Balance and ledger Source: https://docs.creatorline.io/api-reference/credits/balance-and-ledger /openapi.json get /v1/credits The ledger is the source of truth for spend: one row per debit (a generation), grant (a top-up or plan renewal) and refund (a failed run). # Estimate a generation's cost Source: https://docs.creatorline.io/api-reference/generations/estimate-a-generations-cost /openapi.json post /v1/generations/cost Price a run in credits WITHOUT starting it. Takes the same body as the create call and runs it through the same function the debit uses, so an estimate can never disagree with the charge. Free, and available to `read` keys. # List generations Source: https://docs.creatorline.io/api-reference/generations/list-generations /openapi.json get /v1/generations # Poll a generation Source: https://docs.creatorline.io/api-reference/generations/poll-a-generation /openapi.json get /v1/generations/{id} `queued` → `processing` → `succeeded` | `failed`. On success `output[]` carries the assets with resolved media URLs. Those URLs are **time-limited** for stored objects — download the bytes rather than caching the link. On failure, `error` explains why and the credits have already been refunded. Poll every few seconds; a photo takes ~30s, a video several minutes. # Start a generation Source: https://docs.creatorline.io/api-reference/generations/start-a-generation /openapi.json post /v1/generations Start an image or video run. **This is the API's one paid operation** and it is asynchronous by design: a video takes minutes, so the call returns a job handle immediately and you poll `GET /v1/generations/{id}`. `inputs` values are either an asset id or a public `https` URL (adopted as an asset automatically). `params` are validated against the model's own schema — an unknown key is a 400, never a silent drop, because a silently ignored parameter on a paid call is the worst possible outcome. Send `Idempotency-Key` and a retry returns the ORIGINAL generation instead of paying for a second run. # Create a post Source: https://docs.creatorline.io/api-reference/posts/create-a-post /openapi.json post /v1/clips Turn finished media into a post for one creator. It is created at **`pending_review`** — the review gate's entry state. Nothing is public yet; it appears in the studio's review queue like any other post. `channels` defaults to that creator's CONNECTED channels, so the common case is just `account_id`, `asset_ids` and `caption`. The format (video / carousel / photo) is derived from the media and validated against every target platform before the post is written. # Get one post Source: https://docs.creatorline.io/api-reference/posts/get-one-post /openapi.json get /v1/clips/{id} After publishing, `channel_posts[]` carries each platform's status and the live URL once that platform confirms. # List posts Source: https://docs.creatorline.io/api-reference/posts/list-posts /openapi.json get /v1/clips # Publish a post Source: https://docs.creatorline.io/api-reference/posts/publish-a-post /openapi.json post /v1/clips/{id}/publish **This is the one endpoint that reaches a real audience.** It moves the post `pending_review → scheduled` — the only legal transition — and hands it to the publisher. `published` is written only after the platform confirms, and a failed hand-off leaves the post scheduled and retryable rather than falsely marked live. Every call writes an audit entry naming the API key that triggered it. # Authentication Source: https://docs.creatorline.io/authentication Organization-scoped API keys: how they work, what they can do, and how to keep them safe. Every request carries an API key that belongs to **one organization**. There is no account switching and no workspace parameter — the tenant comes from the credential, so a key can only ever see and spend its own organization's data and credits. ```bash theme={null} curl https://api.creatorline.io/v1/me \ -H "Authorization: Bearer $CREATORLINE_API_KEY" ``` `X-API-Key: crl_sk_live_…` works identically if your tooling prefers it. ## Creating a key Owners and admins create keys in the studio under **Settings → API**. The key is displayed **exactly once**, when you create it. We store only a keyed hash of it, so there is no endpoint — and no support request — that can show it to you again. Lost a key? Revoke it and create another. ## Anatomy ``` crl_sk_live_a7F3k2p9QxZm_8Kd2mXpLq…4f2c_9xQ2mR └ prefix ──┘└ key id ────┘└ secret ────┘└ crc ┘ ``` Marks the string as a Creatorline secret key so secret scanners and log scrubbers can spot a leak. If one of these ever lands in a public repository, revoke it immediately. Public. It is what identifies the key in the dashboard (`crl_sk_live_a7F3k2p9QxZm…4f2c`) and in our logs — safe to quote in a support conversation. 190 bits of randomness, never stored. We keep only a keyed hash. A checksum over everything before it. A mistyped or forged key is rejected instantly, without a database lookup. ## Scopes Every `GET`, plus cost estimates. Cannot spend a single credit. **Use this for CI and for anything that only reads.** Everything above, plus starting generations, uploading media, and publishing posts. A `read` key that attempts a write gets a `403` naming the problem, not a mysterious failure: ```json theme={null} { "error": { "type": "permission_error", "code": "insufficient_scope", "message": "This API key is read-only. Create a key with the `write` scope to run generations." }, "request_id": "req_8fQ2mRxK1pLd" } ``` ## Rotation and revocation Revoking is immediate — the next request with that key gets a `401`. The key's row survives so your usage history and audit trail keep resolving. To rotate without downtime: create the new key, deploy it, then revoke the old one. A key can also be given an expiry at creation, which is the right choice for a short-lived job. ## Rate limits **120 requests per minute per key.** Every response carries the state of your window: | Header | | | ----------------------- | --------------------------- | | `x-ratelimit-limit` | requests allowed per window | | `x-ratelimit-remaining` | requests left | | `x-ratelimit-reset` | unix seconds when it resets | On a `429`, honour `Retry-After`. Generation is separately gated by your credit balance, which is usually the limit you will meet first. ## Keeping keys safe A key can spend your organization's credits and publish to your creators' accounts. Never ship one to a browser, a mobile app, or anything a user can read. `CREATORLINE_API_KEY` in your environment or secret manager. Never in a repository, never in a URL query string. A key per service, per environment, per developer machine. Then revoking one is a non-event instead of an outage, and the usage counters tell you who is spending what. # CLI Source: https://docs.creatorline.io/cli crl — generate and publish from your terminal, your CI, and your coding agent. ```bash theme={null} npm install -g @creatorline/cli crl auth login # paste a key from Settings → API ``` Zero runtime dependencies, Node 20+. Grammar is always `crl `. ## The three-line version ```bash theme={null} crl accounts use Eda crl gen create text-to-photo --prompt "matcha latte flatlay" --wait --output-dir ./out crl posts create --asset --caption "morning ritual ☕️" --publish now ``` ## Commands | Command | Subcommands | | ---------- | --------------------------------------------------------------- | | `auth` | `login`, `logout`, `whoami` | | `models` | `list [tool]`, `get ` | | `accounts` | `list`, `use `, `current`, `clear` | | `gen` | `create `, `cost `, `get `, `wait `, `list` | | `posts` | `create`, `publish `, `list`, `get ` | | `assets` | `list`, `get ` | | `upload` | `crl upload ` | | `credits` | — | | `mcp` | `config`, `install` | `generate` aliases `gen`, `creators` aliases `accounts`, `clips` aliases `posts`. ## Global flags | Flag | Default | | | ----------------------------------- | ------- | --------------------------------------------------- | | `--json` | | machine-readable output, and nothing else on stdout | | `--wait` | | block until the generation finishes | | `--wait-timeout` | `10m` | give up waiting — the run continues server-side | | `--wait-interval` | `3s` | poll interval | | `--output-dir` | | download results to disk | | `--api-key`, `--api-url` | | override the stored credentials | | `--no-color`, `--help`, `--version` | | | **Both spellings of every flag work.** `--aspect-ratio` and `--aspect_ratio` are the same flag. Model parameters are snake\_case and CLI convention is kebab-case; guessing wrong should not be a failure mode. ## The model's flags are the model's schema `crl gen create` fetches the model's schema first, then interprets the remaining flags against it. The CLI hardcodes no model parameters, so a new model works the day it ships: ```bash theme={null} crl models get bytedance-seedance-2-0-reference-to-video-fast crl gen create ugc-video --prompt "unboxing" --image ./face.png --duration 5 --wait ``` A wrong flag tells you the fix: ``` error Unknown flag --durations for model bytedance-seedance-2-0-reference-to-video-fast. inputs: --characters --location --objects --audio_urls parameters: --ugc_style --aspect_ratio --duration --resolution --generate_audio Run `crl models get bytedance-seedance-2-0-reference-to-video-fast` for values and defaults. ``` ## Media flags take a path or an id `--image ./face.png` uploads the file and uses it. `--image ` reuses an existing asset. `--image https://…` passes the URL through. No separate upload step — though `crl upload ` exists when you want to pin an id. Prompts can come from stdin: ```bash theme={null} echo "a cinematic city at dusk" | crl gen create text-to-photo --wait ``` ## Pick a creator once ```bash theme={null} crl accounts list crl accounts use Eda # persisted, like the studio's creator switcher crl accounts current ``` Every later `gen create` and `posts create` runs as that creator. `--account-id` overrides it for one command. ## Scripting and agents Results go to **stdout**, progress and errors to **stderr**, so pipes stay clean: ```bash theme={null} crl gen list --json | jq -r '.data[] | select(.status=="succeeded") | .output[0].url' ``` With `--wait`, `crl gen create` prints the media URL. Without it, the generation id. Parallel fan-out is just shell job control — `--wait` blocks per process: ```bash theme={null} for tool in text-to-photo ugc-video; do crl gen create "$tool" --prompt "$PROMPT" --wait --output-dir "./out/$tool" & done wait ``` Exit codes: `0` success · `1` usage or runtime error · `2` not authenticated · `3` out of credits. ## Configuration Precedence: flags → environment → config file. | | | | ----------------------------------- | --------------------------------------------------- | | `CREATORLINE_API_KEY` | the key | | `CREATORLINE_API_URL` | the API base (default `https://api.creatorline.io`) | | `~/.config/creatorline/config.json` | written by `crl auth login`, mode `0600` | CI wants the environment variable — there is no interactive login there. ```bash theme={null} CREATORLINE_API_KEY=$CI_SECRET crl gen create text-to-photo --prompt "…" --wait --json ``` # Errors and idempotency Source: https://docs.creatorline.io/errors One error shape everywhere, what each status means, and how to retry safely. Every failure has the same shape, and every response — success or not — carries an `x-request-id` header. Quote it and we can find the exact request. ```json theme={null} { "error": { "type": "invalid_request_error", "code": "invalid_parameter", "message": "Invalid value for \"aspect_ratio\": \"99:99\". Allowed: 1:1, 9:16, 16:9.", "param": "params.aspect_ratio" }, "request_id": "req_8fQ2mRxK1pLd" } ``` Errors are written to be **actionable**: a bad enum echoes the legal values, an unknown tool lists the real ones, and `param` points at the exact field. If you are writing an agent, surfacing `error.message` verbatim is usually enough for it to fix itself. ## Statuses | Status | `type` | What it means | Retry? | | ------ | ---------------------------- | -------------------------------------------------------------------- | -------------------- | | `400` | `invalid_request_error` | Bad input. `param` names the field. | Only after fixing it | | `401` | `authentication_error` | Missing, malformed, revoked or expired key. | No | | `402` | `insufficient_credits_error` | Not enough credits. Carries `required_credits` and `credit_balance`. | After topping up | | `403` | `permission_error` | A `read` key tried to write. | No | | `404` | `not_found_error` | Unknown id — **or** an id in another organization. | No | | `429` | `rate_limit_error` | Over 120 req/min. Honour `Retry-After`. | Yes, with backoff | | `5xx` | `api_error` | Our fault. | Yes, with backoff | A resource belonging to another organization returns `404`, never `403`. We will not confirm that an id exists somewhere else. ## Retrying safely `GET` is safe to retry. For `POST /v1/generations` — the one call that spends money — send an `Idempotency-Key`: ```bash theme={null} curl https://api.creatorline.io/v1/generations \ -H "Authorization: Bearer $CREATORLINE_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "tool": "text-to-photo", "prompt": "…" }' ``` A replay with the same key returns the **original** generation, with `idempotent-replay: true`, and charges nothing more. Generate one UUID per logical operation and reuse it across that operation's retries. Keys are scoped to your API key and kept for 24 hours. If your request times out, the run may well have started — the charge happens server side, before your response is written. Retrying with the same `Idempotency-Key` is how you find out which: you get the original job handle back instead of paying twice. ## The 402 you should expect ```json theme={null} { "error": { "type": "insufficient_credits_error", "code": "insufficient_credits", "message": "This request costs 30 credits and the workspace has 12." }, "request_id": "req_8fQ2mRxK1pLd", "required_credits": 30, "credit_balance": 12 } ``` Nothing was charged and nothing was started. Call [`POST /v1/generations/cost`](/api-reference/generations/estimate-a-generation-s-cost) first if you want to avoid it, or watch [`GET /v1/credits`](/api-reference/credits/balance-and-ledger). # Generating media Source: https://docs.creatorline.io/generations Tools, models, inputs and parameters — and how a run actually flows. A generation is a job. You start it, it costs credits, and you poll it until media comes out. Nothing about it blocks: a photo takes around 30 seconds and a video several minutes. ## Tools, not models You address a **tool** (`ugc-video`, `text-to-photo`, `lipsync`), and the platform picks the right model and pipeline. Pass `model` only when you specifically want to override it. ```bash theme={null} curl https://api.creatorline.io/v1/models \ -H "Authorization: Bearer $CREATORLINE_API_KEY" ``` **Read the catalog, don't hardcode it.** `GET /v1/models` returns every tool, every model, and the exact inputs and parameters each accepts. When we add a model, your code can use it without a change on your side. Narrow with `?tool=ugc-video` or `?model=`. `ugc-video` · `short-clip` · `scene-video` · `motion-control` · `hook-captions` `text-to-photo` · `photo-edit` `text-to-speech` · `lipsync` · `change-voice` · `add-audio` · `dialogue` ## Generating as a creator Pass `account_id` and the run inherits that creator's look, voice and persona, and the output lands on their wall in the studio. Without it, the run is workspace-scoped and generic. ```bash theme={null} curl https://api.creatorline.io/v1/accounts \ -H "Authorization: Bearer $CREATORLINE_API_KEY" ``` ## Inputs and parameters Slot key → asset id(s) **or** public `https` URL(s). A URL is adopted as an asset automatically, so there is no upload step when your media is already on the internet. Which slots a model takes — and which are required — is in its catalog entry. Model parameters like `aspect_ratio`, `duration`, `resolution`. Validated against the model's schema. An unknown parameter is a `400`, never a silent drop. A typo'd parameter quietly ignored on a paid call is the worst possible outcome, so we refuse the call instead. ```json theme={null} { "tool": "ugc-video", "prompt": "unboxing a skincare set, chatty and warm", "account_id": "e307923d-…", "inputs": { "characters": ["https://example.com/face.png"] }, "params": { "aspect_ratio": "9:16", "duration": "5" } } ``` ## Uploading your own media Bytes never pass through the API. Three steps, and the asset id is usable the moment the `PUT` succeeds: `POST /v1/uploads` with `content_type` and `size_bytes`. You get `upload_url`, `headers` and the `asset_id` it will become. Straight to storage, with the headers you were given. `POST /v1/uploads/{id}/complete`. This starts thumbnailing and transcoding, and hands you the finished asset. Limits: images 25 MB, video 100 MB, audio 50 MB. The [CLI](/cli) does all three for you — `--image ./face.png` just works. ## Cost ```bash theme={null} curl https://api.creatorline.io/v1/generations/cost \ -H "Authorization: Bearer $CREATORLINE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tool": "ugc-video", "params": { "duration": "10" } }' ``` ```json theme={null} { "object": "cost_estimate", "model": "bytedance-seedance-2-0-reference-to-video-fast", "credits": 48, "credit_balance": 4433, "sufficient": true } ``` Free to call, available to `read` keys, and priced by the same function that performs the debit — so an estimate cannot disagree with the charge. Credits are taken at kick-off; a run that terminally fails, including a moderation rejection, is **refunded automatically**. ## Polling ``` queued → processing → succeeded ↘ failed ``` Poll `GET /v1/generations/{id}` every few seconds. On `succeeded`, `output[]` holds the assets: ```json theme={null} { "object": "generation", "id": "3f6c1a2b-…", "status": "succeeded", "output": [ { "object": "asset", "id": "9b2f7c10-…", "file_type": "video", "ratio": "9:16", "duration_sec": 5, "url": "https://…/clip.mp4" } ] } ``` Media URLs are **time-limited**. Download the bytes; do not persist the link. On `failed`, `error` carries the reason — a moderation rejection reads differently from a provider failure — and your credits are already back. ## Next Turn finished media into a post on your creator's social accounts. Every field, with a live playground. # Creatorline API Source: https://docs.creatorline.io/index Generate AI influencer photos and videos, and publish them to social platforms, from your own code. Creatorline runs AI creators: a persona with a consistent face, voice and audience, posting on a schedule. The API gives you every piece of that pipeline except the human judgement — generate media as a specific creator, then publish it to their connected accounts. Three surfaces, one implementation. They share the same key, the same validation, and the same credit accounting. Your backend, your scripts, anything that speaks HTTP. `crl` — terminals, CI, and coding agents. Claude Code, Cursor, any MCP client. ## Quickstart In the studio, go to **Settings → API** and create a key with the `write` scope. The key is shown **once** — copy it now. ```bash theme={null} export CREATORLINE_API_KEY="crl_sk_live_…" ``` ```bash theme={null} curl https://api.creatorline.io/v1/me \ -H "Authorization: Bearer $CREATORLINE_API_KEY" ``` ```json theme={null} { "object": "account_info", "workspace": { "id": "…", "name": "Studio", "plan": "plus", "credit_balance": 4433 }, "api_key": { "id": "oyYv4L5qhJlH", "name": "Backend", "scope": "write" } } ``` ```bash theme={null} curl https://api.creatorline.io/v1/generations \ -H "Authorization: Bearer $CREATORLINE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tool": "text-to-photo", "prompt": "matcha latte flatlay on linen, morning light", "params": { "aspect_ratio": "4:5" } }' ``` Returns immediately with a job handle and the credits it charged: ```json theme={null} { "object": "generation", "id": "3f6c1a2b-…", "status": "queued", "model": "nano-banana-2-text-to-image", "credits_charged": 2, "credit_balance": 4431 } ``` ```bash theme={null} curl https://api.creatorline.io/v1/generations/3f6c1a2b-… \ -H "Authorization: Bearer $CREATORLINE_API_KEY" ``` `queued` → `processing` → `succeeded`, then `output[0].url` is your image. Result URLs are time-limited. Download the bytes rather than storing the link. ## The shape of the thing A photo takes \~30 seconds and a video several minutes, so no endpoint blocks on a run. You get a job handle and poll it. The run is durable: it survives your process dying, and you can rejoin it by id at any time. Do not hardcode model ids or parameter names. [`GET /v1/models`](/api-reference/catalog/list-tools-and-models) returns every tool, every model, and the exact inputs and parameters each accepts — so a model added to the platform is usable from your code with no change on your side. [`POST /v1/generations/cost`](/api-reference/generations/estimate-a-generation-s-cost) prices a run without starting it, using the same function the debit uses. A failed or moderation-blocked run is refunded automatically. A post is created at `pending_review` and publishing is a separate, explicit call. If you want a human to approve, simply never call publish — the post waits in the studio's review queue. See [Publishing](/publishing). ## What you can do | | | | -------------- | -------------------------------------------------------------------------------------------------- | | **Video** | UGC video, short clips from a long source, scene replacement, motion sync, burned-in hook captions | | **Photo** | text to photo, character-consistent photo editing | | **Audio** | text to speech, lipsync, voice change, dialogue, audio over video | | **Creators** | list the workspace's AI creators and generate as one of them | | **Media** | browse the library, upload your own images, video and audio | | **Publishing** | build a post from media and send it to TikTok, Instagram, YouTube or Telegram | | **Billing** | live credit balance and the full consumption ledger | # MCP server Source: https://docs.creatorline.io/mcp Give Claude Code, Cursor, or any MCP client the ability to generate and publish. Creatorline runs a **remote, stateless** MCP server. There is nothing to install and no local process — point your client at the URL with your API key. ```bash Claude Code theme={null} claude mcp add --transport http creatorline https://api.creatorline.io/mcp \ --header "Authorization: Bearer $CREATORLINE_API_KEY" ``` ```json .mcp.json / Cursor theme={null} { "mcpServers": { "creatorline": { "type": "http", "url": "https://api.creatorline.io/mcp", "headers": { "Authorization": "Bearer ${CREATORLINE_API_KEY}" }, "timeout": 600000 } } } ``` ```bash CLI helper theme={null} crl mcp config # print the snippets for your setup crl mcp install # run `claude mcp add` for you ``` Set `timeout` to at least `60000`. Claude Code applies a 60-second first-byte timer to HTTP MCP servers and a 5-minute idle timer; the default is too tight for a video run's first poll. ## Tools `list_models` · `list_accounts` · `get_credit_balance` · `estimate_generation_cost` `create_generation` · `get_generation` · `list_assets` `create_post` · `publish_post` · `list_posts` Ten coarse tools, not one per model. A tool per model would consume an agent's context for no benefit; instead `create_generation` takes a `tool` and the model schema comes from `list_models`. ## How an agent should use it `list_models` for the catalog, `list_accounts` for the creators. Never guess an id. `estimate_generation_cost` is free and does not start anything. `create_generation` returns a job handle **immediately** and reports the credits it charged. Poll `get_generation` every few seconds — a video takes minutes, and holding the tool call open would hit the client's idle timer. `create_post` puts a post in the review queue. `publish_post` makes it public — it is annotated as destructive so your client prompts first, and the tool description tells the model to confirm with the user. ## Multi-tenancy The workspace comes from the **API key**, never from a tool argument. There is no `workspace_id` parameter anywhere in the tool surface, so a model cannot be talked into reading another organization's data. A `read`-scope key gets a plain-English refusal from the writing tools rather than a protocol error, so the agent can explain the problem instead of retrying. ## Stateless The server implements the 2026-07-28 MCP revision: no session id, no handshake, no resumable streams. Every request builds a fresh server instance bound to the calling key. In practice that means it never gets into a bad session state, it scales horizontally, and reconnecting costs nothing. Custom connectors on claude.ai currently support OAuth only — there is no field for a custom header — so the API-key connection above works with **Claude Code and Cursor** today. OAuth support for claude.ai connectors is planned. ## Results stay small Media comes back as URLs, never inline. Lists default to 20 rows and page. Claude Code truncates tool output at 25,000 tokens, so the tools are built to stay well under that — if you need everything, page rather than raising the limit. # Publishing Source: https://docs.creatorline.io/publishing Turn finished media into a post and send it to your creator's connected accounts. Publishing is two calls: build the post, then send it. They are separate on purpose — the split is where a human fits, if you want one. ```bash theme={null} curl https://api.creatorline.io/v1/clips \ -H "Authorization: Bearer $CREATORLINE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "e307923d-…", "asset_ids": ["9b2f7c10-…"], "caption": "morning ritual ☕️", "hashtags": ["skincare", "grwm"] }' ``` The post is created at **`pending_review`**. Nothing is public yet — it shows up in the studio's review queue like any other post. `channels` defaults to that creator's **connected** channels, so most calls need only `account_id`, `asset_ids` and `caption`. The format (photo, carousel or video) is derived from the media and validated against every target platform before the post is written — a carousel that a platform cannot take is a `400`, not a failed publish an hour later. ```bash theme={null} curl https://api.creatorline.io/v1/clips/{id}/publish \ -H "Authorization: Bearer $CREATORLINE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "now" }' ``` | mode | | | ---------- | ----------------------------------------------------- | | `now` | post immediately | | `schedule` | post at `scheduled_for` (`"21:30"`) | | `queue` | the creator's next free slot in their posting cadence | ```bash theme={null} curl https://api.creatorline.io/v1/clips/{id} \ -H "Authorization: Bearer $CREATORLINE_API_KEY" ``` ```json theme={null} { "object": "clip", "status": "published", "channels": ["TikTok", "IG"], "channel_posts": [ { "platform": "TikTok", "status": "published", "url": "https://tiktok.com/@…/video/…" }, { "platform": "IG", "status": "published", "url": "https://instagram.com/p/…" } ] } ``` ## The review gate `POST /v1/clips/{id}/publish` is the only call in this API that reaches a real audience, and it is not undoable. Treat it the way you would treat a deploy to production. The states a post moves through: ``` pending_review → scheduled → published ↓ rejected ``` Three rules hold regardless of who is calling: * A post **enters** at `pending_review`. The API cannot create one in any other state. * `scheduled` is reachable **only** from `pending_review`. Asking to publish an already published post is a `400` that says so. * `published` is written **only** after the platform confirms. If the hand-off fails, the post stays `scheduled` with the reason recorded, and you can retry — it is never falsely marked live. **Want a human in the loop?** Create posts and never call publish. They queue up in the studio's review screen, where an operator approves them exactly as they would a post the platform generated. The API gives you the operator's authority; it does not force you to use it. Every publish writes an audit entry naming the API key that triggered it, so "who posted this?" always has an answer. ## Requirements Channels are connected in the studio, per creator. A creator with none gets a `400` telling you so — the API cannot connect accounts on your behalf. Use asset ids from a `succeeded` generation, or from a completed upload. An asset that is still uploading is rejected. One video, or up to ten stills for a carousel. Platform-specific carousel limits are checked at creation. ## From the CLI ```bash theme={null} ASSET=$(crl gen create text-to-photo --prompt "matcha flatlay" --wait --json | jq -r '.output[0].asset_id') crl posts create --asset "$ASSET" --caption "morning ritual ☕️" --publish now ``` `crl posts create` alone leaves the post in review. `--publish now|schedule|queue` chains the second call. Publishing from a terminal asks for confirmation; pass `--yes` in scripts.