# Clippie API and MCP docs

URL: https://clippie.ai/developers/docs

The Clippie API makes AI Story videos, Clippie Recast videos, AI images and video, voiceovers and transcripts from your own code, at `https://api.clippie.ai/v1`. The MCP server at `https://api.clippie.ai/mcp` gives Claude, ChatGPT, Cursor and other AI clients the same tools. Both spend the same credits and minutes as the app, at the same prices.

## Quickstart

1. Create an API key in [Settings, API](https://clippie.ai/user/settings?tab=api) (paid plans). It starts with `clp_pat_` and is shown once. Put it in an environment variable, `CLIPPIE_API_KEY`, never in code.
2. Make your first call: `GET /v1/me` shows your plan, credits and minutes.
3. Check the cost: send the create call with `"dryRun": true`. Nothing is spent.
4. Create: the same call without `dryRun`, with an `Idempotency-Key` header. It answers `202 { job }` straight away.
5. Wait: `GET /v1/jobs/:id?wait=30` until the job is `completed`, or pass a `webhookUrl` and get `job.completed` posted to you.

First call:

```bash
curl https://api.clippie.ai/v1/me \
  -H "Authorization: Bearer $CLIPPIE_API_KEY"
# => { "user": { ... }, "plan": { ... }, "credits": { "balance": ... }, "minutesLeft": { ... }, "watermark": { ... } }
```

ai-story.sh:

```bash
# 1. Create the video. Send the same Idempotency-Key if you retry,
#    so a retry returns the first job instead of charging twice.
#    Add "dryRun": true to see the exact cost without spending anything.
curl https://api.clippie.ai/v1/videos/ai-story \
  -H "Authorization: Bearer $CLIPPIE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5d1c0e2a-7c4f-4a51-9b1e-2f0c6f1d9a77" \
  -d '{
    "prompt": "The lost library of Alexandria",
    "aspectRatio": "9:16",
    "durationSeconds": 60,
    "webhookUrl": "https://example.com/clippie-webhook"
  }'
# => 202 { "job": { "id": "job_...", "status": "queued", ... } }

# 2. Wait for it. ?wait= holds the request open for up to 50 seconds.
curl "https://api.clippie.ai/v1/jobs/job_...?wait=30" \
  -H "Authorization: Bearer $CLIPPIE_API_KEY"
# => { "job": { "status": "completed", "output": { "videoUrl": "...", "editorUrl": "..." } } }
```

ai-story.mjs:

```js
import { randomUUID } from 'node:crypto';

const API = 'https://api.clippie.ai/v1';
const auth = { Authorization: `Bearer ${process.env.CLIPPIE_API_KEY}` };
const video = { prompt: 'The lost library of Alexandria', aspectRatio: '9:16', durationSeconds: 60 };

// Check the cost first. A dry run spends nothing.
const dry = await fetch(`${API}/videos/ai-story`, {
  method: 'POST',
  headers: { ...auth, 'Content-Type': 'application/json' },
  body: JSON.stringify({ ...video, dryRun: true }),
}).then((res) => res.json());
console.log(dry.estimate); // { credits, minutes, wouldSucceed, reason }

// Make one key per video and reuse it on retries, so you are never charged twice.
const idempotencyKey = randomUUID();
let { job } = await fetch(`${API}/videos/ai-story`, {
  method: 'POST',
  headers: { ...auth, 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey },
  body: JSON.stringify(video),
}).then((res) => res.json());

// Long-poll: each call waits up to 30 seconds for the job to change.
while (!['completed', 'failed', 'canceled'].includes(job.status)) {
  ({ job } = await fetch(`${API}/jobs/${job.id}?wait=30`, { headers: auth }).then((res) => res.json()));
}

console.log(job.status, job.output?.videoUrl, job.output?.editorUrl);
```

ai_story.py:

```python
import os, uuid, requests

API = "https://api.clippie.ai/v1"
auth = {"Authorization": f"Bearer {os.environ['CLIPPIE_API_KEY']}"}
video = {"prompt": "The lost library of Alexandria", "aspectRatio": "9:16", "durationSeconds": 60}

# Check the cost first. A dry run spends nothing.
dry = requests.post(f"{API}/videos/ai-story", json={**video, "dryRun": True}, headers=auth).json()
print(dry["estimate"])

# Make one key per video and reuse it on retries, so you are never charged twice.
idempotency_key = str(uuid.uuid4())
job = requests.post(
    f"{API}/videos/ai-story",
    json=video,
    headers={**auth, "Idempotency-Key": idempotency_key},
).json()["job"]

# Long-poll: each call waits up to 30 seconds for the job to change.
while job["status"] not in ("completed", "failed", "canceled"):
    job = requests.get(f"{API}/jobs/{job['id']}", params={"wait": 30}, headers=auth, timeout=60).json()["job"]

print(job["status"], (job.get("output") or {}).get("videoUrl"))
```

## Authentication

Send a bearer token on every call: `Authorization: Bearer <token>`.

- API keys (`clp_pat_…`) are for your code and for MCP clients that send a fixed header. Create them in [Settings, API](https://clippie.ai/user/settings?tab=api). A key has the `read` scope, the `create` scope or both, and expires after 30 days, 90 days (the default) or never. You can have 20. Only a hash is stored, so a lost key cannot be shown again; revoke it and make a new one.
- OAuth access tokens (`clp_at_…`) are what Claude, ChatGPT, Cursor and VS Code get when you connect them to the MCP server and sign in. They use authorization code with PKCE and dynamic client registration; a `401` carries a `WWW-Authenticate` header that points clients at the sign-in.
- The API is part of every paid plan. Without an active subscription, calls answer `403 subscription_required`. A suspended account gets `403 suspended`.

> Keep keys in an environment variable or a secret manager. Anyone with a key can spend your credits within its scopes.

## Requests and responses

- Base URL: `https://api.clippie.ai/v1`. Breaking changes go to `/v2`; new fields can appear at any time.
- POST bodies are JSON (`Content-Type: application/json`). GET calls take query parameters.
- Lists take `?limit=` (up to 100, default 25) and `?cursor=`, and answer `{ data, nextCursor }`. Pass `nextCursor` back to get the next page; it is `null` on the last one.
- Every response has an `X-Request-Id`. Include it when you contact support.
- The OpenAPI 3.1 spec is at https://api.clippie.ai/v1/openapi.json.

## Errors

Errors are `{ "error": { "type", "message", "param"?, "requestId" } }`. Branch on `type`; the message is written for people.

| Status | type | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | A field is missing or wrong. `param` names it. |
| 401 | `unauthorized` | No key, or the key is wrong, expired or revoked. MCP clients get a `WWW-Authenticate` hint to sign in. |
| 402 | `insufficient_credits` | Not enough credits for this call. Top up or wait for the next period. |
| 402 | `insufficient_minutes` | Not enough voiceover, caption or export minutes. |
| 403 | `subscription_required` | The API needs an active paid plan. |
| 403 | `forbidden_scope` | The key is read-only and the call creates something. |
| 403 | `suspended` | The account is suspended. |
| 403 | `forbidden` | This account cannot use the API (yet). |
| 404 | `not_found` | No such job, file or project on your account. |
| 409 | `idempotency_conflict` | The Idempotency-Key was used with a different body. |
| 409 | `too_many_active_jobs` | Too many jobs are running. Wait for one to finish. |
| 422 | `generation_rejected` | The model refused the input, for example a safety filter. |
| 429 | `rate_limited` | Too many calls. Wait for `Retry-After` seconds. |
| 500 | `internal` | Something broke on our side. Retry with the same Idempotency-Key. |

A generation that fails after the `202` is a job failure, not an HTTP error: the job ends `failed` with an `error`, and whatever was charged is refunded.

## Rate limits

- 120 calls a minute per account, REST and MCP together. Responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`.
- Over the limit, calls answer `429 rate_limited` with a `Retry-After` header. Wait that many seconds.
- Create calls also count against the same per-feature limits as the app (below).
- At most 5 jobs run at once per account (3 for AI Story videos). Another one answers `409 too_many_active_jobs`.

| Call | Limit bucket |
| --- | --- |
| [generate_image](https://clippie.ai/developers/docs/generate-image.md) | `ai_image` |
| [generate_video](https://clippie.ai/developers/docs/generate-video.md) | `ai_video` |
| [recast_video](https://clippie.ai/developers/docs/recast-video.md) | `ai_video` |
| [upscale](https://clippie.ai/developers/docs/upscale.md) | `upscale` |
| [text_to_speech](https://clippie.ai/developers/docs/text-to-speech.md) | `voiceover` |
| [transcribe](https://clippie.ai/developers/docs/transcribe.md) | `transcribe` |
| [write_script](https://clippie.ai/developers/docs/write-script.md) | `llm` |
| [create_ai_story_video](https://clippie.ai/developers/docs/create-ai-story-video.md) | `render` |
| [render_video](https://clippie.ai/developers/docs/render-video.md) | `render` |

## Idempotency, dry runs and estimates

- Send `Idempotency-Key: <up to 255 chars>` on every create call (MCP: the `idempotencyKey` argument). A retry with the same key and body within 24 hours returns the first job and charges nothing; the same key with a different body is `409 idempotency_conflict`. Make one key per thing you want made, for example a UUID, and keep it until the job exists.
- Add `"dryRun": true` to any create call to run every check and get `{ dryRun: true, estimate }` back: the credits and minutes it would take, `wouldSucceed`, and the `reason` when it would be refused. Nothing is spent and no job is made.
- [estimate_cost](https://clippie.ai/developers/docs/estimate-cost.md) (`POST /v1/estimate`) does the same for any create tool by name, which is handy for agents and for adding up a batch.

## Jobs and waiting

Every create call answers `202 { job }`. A job goes `queued` → `processing` → `rendering` → `completed`, `failed` or `canceled`.

- `job.output` holds what was made: `videoUrl`, `imageUrls`, `audioUrl`, `captions`, `editorUrl` (open it in the Clippie editor) and `libraryId`.
- `job.cost` is what was actually charged, after any refund: `{ credits, minutes: { voiceover, captions, export } }`.
- [Get job](https://clippie.ai/developers/docs/get-job.md) with `?wait=30` holds the request (up to 50 seconds) until the job changes, so a simple loop never hammers the API. MCP: `get_job` with `waitSeconds`.
- [Cancel job](https://clippie.ai/developers/docs/cancel-job.md) stops a job that has not started. [List jobs](https://clippie.ai/developers/docs/list-jobs.md) shows your recent ones.

A job:

```json
{
  "job": {
    "id": "job_abc123",
    "object": "job",
    "kind": "ai_story_video",
    "status": "queued",
    "step": null,
    "progress": null,
    "input": {
      "prompt": "The lost library of Alexandria"
    },
    "output": null,
    "cost": {
      "credits": 0,
      "minutes": {
        "voiceover": 1,
        "captions": 1,
        "export": 1
      }
    },
    "error": null,
    "source": "api",
    "webhookUrl": null,
    "createdAt": "2026-10-24T12:00:00.000Z",
    "startedAt": null,
    "completedAt": null
  }
}
```

## Webhooks

Pass `webhookUrl` (https, public address) on a create call and Clippie POSTs `{ event, job }` to it when the job finishes.

- Events: `job.completed` and `job.failed`.
- Headers: `Clippie-Event`, `Clippie-Job-Id` and `Clippie-Signature: t=<unix>,v1=<hex>`, an HMAC-SHA256 of `"<t>.<raw body>"` with your `whsec_…` signing secret from [Settings, API](https://clippie.ai/user/settings?tab=api).
- Answer with any 2xx within 10 seconds. Failed deliveries are retried after 30 s, 2 m, 10 m, 30 m, 1 h, 2 h, 4 h and 8 h. Redirects are not followed.
- Delivery is at least once: dedupe on `Clippie-Job-Id` plus the event. Check the signature against the raw body and reject timestamps older than five minutes.

verify.mjs:

```js
import { createHmac, timingSafeEqual } from 'node:crypto';

// header: the Clippie-Signature header, "t=<unix>,v1=<hex>"
// rawBody: the request body exactly as received, before JSON parsing
// secret: your whsec_... signing secret from Settings > API
export function verifyClippieWebhook(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((part) => part.trim().split('=')));
  const timestamp = Number(parts.t);
  if (!Number.isInteger(timestamp) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1, 'hex');
  return given.length === expected.length && timingSafeEqual(given, expected);
}
```

verify.py:

```python
import hashlib, hmac, time

# header: the Clippie-Signature header, "t=<unix>,v1=<hex>"
# raw_body: the request body bytes exactly as received
# secret: your whsec_... signing secret from Settings > API
def verify_clippie_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(part.strip().split("=", 1) for part in header.split(",") if "=" in part)
    try:
        timestamp = int(parts.get("t", ""))
    except ValueError:
        return False
    if abs(time.time() - timestamp) > tolerance:
        return False
    signed = f"{timestamp}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))
```

## MCP server

The MCP server is at `https://api.clippie.ai/mcp` (Streamable HTTP). It offers every endpoint below as a tool, with the same scopes, prices and errors. Claude, ChatGPT, Cursor and VS Code sign in with OAuth when you add the URL; clients that send a fixed header can use an API key instead.

### Install

Claude: Customize → Connectors → + → Add custom connector, paste the URL, then Connect:

```text
https://api.clippie.ai/mcp
```

Claude Code (then /mcp to sign in):

```bash
claude mcp add --transport http clippie https://api.clippie.ai/mcp
```

Cursor: ~/.cursor/mcp.json:

```json
{
  "mcpServers": {
    "clippie": {
      "url": "https://api.clippie.ai/mcp"
    }
  }
}
```

VS Code: .vscode/mcp.json:

```json
{
  "servers": {
    "clippie": {
      "type": "http",
      "url": "https://api.clippie.ai/mcp"
    }
  }
}
```

ChatGPT: Settings → Apps → Advanced settings → Developer mode on, then Create app with this URL and OAuth:

```text
https://api.clippie.ai/mcp
```

Clients that send a fixed header:

```bash
claude mcp add --transport http clippie https://api.clippie.ai/mcp \
  --header "Authorization: Bearer clp_pat_..."

# Cursor mcp.json
{
  "mcpServers": {
    "clippie": {
      "url": "https://api.clippie.ai/mcp",
      "headers": { "Authorization": "Bearer clp_pat_..." }
    }
  }
}

# VS Code .vscode/mcp.json
{
  "inputs": [
    { "type": "promptString", "id": "clippie-key", "description": "Clippie API key", "password": true }
  ],
  "servers": {
    "clippie": {
      "type": "http",
      "url": "https://api.clippie.ai/mcp",
      "headers": { "Authorization": "Bearer ${input:clippie-key}" }
    }
  }
}

# Any client
curl https://api.clippie.ai/mcp \
  -H "Authorization: Bearer $CLIPPIE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

### Server instructions

What the server tells your AI when it connects:

> Clippie makes short-form videos, images and audio for the signed-in user, from their own credits and minutes. Call get_account first. Before any tool that spends (its description says so), run estimate_cost or the tool with dryRun: true and confirm the cost with the user. Give every create call an idempotencyKey and reuse it if you retry, so a retry never charges twice. Create tools return a job: use get_job with waitSeconds (up to 50) instead of polling in a tight loop, and never report a job as finished before its status is completed. Finished jobs include links to the media and to the Clippie editor. Lists take limit and cursor; catalogs are also resources under clippie://catalog/.

### Resources

Catalogs your client can attach to the conversation without a tool call.

| URI | Title | What it is |
| --- | --- | --- |
| `clippie://account` | Account | Your plan, credit balance and minutes left. |
| `clippie://catalog/voices` | Voices | Narration voices (first 100; list_voices searches them all). |
| `clippie://catalog/caption-styles` | Caption styles | Burned-in caption styles. |
| `clippie://catalog/art-styles` | Art styles | Looks for AI Story videos. |
| `clippie://catalog/story-templates` | Story templates | Ready-made AI Story formats. |
| `clippie://catalog/motion-templates` | Motion templates | Trending motion clips for Clippie Recast (first 100). |
| `clippie://catalog/models` | AI models | AI Studio video and image models with credit prices. |
| `clippie://catalog/music` | Music | Background music tracks (first 100). |
| `clippie://jobs/{id}` | Job | One API job: status, output and cost. |

### Prompts

Guided workflows you can pick in your client. Each one checks the cost and asks you before it spends.

| Prompt | What it does | Arguments |
| --- | --- | --- |
| `make_short_video` | Topic → script → voice and style → cost check → create → wait for the finished video. | `topic`, `style` (optional), `length` (optional) |
| `recast_me` | Put a photo of you into a trending motion clip with Clippie Recast. | `photoUrl` (optional), `template` (optional) |
| `batch_shorts` | Several short videos from a list of topics, with one cost check for the whole batch. | `topics`, `style` (optional) |

## Endpoints

### Templates

Finished, narrated videos built and rendered on the server.

| Endpoint | REST | MCP tool | Cost |
| --- | --- | --- | --- |
| [Create AI Story video](https://clippie.ai/developers/docs/create-ai-story-video.md) | `POST /v1/videos/ai-story` | `create_ai_story_video` | Spends |
| [Render video](https://clippie.ai/developers/docs/render-video.md) | `POST /v1/videos/:projectId/render` | `render_video` | Spends |

### AI tools

Single generations: images, AI video, Recast, upscales, speech, transcripts and scripts.

| Endpoint | REST | MCP tool | Cost |
| --- | --- | --- | --- |
| [Generate images](https://clippie.ai/developers/docs/generate-image.md) | `POST /v1/ai/images` | `generate_image` | Spends |
| [Generate a video clip](https://clippie.ai/developers/docs/generate-video.md) | `POST /v1/ai/videos` | `generate_video` | Spends |
| [Recast a trending video](https://clippie.ai/developers/docs/recast-video.md) | `POST /v1/ai/recast` | `recast_video` | Spends |
| [Upscale an image or video](https://clippie.ai/developers/docs/upscale.md) | `POST /v1/ai/upscale` | `upscale` | Spends |
| [Text to speech](https://clippie.ai/developers/docs/text-to-speech.md) | `POST /v1/ai/speech` | `text_to_speech` | Spends |
| [Transcribe audio or video](https://clippie.ai/developers/docs/transcribe.md) | `POST /v1/ai/transcribe` | `transcribe` | Spends |
| [Write a script](https://clippie.ai/developers/docs/write-script.md) | `POST /v1/ai/scripts` | `write_script` | Free |

### Resources

Catalogs to pick from: voices, styles, templates, models, music and footage.

| Endpoint | REST | MCP tool | Cost |
| --- | --- | --- | --- |
| [List voices](https://clippie.ai/developers/docs/list-voices.md) | `GET /v1/voices` | `list_voices` | Free |
| [List caption styles](https://clippie.ai/developers/docs/list-caption-styles.md) | `GET /v1/caption-styles` | `list_caption_styles` | Free |
| [List art styles](https://clippie.ai/developers/docs/list-art-styles.md) | `GET /v1/art-styles` | `list_art_styles` | Free |
| [List story templates](https://clippie.ai/developers/docs/list-story-templates.md) | `GET /v1/story-templates` | `list_story_templates` | Free |
| [List motion templates](https://clippie.ai/developers/docs/list-motion-templates.md) | `GET /v1/motion-templates` | `list_motion_templates` | Free |
| [List AI models](https://clippie.ai/developers/docs/list-models.md) | `GET /v1/models` | `list_models` | Free |
| [List background videos](https://clippie.ai/developers/docs/list-background-videos.md) | `GET /v1/background-videos` | `list_background_videos` | Free |
| [List music](https://clippie.ai/developers/docs/list-music.md) | `GET /v1/music` | `list_music` | Free |
| [List sound effects](https://clippie.ai/developers/docs/list-sound-effects.md) | `GET /v1/sound-effects` | `list_sound_effects` | Free |
| [List languages](https://clippie.ai/developers/docs/list-languages.md) | `GET /v1/languages` | `list_languages` | Free |

### Jobs & files

Wait for and cancel jobs, check costs, and upload the files other calls use.

| Endpoint | REST | MCP tool | Cost |
| --- | --- | --- | --- |
| [List jobs](https://clippie.ai/developers/docs/list-jobs.md) | `GET /v1/jobs` | `list_jobs` | Free |
| [Get job](https://clippie.ai/developers/docs/get-job.md) | `GET /v1/jobs/:id` | `get_job` | Free |
| [Cancel job](https://clippie.ai/developers/docs/cancel-job.md) | `POST /v1/jobs/:id/cancel` | `cancel_job` | Free |
| [Estimate cost](https://clippie.ai/developers/docs/estimate-cost.md) | `POST /v1/estimate` | `estimate_cost` | Free |
| [Upload file](https://clippie.ai/developers/docs/upload-file.md) | `POST /v1/files` | `upload_file` | Free |
| [Start a large file upload](https://clippie.ai/developers/docs/create-file-upload.md) | `POST /v1/files/uploads` | REST only | Free |
| [Finish a large file upload](https://clippie.ai/developers/docs/complete-file-upload.md) | `POST /v1/files/uploads/complete` | REST only | Free |
| [Get file](https://clippie.ai/developers/docs/get-file.md) | `GET /v1/files/:id` | `get_file` | Free |

### Account

Your plan, balances and where they went.

| Endpoint | REST | MCP tool | Cost |
| --- | --- | --- | --- |
| [Get account](https://clippie.ai/developers/docs/get-account.md) | `GET /v1/me` | `get_account` | Free |
| [List credit history](https://clippie.ai/developers/docs/list-credit-history.md) | `GET /v1/credits/history` | `list_credit_history` | Free |
