Overview
GhostShorts has a REST API and an MCP server, and four templates run on both: Article to Video, AI Quiz Video, Reddit Story and Text Messages. Connect either one to Claude Code, Cursor, ChatGPT or n8n and ask for a video.
The GhostShorts API creates the same videos as the app, from your own scripts, automation tools and AI agents. API keys use your plan's videos and credits; there is no separate API plan. Create a key at Settings, API keys. Keys are shown once, so store yours safely. Working with an AI agent? Copy the guide below and paste it into the chat: it has the auth, the flow, every template and every error code, generated from the API itself.
Quick start
Base URL https://api.ghostshorts.com. Send your key as Authorization: Bearer gs_live_....
# 1. Create a key at https://ghostshorts.com/app/settings/api-keys, then:
export GS_KEY=gs_live_...
curl https://api.ghostshorts.com/v1/me -H "Authorization: Bearer $GS_KEY"# 2. Create a video (answers 202 with an id; the video is made in the background)
curl -X POST https://api.ghostshorts.com/v1/videos \
-H "Authorization: Bearer $GS_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: my-first-video-001' \
-d '{"template":"article_video","inputs":{"source":{"kind":"url","url":"https://example.com/my-post"}}}'# 3. Check on it (or pass webhookUrl when creating). downloadUrl appears when status is "completed".
curl https://api.ghostshorts.com/v1/videos/VIDEO_ID -H "Authorization: Bearer $GS_KEY"A video usually finishes in about a minute. Poll every 20 seconds or so, or pass webhookUrl when creating. The downloadUrl needs no key and works for 7 days, so anyone you share it with can download the file.
MCP server
The same videos, credits and limits as an MCP server at https://api.ghostshorts.com/mcp.
Claude.ai or Claude Desktop (no key needed): Settings, Connectors, Add custom connector. Name it GhostShorts, paste https://api.ghostshorts.com/mcp as the URL, leave the OAuth client fields empty, click Add, then Connect. Sign in to GhostShorts in the window that opens and click Allow. The connection uses your plan and credits; disconnect it any time in Settings, API keys, Connected apps.
Claude Code, with an API key, one command:
claude mcp add --transport http ghostshorts https://api.ghostshorts.com/mcp --header "Authorization: Bearer gs_live_..."Cursor: add GhostShorts to Cursor, then replace gs_live_... with your key (Settings, API keys shows this link with your key filled in). n8n and other clients: the MCP section of the guide above has the settings.
Templates
Each template has its own page with what it makes, its inputs, an example request and its full guide.
- Reddit Story Video API
reddit_story - Fake Text Message Video API
fake_text_message - Quiz Video API
quiz_video - Article to Video API
article_video
Each template's full input schema and defaults are at api.ghostshorts.com/v1/templates, and the voices, backgrounds and music to pick from at api.ghostshorts.com/v1/options. Both are public: no key needed. Check a cost before you spend it with POST /v1/estimate. Every video is a 1080x1920 (9:16) MP4.
Your own media
Pass your own background footage or music by public https URL instead of a library pick. On Text Messages that is background.ownUrl and music.ownUrl; on Reddit Story, backgroundVideoUrl and backgroundAudioUrl.
- Video: up to 300 MB and 10 minutes. Converted to 9:16 H.264 for rendering, cropped to fill.
- Audio: up to 30 MB and 10 minutes.
- Up to 20 new URLs per day. The same URL is reused for 30 days without being fetched again.
- Media is fetched before anything is charged, so a bad URL costs nothing.
Webhooks
Pass webhookUrl (public https) on POST /v1/videos. We POST JSON { id, type, createdAt, data } where type is video.completed or video.failed and data is the same video object GET /v1/videos/{id} returns. Answer with any 2xx. A failed delivery is retried 3 times.
Every delivery is signed: X-GhostShorts-Signature: t=<unix>,v1=<hex>, where v1 is HMAC-SHA256 over "<t>.<body>"keyed with your account's signing secret (webhooks.signingSecret on GET /v1/me, without its whsec_ prefix). In Node:
const crypto = require('crypto');
// rawBody: the request body as received (string), before JSON.parse
function verifyGhostShorts(rawBody, header, signingSecret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', signingSecret.replace(/^whsec_/, '')).update(parts.t + '.' + rawBody).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && expected.length === (parts.v1 || '').length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Rate limits and idempotency
- 60 requests per minute per account, of which 10 may create videos.
- Estimates, scripts and previews: 30 per hour and 100 per day.
- 3 videos in progress at once per account.
- 20 new media URLs per day.
- The public reference (templates, options, OpenAPI): 60 per minute per IP address.
Your current limits are on GET /v1/me under limits, and each response carries X-RateLimit-* headers. A 429 carries Retry-After.
Send an Idempotency-Key header on every POST, so a retry after a timeout never makes a second video. For 24 hours:
- Same key, same body: the first answer is replayed with
Idempotent-Replayed: true. ForPOST /v1/videosthe replay shows the video as it is now, with its current status and download link. Nothing is created or charged twice. - Same key, different body: 422
IDEMPOTENCY_KEY_REUSED. - Same key while the first request is still running: 409
IDEMPOTENCY_IN_PROGRESS.
Errors
Errors are RFC 9457 problem JSON (application/problem+json): { type, title, status, detail, code }, plus an errors array on INVALID_INPUT and upsell + plansUrl on every 402. Branch on code.
| Code | HTTP | What it means |
|---|---|---|
MISSING_API_KEY | 401 | No Authorization: Bearer header was sent. |
INVALID_API_KEY | 401 | The key is unknown or has been revoked. Create a new one in Settings. |
UNSUPPORTED_TOKEN | 401 | The bearer token is not a GhostShorts API key (gs_live_...). |
UNAUTHENTICATED | 401 | The request reached an endpoint without an authenticated account. Send your API key. |
SUBSCRIPTION_REQUIRED | 402 | This step needs an active plan. The response carries plansUrl. |
INSUFFICIENT_CREDITS | 402 | Not enough credits for this step. Top up or wait for your plan to renew. |
INSUFFICIENT_AI_CREDITS | 402 | Not enough AI credits for this step. |
VIDEO_ALLOWANCE_EXHAUSTED | 402 | This month's video allowance is used up. |
CLIPPING_ALLOWANCE_EXHAUSTED | 402 | This month's clipping minutes are used up. |
SOFT_CAP_REACHED | 402 | A fair-use limit for this period was reached. |
VOICE_FAIR_USE_REACHED | 402 | This month's voice allowance is used. Videos already started can still be finished; new narrations start again at the next renewal, or move up a plan. |
VIDEO_TOO_LONG | 400 | Videos can be up to 10 minutes. Split this one into parts. Also the error.code of a video whose finished length came out over 10 minutes. |
INVALID_INPUT | 400 | The body does not match the schema. The errors array names each field. Also sent as 413 for a body over 2 MB. |
INVALID_VOICE | 400 | That voice id is not available for this template. See GET /v1/options voices. |
UNKNOWN_TEMPLATE | 400 | No template with that id. See GET /v1/templates. |
TEMPLATE_NOT_AVAILABLE | 400 | The template has no such step through the API (for example POST /v1/scripts for reddit_story). The detail says what to call instead. |
NARRATION_REQUIRED | 400 | The video cannot be rendered without narration. |
INVALID_WEBHOOK_URL | 400 | webhookUrl must be a public https URL. |
SAFETY_REJECTED | 400 | The content was refused by our safety checks. Change the topic or text. |
INVALID_IDEMPOTENCY_KEY | 400 | The Idempotency-Key header is empty or longer than 255 characters. |
NOT_FOUND | 404 | No such endpoint, or no such video, project or download link on this account. |
IDEMPOTENCY_IN_PROGRESS | 409 | A request with this Idempotency-Key is still running. Retry after Retry-After. |
CANNOT_CANCEL | 409 | Only a queued video can be cancelled. |
LINK_EXPIRED | 410 | The download link is past its 7 days. GET /v1/videos/{id} again for a fresh one. |
IDEMPOTENCY_KEY_REUSED | 422 | This Idempotency-Key was already used with a different body. Use a new key for a new request. |
RATE_LIMITED | 429 | Too many requests. Wait for Retry-After seconds. |
CONCURRENCY_LIMIT | 429 | Three videos are already in progress on this account. Retry after one finishes. |
INTERNAL_ERROR | 500 | Something failed on our side. Retry; the same Idempotency-Key is safe to reuse. |
RATE_LIMITER_UNAVAILABLE | 503 | Rate limiting is briefly unavailable, so the request was refused. Retry shortly. |
SERVICE_UNAVAILABLE | 503 | The video queue is briefly unavailable. Nothing was charged; retry shortly. |
Video failure codes
When a video ends as failed or cancelled, error.code on the video (and in the video.failed webhook) is one of these. error.message always says what happened.
| Code | What it means |
|---|---|
MEDIA_FETCH_FAILED | Your background or music URL could not be downloaded: not public https, not reachable, refused, or larger than 300 MB (video) / 30 MB (audio). |
MEDIA_INVALID | The file at your URL is not a usable video or audio file. |
MEDIA_TOO_LONG | The file at your URL is longer than 10 minutes. |
MEDIA_QUOTA_EXCEEDED | More than 20 new media URLs today. Reuse a URL (free for 30 days) or wait. |
BACKGROUND_UNAVAILABLE | The background video or category you picked has no usable footage. Pick one from GET /v1/options. |
MUSIC_UNAVAILABLE | The music id is not in the library. Pick one from GET /v1/options music. |
IMAGE_UNAVAILABLE | An avatar or photo id is not an image you can use. |
SCRIPT_FAILED | The script could not be written. |
PACK_FAILED | The quiz questions could not be written. |
INGEST_FAILED | The article at your URL could not be read. Paste the text instead. |
SOURCE_TOO_SHORT | The article is too short to make a script from. |
NARRATION_FAILED | The narration could not be recorded. |
VOICE_TTS_FAILED | The voice provider failed while recording. |
RENDER_FAILED | The render failed. Create the video again. |
INTERRUPTED | Processing was interrupted. Create the video again; contact support if narration was charged. |
CANCELLED | You cancelled the video while it was queued. |
OpenAPI
The full OpenAPI 3.1 document, with every endpoint and schema, is at api.ghostshorts.com/v1/openapi.json. No key needed.
For agents
Stable plain-text URLs an agent can fetch directly. No key needed; they are generated from the API's own template registry, so they always match what the API accepts.
https://api.ghostshorts.com/v1/llms.txtThe whole API in one plain-text guide.https://api.ghostshorts.com/v1/templates/reddit_story.txtReddit Story: inputs, a full example, credits, errors.https://api.ghostshorts.com/v1/templates/fake_text_message.txtText Messages: inputs, a full example, credits, errors.https://api.ghostshorts.com/v1/templates/quiz_video.txtAI Quiz Video: inputs, a full example, credits, errors.https://api.ghostshorts.com/v1/templates/article_video.txtArticle to Video: inputs, a full example, credits, errors.https://ghostshorts.com/developers/llms.txtThe same guide, served from this site.