API Reference

API Reference

Generate AI videos programmatically. Submit a prompt, poll for completion, and receive a direct video URL — all via REST.

Base URL: https://yoh.app

Code examples will use the placeholder until you sign in.

Quick Start

Create your first video in three API calls: generate → poll → download.

1. Generate a video
curl -X POST https://yoh.app/api/v1/stories/generate \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "A dragon teaches a young knight how to fly", "aspectRatio": "16:9" }'
Response — job enqueued
{
  "jobId": "cmnk8wu2q0001q3vpmqv93nmy",
  "status": "queued",
  "pollUrl": "/api/v1/stories/jobs/cmnk8wu2q0001q3vpmqv93nmy"
}
2. Poll until completed
curl https://yoh.app/api/v1/stories/jobs/cmnk8wu2q0001q3vpmqv93nmy \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response — job completed
{
  "status": "completed", "progress": 100,
  "storyId": "cmnk8xxnx000004lbd7siief7",
  "videoUrl": "https://cdn.example.com/video/clip.mp4",
  "error": null
}

That's it. The videoUrl is a publicly accessible URL you can download, stream, or share immediately.

Authentication

All API requests must include your API key in the header using the scheme.

Authorization: Bearer sk_live_YOUR_API_KEY

API keys begin with . You can create and manage keys from the .

Keep your API keys secret. Do not expose them in client-side code or public repositories. If a key is compromised, revoke it immediately from the API Keys page.

Scopes & Credits

Each API key is granted one or more scopes that control which endpoints it can access. Story generation costs credits per job. Credits are refunded if the job fails after all retries.

ScopeGrants access to
stories:generatePOST /api/v1/stories/generate, POST /api/v1/series-assets/ensure-character-image
stories:readGET /api/v1/stories, GET /api/v1/stories/:id, GET /api/v1/stories/jobs/:id
assets:readGET .../characters, .../scenes, .../items, .../clips

Generate a Video

Enqueue a video generation job

POST/api/v1/stories/generatescope: stories:generate

Submits a prompt and returns a immediately. The video is generated asynchronously. Poll to track progress.

Request body

FieldTypeStatusDescription
promptstringrequiredStory description, max 5000 characters.
aspectRatiostringoptionalOne of , , , . Default: .
renderVideobooleanoptional — returns the first AI clip (fast, ~10 min). — renders all clips into one composed MP4 via Remotion (~12–15 min).
titlestringoptionalDisplay name for the story.
contentTypestringoptionalStory format category. One of cinematic_story, news_analysis, music_video, persona_channel, youtube_clone, ad_spot. Default: cinematic_story.
localestringoptionalLanguage code, e.g. , . Default: .
seriesIdstringoptionalAttach to an existing series for consistent character visuals.

Response 202 Accepted

{ "jobId": "cmnk8wu2q0001q3vpmqv93nmy", "status": "queued", "pollUrl": "/api/v1/stories/jobs/..." }

Poll Job Status

Get job status

GET/api/v1/stories/jobs/:jobIdscope: stories:read

Poll this endpoint after calling . The job typically takes 8–15 minutes depending on story length and whether is enabled. Recommend polling every .

Job status lifecycle

queuedgenerating_storygenerating_videosrendering_videocompletedorfailed

Response fields

FieldTypeDescription
jobIdstringUnique job identifier.
statusstringCurrent status. See lifecycle above.
progressnumber0–100 integer indicating completion percentage.
storyIdstring|nullSet once story generation completes. Use to fetch assets.
renderVideobooleanWhether a composed final MP4 was requested.
videoUrlstring|nullAbsolute video URL when status is completed. Either a composed MP4 (renderVideo: true) or the first clip's AI video (renderVideo: false).
errorstring|nullHuman-readable error message on failure. Always null on success.
createdAt / startedAt / completedAtstring (ISO)Timestamps.

Stories

List stories

GET/api/v1/storiesscope: stories:read

Returns a paginated list of your stories.

Query parameters

ParamDefaultDescription
page1Page number (≥ 1).
limit20Results per page (1–100).
statusFilter by story status.

Get a story

GET/api/v1/stories/:storyIdscope: stories:read

