# 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.