Quickstart
The API is included with Ultimate. On an Ultimate plan, open Dashboard > API Keys, create a key and keep it on your server. Every endpoint lives under https://www.autofeed.ai/api/v1.
1. Check the key. GET /me is cheap and tells you the balance, the plan and your limits.
curl https://www.autofeed.ai/api/v1/me \
-H "Authorization: Bearer $AUTOFEED_API_KEY"
2. Create a video. The response is a 202 with the job id and its exact cost, which is charged now.
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "ai_slideshow",
"input": {
"category": "History Facts",
"topic": "How the Romans built roads that still exist today",
"duration": "45 to 60 seconds",
"substyle": "Painting",
"format": "9:16"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-ai-slideshow"
}'
{
"id": "1c9d6c3e-1f5b-4d2e-9c31-2b8b0f0f9a11",
"type": "ai_slideshow",
"status": "processing",
"costInCredits": 1200,
"createdAt": "2026-11-02T12:00:00.000Z"
}
3. Collect it. Add a webhookUrl to the create call and we POST the finished generation to it (see Webhooks), or poll:
curl https://www.autofeed.ai/api/v1/videos/1c9d6c3e-1f5b-4d2e-9c31-2b8b0f0f9a11 \
-H "Authorization: Bearer $AUTOFEED_API_KEY"
{
"id": "1c9d6c3e-1f5b-4d2e-9c31-2b8b0f0f9a11",
"type": "ai_slideshow",
"status": "completed",
"error": null,
"costInCredits": 1200,
"createdAt": "2026-11-02T12:00:00.000Z",
"completedAt": "2026-11-02T12:03:41.000Z",
"url": "https://.../1c9d6c3e-1f5b-4d2e-9c31-2b8b0f0f9a11.mp4"
}
4. Download the file. url is a plain MP4 link. Media is deleted after 14 days, so copy it to your own storage.
The same create from Node.js 18 or later:
const res = await fetch('https://www.autofeed.ai/api/v1/videos', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.AUTOFEED_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'ai_slideshow',
input: {"category":"Write Your Own Story","topic":"Three habits that quietly double your focus","duration":"15 to 30 seconds","format":"9:16"},
// Safe to retry: a repeat of this key returns the original job.
idempotencyKey: `order-${orderId}`
})
});
const body = await res.json();
if (res.status !== 202) throw new Error(body.error.code);
console.log(body.id, body.costInCredits);
Authentication
Send the key as a bearer token on every request:
Authorization: Bearer af_live_...
The key is shown once, when you create it. We store only a hash, so a lost key cannot be recovered: revoke it and create another.
Keys are server-side only. A key spends your credits. Never put one in a browser, a mobile app or a public repository. If your own users trigger videos, queue their requests in your backend and call the API from there. CORS is open for tooling; that does not make a key safe in a browser.
Every authentication failure (no header, unknown, revoked or expired key) is the same 401 UNAUTHORIZED. A valid key on a plan without API access gets 403 API_ACCESS_REQUIRED everywhere except GET /me and GET /types, which answer so you can see why.
Billing and credits
The API is prepaid and included with Ultimate. There is no separate API plan and no per-call fee: each video costs the same credits it costs in the dashboard, priced by the same function.
| Ultimate size | Credits / month | $ / 1k credits |
|---|---|---|
| $249/mo (Ultimate) | 180k | $1.38 |
| $449/mo | 500k | $0.90 |
| $799/mo | 1M | $0.80 |
| $1,399/mo | 2M | $0.70 |
A bigger size raises both the monthly credits and the API limits (see Limits). Switch sizes from the pricing page while signed in.
One balance. API videos, dashboard videos and your autoposting series all spend the same credit balance. Nothing is held back for autoposting, so a busy API integration can leave a series without credits for its next post. Size your plan for both.
When you are charged. At accept: the 202 carries costInCredits and the balance is already debited. A generation that fails refunds its credits automatically. A 202 that replays an idempotencyKey charges nothing, because the first call already did.
When the balance is short, a create answers 402 INSUFFICIENT_CREDITS before anything is queued. Nothing is charged, and the same request can be sent again once the balance covers it:
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "This request costs 1000 credits and your balance is 250 (short by 750). Buy a credit pack at topUpUrl, or move to a bigger Ultimate size at upgradeUrl. Nothing was charged.",
"creditsRequired": 1000,
"creditsAvailable": 250,
"topUpUrl": "https://www.autofeed.ai/dashboard/billing",
"upgradeUrl": "https://www.autofeed.ai/pricing"
}
}
Credit packs top up the same balance.
Unpaid invoice. While the subscription is past due, every endpoint except GET /me and GET /types answers 402 PAYMENT_REQUIRED. Pay the open invoice from Billing in the dashboard and calls work again. With no subscription at all, a create is 402 NO_ACTIVE_SUBSCRIPTION.
YouTube Clips is priced from a fixed source-video length plus a per-clip charge, whatever the real video length. The price in the 202 is final.
Pay-as-you-go (billing usage past your balance instead of a 402) is planned, with a spending cap you set. Until then the API stops at zero.
Lifecycle
POST /videos -> 202 { id, status: "processing", costInCredits } charged now
GET /videos/{id} -> 200 { status: "processing" | "completed" | "failed", url, ... }
POST <your webhookUrl> once, when the generation finishes either way
A create returns in well under a second. Script writing, images, voice and the render all happen afterwards.
| status | meaning |
|---|---|
processing | Accepted and running. Keep waiting. |
completed | Done. url (or clipUrls for yt_clips) is set. |
failed | Finished without a video. The credits are already refunded. |
Branch on status, not on error. A failed generation usually carries a human-readable error, but it can be null. A client that waits for error to appear would poll a failed job forever.
There is no queued status: a generation is created already running, and its status never goes backwards.
Time limits. A generation still running after 25 minutes (60 minutes for yt_clips) is failed and refunded. Most finish in 1 to 5 minutes; animatedImages, long durations, slow AI video models and long YouTube sources take longest.
Media expires. Rendered files are public links (anyone with the URL can download them) and are deleted 14 days after the render. Copy each file to your own storage when its webhook arrives.
Retries and idempotency
Send an idempotencyKey (1 to 255 characters) on every create. Then a retry after a timeout cannot charge you twice. Keys are scoped to your account.
| you send | you get | charged? |
|---|---|---|
| the same key and the same request | 202 with the original job | once, on the first call |
| the same key and a different request | 409 IDEMPOTENCY_KEY_REUSED | no |
| the same key after an attempt that could not be resolved | 409 IDEMPOTENCY_KEY_UNRESOLVED | unknown, see below |
"The same request" means the same type, input and webhookUrl after parsing, so key order and fields left at their defaults do not matter.
A replayed 202 is the original accept body, with status: "processing" however far the job has got. Read live status from GET /videos/{id}.
IDEMPOTENCY_KEY_UNRESOLVED means an earlier call on that key reached the charge and then stopped, so we cannot prove whether it was debited. Send the request again under a new key, and contact support with the X-Request-Id so the first attempt is settled by hand.
Without a key, every create is a new video at a new price.
Endpoints
| endpoint | does |
|---|---|
GET /me | Balance, plan, API access, past-due flag, your rate limits and in-flight count. |
GET /types | Every video type with its input schema, an example and prices, plus every AI video model with each price. Built from the same code that validates and charges. |
POST /videos | Create a generation. Body: type, input, optional webhookUrl and idempotencyKey. Unknown fields are a 400. |
GET /videos/{id} | One generation. |
GET /videos?ids=a,b,c | Up to 50 generations by id for the price of one read. Ids that are not on your account come back in notFound. |
GET /videos | Your generations, newest first. limit 1 to 50 (default 20), cursor, type, status. |
Paging. Pass the previous page's nextCursor back unchanged until it is null. A short page is not the end of the list; nextCursor: null is. The cursor is opaque. Near a page edge a row can appear on two pages, so deduplicate on id.
The list includes videos made in the dashboard on the same account, not only API ones.
yt_clips returns several files. Its generation has url: null and a clipUrls array instead. A clip that fails to render is left out, so the list can be shorter than clipCount.
Every field of every request and response is in the API reference and in the machine-readable OpenAPI document.
Video types
There are 8 types. Every input is strict: an unknown field is a 400 that names it, and every optional field has a default. The prices below are computed by the function that charges; GET /types returns the same numbers.
AI Slideshow (ai_slideshow)
A topic in, a narrated slideshow of AI images with word-timed captions out.
- Price rises with duration, and much more with animatedImages, which turns every image into a short AI clip and adds several minutes of render time.
Cost: 1000 credits for the example. Other inputs:
- duration "15 to 30 seconds": 1000 credits
- duration "45 to 60 seconds": 1200 credits
- duration "60 to 90 seconds": 1500 credits
- duration "15 to 30 seconds", animatedImages: true: 2400 credits
- duration "45 to 60 seconds", animatedImages: true: 4800 credits
- duration "60 to 90 seconds", animatedImages: true: 7200 credits
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "ai_slideshow",
"input": {
"category": "History Facts",
"topic": "How the Romans built roads that still exist today",
"duration": "45 to 60 seconds",
"substyle": "Painting",
"format": "9:16"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-ai-slideshow"
}'
| field | type | default | notes |
|---|---|---|---|
category | one of 28 values | "Write Your Own Story" | The story type. "Write Your Own Story" makes topic the whole brief, paragraph breaks kept. |
topic | string (max 4000) | - | Optional steer for the script, or the whole brief under "Write Your Own Story". |
substyle | one of 7 values | "Realistic" | |
language | one of 39 values | "English (USA)" | A display name such as "English (USA)", not a language code. |
music | string (max 255) | "none" | |
voice | string | "pNInz6obpgDQGcFmaJgB" | |
captionStyle | one of 14 values | "YouTuber" | |
customCaptions | object | - | Required when captionStyle is "Custom". Any field you leave out comes from the preset. |
captionSize | "Small" | "Medium" | "Large" | "Medium" | |
captionPosition | "Top" | "Center" | "Bottom" | "Bottom" | |
particles | one of 8 values | "None" | |
duration | "15 to 30 seconds" | "45 to 60 seconds" | "60 to 90 seconds" | "15 to 30 seconds" | Price-bearing: longer bands cost more credits. |
format | "9:16" | "16:9" | "1:1" | "9:16" | |
cameraShake | boolean | true | |
animatedImages | boolean | false | Animates each image. Price-bearing and much slower, so it is off by default. |
Reddit Stories (reddit_stories)
A subreddit style in, an original narrated story over gameplay footage out.
- Always vertical (9:16): there is no format, captionSize or captionPosition.
- The background clip is drawn server-side from the category's pool and cannot be chosen.
Cost: 1000 credits, whatever the input.
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "reddit_stories",
"input": {
"subreddit": "AskReddit",
"commentsCount": 3,
"backgroundCategory": "Minecraft",
"captionStyle": "YouTuber"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-reddit-stories"
}'
| field | type | default | notes |
|---|---|---|---|
author | string (max 100) | "AutoFeed.ai" | |
subreddit | string | "AskReddit" | The subreddit whose style the story is written in. Any name, not only the dashboard suggestions. |
flairs | string (max 500) | "" (none) | |
commentsCount | integer 0-10 | 3 | How many replies are read after the story. Capped at 10, because each one adds narration at a flat price. |
backgroundCategory | "Minecraft" | "GTA V" | "Subway Surfers" | "Fortnite" | "Satisfying" | "Minecraft" | The gameplay pool. The clip itself is picked at random from that pool on our side. |
music | string (max 255) | "none" | |
voice | string | "JBFqnCBsd6RMkjVDRZzb" | |
captionStyle | one of 14 values | "YouTuber" | |
customCaptions | object | - | Required when captionStyle is "Custom". Any field you leave out comes from the preset. |
Would You Rather (would_you_rather)
A topic in, a voiced "would you rather" video with an AI image per option out.
- The price does not change with numberOfComparisons.
Cost: 1000 credits for the example. Other inputs:
- numberOfComparisons: 10: 1000 credits
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "would_you_rather",
"input": {
"topic": "Superpowers with a catch",
"numberOfComparisons": 8,
"format": "9:16"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-would-you-rather"
}'
| field | type | default | notes |
|---|---|---|---|
topic (required) | string (max 2000) | - | Required. The theme every matchup is written around. |
numberOfComparisons | integer 1-10 | 8 | Enforced at 1-10 by the API. The format is flat-priced, so the count does not change the cost. |
removeBackground | boolean | false | Removes the background from each generated image. Doubles the image work, same price. |
music | string (max 255) | "solitude" | |
voice | string | "pNInz6obpgDQGcFmaJgB" | |
format | "9:16" | "16:9" | "1:1" | "9:16" |
Pick One (pick_one)
A topic in, a "pick one" grid video of AI images out. No narration.
- There is no voice: Pick One has no narration.
- Price rises with numberOfImages beyond the included ones, and per image when animatedImages is on.
Cost: 1000 credits for the example. Other inputs:
- numberOfImages: 3: 1000 credits
- numberOfImages: 10: 1500 credits
- numberOfImages: 5, animatedImages: true: 3000 credits
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "pick_one",
"input": {
"topic": "Pick your dream supercar",
"numberOfImages": 5,
"format": "9:16"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-pick-one"
}'
| field | type | default | notes |
|---|---|---|---|
topic (required) | string (max 2000) | - | Required. What the options are about. |
numberOfImages | integer 3-10 | 5 | Price-bearing: each image adds credits. |
substyle | one of 7 values | "Realistic" | Accepted for compatibility with AI Slideshow bodies; the renderer no longer reads it. |
music | string (max 255) | "/ugc-sounds/moods/let-go-slowed.mp3" | |
format | "9:16" | "16:9" | "1:1" | "9:16" | |
animatedImages | boolean | false | Price-bearing: adds credits per image. |
zoomEffect | boolean | true |
Fake Text (fake_text)
A topic in, a voiced text-message conversation video out.
- Two voices: voiceThem (left, grey) and voiceMe (right, blue). The singular "voice" field is rejected.
- The price does not change with numberOfMessages.
Cost: 1000 credits, whatever the input.
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "fake_text",
"input": {
"topic": "A son texts his mom that he scratched her new car",
"numberOfMessages": 10,
"theme": "dark"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-fake-text"
}'
| field | type | default | notes |
|---|---|---|---|
topic (required) | string (max 2000) | - | Required. The premise the conversation is written from. |
numberOfMessages | integer 6-14 | 10 | |
historyMode | "auto" | "always" | "never" | "auto" | Whether to show earlier messages above the story: "auto", "always" or "never". |
backgroundCategory | "None" | "Minecraft" | "GTA V" | "Subway Surfers" | "Fortnite" | "Satisfying" | "None" | "None" is the plain phone canvas; the others put a background video behind it. |
theme | "dark" | "light" | "dark" | |
music | string (max 255) | "" (none) | |
voiceThem | string | "cgSgspJ2msm6clMCkdW9" | Voice for the other person (left, grey bubbles). |
voiceMe | string | "TX3LPaxmHKxFdv7VOQHJ" | Voice for the phone owner (right, blue bubbles). |
format | "9:16" | "16:9" | "1:1" | "9:16" |
UGC Ads (ugc)
A hook line in, an avatar reaction clip followed by your product demo out.
- Always 1080x1920: there is no format.
- demoSrc is fetched by the renderer, so it must be a public https URL. Leave it empty for a reaction-only clip.
- No script writing and no voiceover, so this is the fastest type.
Cost: 1000 credits, whatever the input.
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "ugc",
"input": {
"hook": "I cancelled four subscriptions after finding this app",
"avatarSrc": "/ugc-templates/1.mp4",
"demoSrc": "https://example.com/media/app-demo.mp4"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-ugc-ads"
}'
| field | type | default | notes |
|---|---|---|---|
hook (required) | string (max 300) | - | Required. The line shown on screen over the avatar. |
avatarSrc | one of 58 values | "/ugc-templates/1.mp4" | Which avatar clip to use, by its path. The dashboard shows every option. |
demoSrc (required) | string (max 2048) | - | An https URL to your product footage. Empty means no demo. Private and local addresses are rejected. |
soundId | one of 77 values | "none" | |
hookAlignment | "start" | "center" | "end" | "center" | |
hookConfig | object | preset | Text styling for the hook. Any field you leave out comes from the preset. |
AI Video (ai_video)
A prompt (and optionally a source image) in, a clip from a text-to-video or image-to-video model out.
- duration, resolution and format must each be in the chosen model's own lists. Not every resolution is sold at every duration. GET /v1/types and the table in the spec list every combination with its price.
- imageUrl is required for image_to_video and rejected for text_to_video. It is fetched by the model host, so it must be a public https URL.
Cost: the price of the model/resolution/duration cell, 400 to 18000 credits. Every cell is in the table below and in GET /types.
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "ai_video",
"input": {
"model": "Wan 3.0",
"mode": "text_to_video",
"textPrompt": "A slow dolly shot through a neon-lit night market in the rain, steam rising from food stalls",
"duration": 5,
"resolution": "720p",
"format": "9:16"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-ai-video"
}'
| field | type | default | notes |
|---|---|---|---|
model (required) | one of 28 values | - | The model label, exactly as listed in the table below. |
mode (required) | "text_to_video" | "image_to_video" | - | "text_to_video" or "image_to_video". Not every model supports both. |
textPrompt (required) | string (max 5000) | - | |
imageUrl | string (max 2048) | - | Required for image_to_video, rejected for text_to_video. A public https URL. |
duration (required) | integer > 0 | - | Seconds. Must be one of the model's own lengths. |
resolution (required) | string (max 32) | - | Must be one of the model's own resolutions. |
format | "9:16" | "16:9" | "1:1" | - | Defaults to the model's first supported ratio. |
Models, and the credits for each resolution and duration they sell. Any other combination is a 400 naming duration.
| model | modes | formats | credits by resolution and duration |
|---|---|---|---|
| Google Veo 3.1 Fast | text_to_video, image_to_video | 9:16, 16:9 | 720p: 4s 2400, 6s 3600, 8s 4800; 1080p: 4s 2400, 6s 3600, 8s 4800 |
| Google Veo 3.1 | text_to_video, image_to_video | 9:16, 16:9 | 720p: 4s 6400, 6s 9600, 8s 12800; 1080p: 4s 6400, 6s 9600, 8s 12800 |
| Google Veo 3.1 Lite | text_to_video, image_to_video | 9:16, 16:9 | 720p: 4s 800, 6s 1200, 8s 1600; 1080p: 8s 2560 |
| Kling 3.0 | text_to_video, image_to_video | 9:16, 16:9, 1:1 | standard: 5s 3360, 10s 6720; pro: 5s 4480, 10s 8960 |
| Kling 2.6 | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 1080p: 5s 2800, 10s 5600 |
| Kling 2.5 Turbo Pro | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 1080p: 5s 1400, 10s 2800 |
| Seedance 2.0 | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 3600, 10s 7200; 1080p: 5s 9000, 10s 18000 |
| Seedance 2.0 Fast | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 3000, 10s 6000; 480p: 5s 1400, 10s 2800 |
| Seedance 1 Pro | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 1200, 10s 2400; 1080p: 5s 3000, 10s 6000 |
| Seedance 1 Pro Fast | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 500, 10s 1000; 1080p: 5s 1200, 10s 2400 |
| Seedance 1 Lite | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 720, 10s 1440; 1080p: 5s 1440, 10s 2880 |
| Hailuo 2.3 | text_to_video, image_to_video | 16:9 | 768p: 6s 1120, 10s 2240; 1080p: 6s 1960 |
| Hailuo 2.3 Fast | image_to_video | 16:9 | 768p: 6s 760, 10s 1280; 1080p: 6s 1320 |
| Hailuo 02 | text_to_video, image_to_video | 16:9 | 768p: 6s 1080, 10s 1800; 1080p: 6s 1920 |
| Hailuo 02 Fast | image_to_video | 16:9 | 512p: 6s 400, 10s 600 |
| Runway Gen-4.5 | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 2400, 10s 4800 |
| Grok Imagine Video | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 1000, 10s 2000 |
| Grok Imagine Video 1.5 | image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 1600, 10s 3200 |
| Vidu Q3 Pro | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 3000, 10s 6000; 1080p: 5s 3200, 10s 6400 |
| Vidu Q3 Turbo | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 1200, 10s 2400; 1080p: 5s 1600, 10s 3200 |
| PixVerse v6 | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 1800, 8s 2880, 10s 3600; 1080p: 5s 3600, 8s 5760, 10s 7200 |
| PixVerse v5 | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 1600, 8s 3200; 1080p: 5s 3200, 8s 5120 |
| Luma Ray 3.2 | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 1200; 1080p: 5s 4800 |
| Wan 3.0 | text_to_video, image_to_video | 9:16, 16:9, 1:1 | 720p: 5s 1000, 10s 2000; 1080p: 5s 2000, 10s 4000 |
| Wan Video 2.2 T2V Fast | text_to_video | 9:16, 16:9 | 720p: 5s 400 |
| Wan Video 2.2 I2V Fast | image_to_video | 9:16, 16:9 | 720p: 5s 400 |
| LTX-2.3 Pro | text_to_video, image_to_video | 9:16, 16:9 | 1080p: 6s 1920, 8s 2560, 10s 3200 |
| LTX-2.5 Fast | text_to_video, image_to_video | 9:16, 16:9 | 720p: 6s 720, 8s 960, 10s 1200; 1080p: 6s 1440, 8s 1920, 10s 2400 |
YouTube Clips (yt_clips)
A YouTube URL in, several captioned vertical clips cut from it out.
- Returns several videos: read clipUrls. url is always null for this type.
- Priced for a fixed-length source video plus a per-clip charge, whatever the real length; the price quoted at accept is final.
- Limited to a small number of API jobs per day across all customers while in early access; over the limit is a 429 CAPACITY with nothing charged.
Cost: 6500 credits for the example. Other inputs:
- clipCount: 1: 4500 credits
- clipCount: 8: 8000 credits
curl -X POST https://www.autofeed.ai/api/v1/videos \
-H "Authorization: Bearer $AUTOFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "yt_clips",
"input": {
"youtubeUrl": "https://www.youtube.com/watch?v=aqz-KE-bpKQ",
"clipCount": 5,
"enableHook": true,
"format": "9:16"
},
"webhookUrl": "https://example.com/webhooks/autofeed",
"idempotencyKey": "order-1042-youtube-clips"
}'
| field | type | default | notes |
|---|---|---|---|
youtubeUrl (required) | string (max 2048) | - | Required. A public YouTube video URL. |
clipCount | integer 1-8 | 5 | Price-bearing: each clip adds credits. |
captionStyle | one of 14 values | "YouTuber" | |
customCaptions | object | - | |
format | "9:16" | "16:9" | "1:1" | "9:16" | |
enableHook | boolean | true | |
hookSeconds | number 2-10 | "full" | 5 | How long the hook overlay stays up, or "full" for the whole clip. |
hookStyle | object | preset | |
swapPanels | boolean | true | |
clipDuration | "Under 30 seconds" | "30 to 45 seconds" | "45 to 60 seconds" | "60 to 90 seconds" | "30 to 45 seconds" | |
captionSize | "Small" | "Medium" | "Large" | "Medium" | |
captionPosition | "Top" | "Center" | "Bottom" | "Bottom" | |
videoFit | "Cover (crop to fill)" | "Contain (show full video)" | "Contain (show full video)" | |
splitScreen | boolean | false | |
splitScreenBackground | "Random" | "Minecraft" | "GTA V" | "Subway Surfers" | "Fortnite" | "Satisfying" | "Random" | A gameplay pool for split screen, or the random option to pick one per clip. |
Errors
Every error has the same shape. details appears only on VALIDATION_ERROR.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body failed validation.",
"details": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"input",
"topic"
],
"message": "Required"
}
]
}
}
| status | code | meaning | what to do |
|---|---|---|---|
| 400 | VALIDATION_ERROR | The body or a query value failed validation, or an unknown field was sent. | details lists each problem with its path. |
| 401 | UNAUTHORIZED | No key, or an unknown, revoked or expired one. | Check the header; create a new key if needed. |
| 402 | INSUFFICIENT_CREDITS | The balance is short of this request. Nothing was charged. | Top up at topUpUrl or upgrade at upgradeUrl, then retry. |
| 402 | NO_ACTIVE_SUBSCRIPTION | There is no subscription to spend credits under. | Subscribe to Ultimate. |
| 402 | PAYMENT_REQUIRED | The subscription has an unpaid invoice. | Pay it from Billing in the dashboard. |
| 403 | API_ACCESS_REQUIRED | The plan does not include the API. | Move to Ultimate. |
| 404 | NOT_FOUND | No generation with that id on this account. | - |
| 409 | IDEMPOTENCY_KEY_REUSED | The key was already used for a different request. | Resend the original body, or use a new key. |
| 409 | IDEMPOTENCY_KEY_UNRESOLVED | An earlier attempt on this key could not be resolved. | Use a new key and contact support with the request id. |
| 429 | RATE_LIMITED | Over your requests per minute. | Wait Retry-After seconds. |
| 429 | QUEUE_FULL | Your account is at its in-flight limit. | Wait for a video to finish (Retry-After 30). |
| 429 | CAPACITY | Every API slot across all customers is busy, or the YouTube Clips daily allowance is spent. | Retry after Retry-After (60 s; 3600 s for the daily allowance). |
| 500 | INTERNAL_ERROR | Our fault. | Retry, and quote the X-Request-Id if it persists. |
| 503 | TEMPORARILY_UNAVAILABLE | We could not count or account for the request, so we refused it. | Retry after Retry-After. Nothing was charged. |
An error on a create charges nothing. The one exception is IDEMPOTENCY_KEY_UNRESOLVED, which exists because we cannot tell. Retry only 429, 500 and 503; the other 4xx answers will not change on their own.
Every response carries X-Request-Id. Quote it when you contact support.
Limits
Limits are per AutoFeed account, shared by all of its keys, and rise with the Ultimate size. Windows are 60 seconds.
| Ultimate size | creates/min | reads/min | in flight |
|---|---|---|---|
| 180k credits/month | 20 | 120 | 10 |
| 500k credits/month | 40 | 240 | 25 |
| 1M credits/month | 60 | 360 | 50 |
| 2M credits/month | 100 | 600 | 100 |
In flight counts generations accepted and not yet finished. A create over it is 429 QUEUE_FULL, with nothing charged. GET /me shows your current count.
Shared capacity. At most 40 API generations run at once across all customers, and YouTube Clips is limited to 5 API jobs a day across all customers while in early access. Over either is 429 CAPACITY.
Every authenticated response carries the limiter headers:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 19
X-RateLimit-Reset: 1793620060
Retry-After: 37 (on 429 and 503 only)
X-InFlight-Count: 3 (on creates)
X-InFlight-Limit: 10
If the limiter itself is down, reads are let through and creates are refused with 503 TEMPORARILY_UNAVAILABLE and Retry-After: 10, so a create is never accepted uncounted.
Prefer webhooks to polling, and batch status checks with GET /videos?ids=.
Webhooks
Set webhookUrl on a create and we POST to it once the generation finishes, whether it completed or failed. The URL must be https:// on the default port, with no credentials, on a public host. It is checked at accept and again at delivery.
POST /webhooks/autofeed HTTP/1.1
Content-Type: application/json
User-Agent: AutoFeed-Webhooks/1
X-Autofeed-Event: video.completed
X-Autofeed-Delivery: 9f1b0f60-3d2a-4a17-8a0b-5f4c2f9f7c31
X-Autofeed-Signature: t=1793620000,v1=6a3c9f1e...
The body is the same object as GET /videos/{id}, with completedAt set. X-Autofeed-Event is always video.completed, failures included: read status.
{
"id": "1c9d6c3e-1f5b-4d2e-9c31-2b8b0f0f9a11",
"type": "ai_slideshow",
"status": "completed",
"error": null,
"costInCredits": 1200,
"createdAt": "2026-11-02T12:00:00.000Z",
"completedAt": "2026-11-02T12:03:41.000Z",
"url": "https://.../1c9d6c3e-1f5b-4d2e-9c31-2b8b0f0f9a11.mp4"
}
Delivery
- Answer
2xxwithin 5 seconds, then do your work. We read only the status code. 5xxand429are retried with backoff, up to 5 attempts. Any other4xxand any redirect is final.- If every attempt is lost on our side, an hourly sweep re-sends the completion. That backstop can arrive an hour or two late, so
GET /videos/{id}remains the source of truth. - Revoking the key that created a generation stops that generation's deliveries. An expired key still gets them.
At-least-once, so deduplicate on the body's id. A retry carries an identical body but a new X-Autofeed-Delivery and a new signature.
Verify the signature. X-Autofeed-Signature is t=<unix seconds>,v1=<hex>, where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). The secret (whsec_...) is shown for each active key at Dashboard > API Keys. Hash the raw bytes before parsing JSON, and reject a t more than 5 minutes old.
const crypto = require('crypto');
// Returns false for anything malformed and never throws: the endpoint is
// public, so an exception here is one anybody can trigger.
function verifyAutofeedSignature(secret, header, rawBody, toleranceSeconds = 300) {
if (typeof header !== 'string') return false;
let t = null;
let v1 = null;
for (const part of header.split(',')) {
const eq = part.indexOf('=');
if (eq < 0) continue;
const key = part.slice(0, eq).trim();
const value = part.slice(eq + 1).trim();
if (key === 't' && t === null) t = value;
else if (key === 'v1' && v1 === null) v1 = value;
}
if (t === null || !/^[0-9]{1,15}$/.test(t)) return false;
if (v1 === null || !/^[0-9a-fA-F]{64}$/.test(v1)) return false;
if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'));
}
In Express:
app.post(
'/webhooks/autofeed',
express.raw({ type: 'application/json' }), // the RAW body is what is signed
(req, res) => {
const raw = req.body.toString('utf8');
const ok = verifyAutofeedSignature(
process.env.AUTOFEED_WEBHOOK_SECRET,
req.get('X-Autofeed-Signature'),
raw
);
if (!ok) return res.status(400).send('bad signature');
const event = JSON.parse(raw);
res.status(200).end(); // answer within 5 s, then do the work
// Deliveries are at-least-once: skip event.id if you have already handled it.
handle(event).catch((err) => console.error('autofeed webhook', err));
}
);
Rotating the secret. It belongs to the key and cannot change on its own. Create a new key, point your receiver at its secret, then revoke the old key. Deliveries for jobs still running under the old key stop, so read those from GET /videos/{id}.
What the API does not do
- No posting. The API returns files. Scheduled posting to social accounts is a dashboard feature (series and autoposting).
- No custom scripts. You give the topic or brief; the AI writes the script, title and image prompts.
- No cancel. A submitted generation runs to the end.
- No browser keys. Call the API from your server only.
Machine-readable docs
- OpenAPI 3.1 document, generated from the code that validates and charges. Point a client generator or an AI agent at it.
- Interactive API reference built from it.
GET https://www.autofeed.ai/api/v1/types: the live catalog of types, schemas and prices.- This guide as Markdown.