Returns full metadata for a single story including counts of its characters, scenes, items, and clips.

{
  "id": "cmnk9cibl0004q3vpijkqfwl4",
  "title": "A robot chef cooks in a futuristic kitchen",
  "status": "completed", "aspectRatio": "9:16", "locale": "en",
  "counts": { "characters": 3, "scenes": 4, "items": 6, "clips": 6 },
  "createdAt": "2026-04-04T11:37:04.662Z"
}

Assets overview

After a job completes you can fetch all assets generated for the story using the returned by the job. All asset endpoints require the scope.

GET /api/v1/stories/:id/characters

Characters — name, description, role, imageUrl, voice info

GET /api/v1/stories/:id/scenes

Scenes — description, timeOfDay, weather, lighting, imageUrl

GET /api/v1/stories/:id/items

Props / items — description, significance, imageUrl

GET /api/v1/stories/:id/clips

Clips — narration, duration, videoUrl, imageUrl, audioUrl

Characters

List characters

GET/api/v1/stories/:storyId/charactersscope: assets:read
{
  "data": [{
    "id": "char_abc123", "name": "Chef Axon", "role": "protagonist",
    "description": "A robot chef with a warm personality",
    "imageUrl": "https://cdn.example.com/characters/chef-axon.jpg",
    "voiceName": "Neutral-EN", "order": 0
  }]
}

Clips & Videos

List clips

GET/api/v1/stories/:storyId/clipsscope: assets:read

Each clip is a scene segment with its own AI-generated video, still image, and audio. All URL fields are absolute and publicly accessible.

{
  "data": [{
    "id": "clip_001", "order": 0, "narration": "In the year 2157...",
    "duration": 8,
    "videoUrl": "https://cdn.example.com/generated/video/clip1.mp4",
    "imageUrl": "https://cdn.example.com/generated/image/clip1.jpg",
    "audioUrl": null, "status": "completed"
  }]
}

When is false, the job's points to the first clip's video. Use this endpoint to get the video URL for every individual clip.

Series Assets

Register a character image for a series

POST/api/v1/series-assets/ensure-character-imagescope: stories:generate

Uploads and persists a character reference image for a series so the AI uses a consistent face/look across all stories in that series. This operation is — calling it multiple times with the same character name is safe.

FieldTypeDescription
seriesIdstringID of the series to attach the character to.
characterNamestringThe character's name (used as a unique key within the series).
imageUrlstringPublicly accessible URL of the character reference image.

Manage API Keys

API key management endpoints use (logged-in user cookie), not an API key. Use the for a browser-based workflow, or call these endpoints programmatically from your backend with a valid session.

List API keys

GET/api/v1/api-keysscope: session
{
  "data": [{
    "id": "key_abc123", "name": "Production",
    "keyPrefix": "sk_live_7WH3",
    "scopes": ["stories:generate", "stories:read", "assets:read"],
    "revokedAt": null, "createdAt": "2026-04-03T10:00:00.000Z"
  }]
}

Create an API key

POST/api/v1/api-keysscope: session
// Response — full key shown once
{
  "id": "key_abc123",
  "key": "sk_live_...",
  "name": "My integration",
  "keyPrefix": "sk_live_7WH3",
  "scopes": ["stories:generate", "stories:read", "assets:read"]
}

Revoke an API key

DELETE/api/v1/api-keys/:idscope: session
{ "id": "key_abc123", "revoked": true }

Errors

All errors return a consistent JSON shape with an object containing a machine-readable .

{ "error": { "code": "insufficient_credits", "message": "...", "status": 402 } }
HTTPcodeMeaning
401unauthorizedMissing or invalid API key.
403forbiddenAPI key does not have the required scope.
404not_foundThe requested resource does not exist.
400bad_requestInvalid request body or parameters.
402insufficient_creditsNot enough credits to perform the operation.
429rate_limitedToo many requests. Check the Retry-After header.
500internal_errorUnexpected server error.

Rate limits: Generation endpoints have a stricter rate limit than read endpoints. When you hit a limit, the response includes a header with the number of seconds to wait. Failed jobs (after all retries) automatically refund the 25 credits that were deducted.