Developers

The Storias API

Send one video and the name of a look. Storias transcribes it, plans the edit, sources or generates the visuals, and renders a finished, captioned film — over REST, or through MCP for an AI assistant.

Quick start

Five calls to a finished film

Set STORIAS_API_KEY once and run these in order.

  1. 1 · Who am I, and what can I spend?

    Every call is a bearer key, so start by reading it back — a good way to prove the key works before anything spends credits.

    curl https://api.storiasai.com/v1/me \
      -H "Authorization: Bearer $STORIAS_API_KEY"
    {
      "user_id": "a059c6f4-604d-4f68-81f6-f839b9c95b0a",
      "plan": "weekly",
      "credits_remaining": 281,
      "permissions": {
        "render": true,
        "read": true
      }
    }
  2. 2 · Choose a look

    Pick a template by best_for and avoid_for, never by name — the wrong look is the most common way an API-made film disappoints.

    curl https://api.storiasai.com/v1/templates \
      -H "Authorization: Bearer $STORIAS_API_KEY"
    {
      "templates": [
        {
          "id": "newsroom",
          "name": "Newsroom",
          "tier": "pro",
          "description": "Broadcast clarity: fast captions, decisive cuts and a clean lower third. The default when someone is talking to camera and the words carry the piece.",
          "best_for": [
            "talking-head updates",
            "news commentary",
            "announcements",
            "quick explainers"
          ],
          "avoid_for": [
            "silent footage",
            "product beauty shots",
            "long-form documentary"
          ],
          "aspect_ratios": [
            "9:16"
          ],
          "preview_url": "https://edit.storiasai.com/previews/newsroom.mp4"
        },
        {
          "id": "scrapbook",
          "name": "Scrapbook",
          "tier": "pro",
          "description": "Tactile layers, paper cutouts and handwritten notes around the speaker. Warm and personal rather than corporate.",
          "best_for": [
            "personal stories",
            "travel diaries",
            "behind the scenes",
            "community updates"
          ],
          "avoid_for": [
            "financial reporting",
            "formal corporate communication",
            "technical documentation"
          ],
          "aspect_ratios": [
            "9:16"
          ],
          "preview_url": "https://edit.storiasai.com/previews/scrapbook.mp4"
        }
      ]
    }
  3. 3 · Upload the clip

    Ask for a one-time URL, then PUT the file to it with the same Content-Type you declared. Already have a link instead? Skip straight to step 4 with video_url.

    curl https://api.storiasai.com/v1/uploads \
      -H "Authorization: Bearer $STORIAS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"content_type":"video/mp4"}'
    {
      "asset_id": "a059c6f4-604d-4f68-81f6-f839b9c95b0a/7afc924f-a906-4b3f-928c-5a36bed8a6fe.mp4",
      "upload_url": "https://swpjvbgpnqbprqtbaksk.supabase.co/storage/v1/object/upload/sign/uploads/example.mp4?token=example",
      "method": "PUT",
      "headers": {
        "content-type": "video/mp4"
      },
      "expires_in": 7200
    }
    curl -X PUT "$UPLOAD_URL" \
      -H "Content-Type: video/mp4" \
      --data-binary @clip.mp4
  4. 4 · Start the render

    Always send Idempotency-Key — a UUID you generate — so retrying this exact call after a timeout returns the same film instead of starting, and charging for, a second one.

    curl https://api.storiasai.com/v1/renders \
      -H "Authorization: Bearer $STORIAS_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{"video_url":"https://example.com/talk.mp4","template_id":"newsroom","aspect_ratio":"9:16"}'
    {
      "render_id": "3f1c1f6e-6a1b-4a1e-9f0a-2b7c9d5e8a10",
      "status": "queued"
    }
  5. 5 · Poll for the finished film

    Check back about every ten seconds until status is completed or failed — both are final, and progress never reaches 100 before completed.

    curl https://api.storiasai.com/v1/renders/3f1c1f6e-6a1b-4a1e-9f0a-2b7c9d5e8a10 \
      -H "Authorization: Bearer $STORIAS_API_KEY"
    {
      "render_id": "3f1c1f6e-6a1b-4a1e-9f0a-2b7c9d5e8a10",
      "status": "completed",
      "progress": 100,
      "output": {
        "video_url": "https://remotionlambda-uswest2.s3.us-west-2.amazonaws.com/renders/n0fecl6yho/out.mp4?X-Amz-Signature=example",
        "download_url": "https://remotionlambda-uswest2.s3.us-west-2.amazonaws.com/renders/n0fecl6yho/out.mp4?response-content-disposition=attachment&X-Amz-Signature=example",
        "expires_in": 7200,
        "duration_seconds": 20,
        "aspect_ratio": "9:16"
      },
      "credits_used": 20,
      "template_id": "newsroom"
    }

Authentication

Getting a key

Open Settings → API keys in the web app, signed in on an active plan, and create a key. The key is shown once — Storias stores only its hash, so a lost key is replaced, never recovered. Send it as Authorization: Bearer storias_sk_live_… on every call. Treat it like a payment instrument: it spends real credits, so keep it on a server and out of any code a browser downloads.

Reference

Endpoints

Every path this API serves, with its fields, straight from the live spec.

Templates

The looks a film can be made in, and what each one is for.

GET/v1/templates

List the looks a film can be made in

Call this before every first render and choose by best_for and avoid_for, not by name. The wrong look is the most common way an API-made film disappoints: a property tour in a financial-reporting look is technically a success and a waste of the customer's credits.

aspect_ratios is per template and is not advisory. A Pro look is drawn for 9:16 only, and asking for 16:9 on one is refused rather than silently corrected.

Responses
StatusMeaning
200Every look currently available.
401The key is missing, malformed or revoked.
500Something went wrong on our side. The body is the standard error envelope; X-Request-Id is what support needs.
200 response
{
  "templates": [
    {
      "id": "newsroom",
      "name": "Newsroom",
      "tier": "pro",
      "description": "Broadcast clarity: fast captions, decisive cuts and a clean lower third. The default when someone is talking to camera and the words carry the piece.",
      "best_for": [
        "talking-head updates",
        "news commentary",
        "announcements",
        "quick explainers"
      ],
      "avoid_for": [
        "silent footage",
        "product beauty shots",
        "long-form documentary"
      ],
      "aspect_ratios": [
        "9:16"
      ],
      "preview_url": "https://edit.storiasai.com/previews/newsroom.mp4"
    },
    {
      "id": "scrapbook",
      "name": "Scrapbook",
      "tier": "pro",
      "description": "Tactile layers, paper cutouts and handwritten notes around the speaker. Warm and personal rather than corporate.",
      "best_for": [
        "personal stories",
        "travel diaries",
        "behind the scenes",
        "community updates"
      ],
      "avoid_for": [
        "financial reporting",
        "formal corporate communication",
        "technical documentation"
      ],
      "aspect_ratios": [
        "9:16"
      ],
      "preview_url": "https://edit.storiasai.com/previews/scrapbook.mp4"
    }
  ]
}

Renders

Making a film and following it to completion.

POST/v1/uploads

Get a URL to upload the source video to

Returns a one-time URL. PUT the video bytes to it with the same Content-Type you declared, then pass the returned asset_id to POST /v1/renders.

The video should be one person speaking to camera, with clear audio — that is what every look is built around. Up to 60 minutes and 2 GB.

Request body
FieldTypeValuesDescription
content_type required string video/mp4 · video/quicktime · video/x-m4v The video's real type. Declaring one type and sending another is refused later, before anything is charged.
{
  "content_type": "video/mp4"
}
Responses
StatusMeaning
201Upload here, then render.
401The key is missing, malformed or revoked.
415That content_type is not a video we can read.
429Too many requests on this key. retry_after says how long to wait.
500Something went wrong on our side. The body is the standard error envelope; X-Request-Id is what support needs.
201 response
{
  "asset_id": "a059c6f4-604d-4f68-81f6-f839b9c95b0a/7afc924f-a906-4b3f-928c-5a36bed8a6fe.mp4",
  "upload_url": "https://swpjvbgpnqbprqtbaksk.supabase.co/storage/v1/object/upload/sign/uploads/example.mp4?token=example",
  "method": "PUT",
  "headers": {
    "content-type": "video/mp4"
  },
  "expires_in": 7200
}
POST/v1/renders

Make a film

Starts a render and returns immediately with a render_id. The film is not ready; poll GET /v1/renders/{render_id}.

Pass either video_url (we fetch it — no upload step) or asset_id from POST /v1/uploads.

Credits are spent here. One per second of finished film, minimum 15, held when the render is admitted. A refusal for INSUFFICIENT_CREDITS tells you exactly how many are missing.

Always send Idempotency-Key. It is the render's own id, so the same key returns the same render however many times you retry — without it, a retried request is a second film and a second charge. The length is measured from the file you uploaded, never from anything you send.

Parameters
ParameterInTypeDescription
Idempotency-Key header string A UUID you generate. Reuse it on every retry of this render; never reuse it for a different one.
Request body

Exactly one of: asset_id or video_url.

FieldTypeValuesDescription
asset_id string From POST /v1/uploads, after the bytes are uploaded.
video_url string (uri) An https link we fetch ourselves — use this instead of asset_id and skip the upload step entirely. It must serve video/mp4, video/quicktime or video/x-m4v, be reachable without credentials, and stay valid for a couple of minutes. Up to 2 GB.
template_id required string newsroom · scrapbook · editorial · broadsheet · blueprint · dossier · briefing · prism · ultraEditorialPop · ultraFinancialJournal · ultraBusinessDocumentary · ultraInvestigativeDossier · ultraEvidence · ultraMindmap · ultraStoryboard · ultraMap · ultraData · ultraMindnet · ultraCountdown · ultraPlaybook A look from GET /v1/templates. Choose it by best_for and avoid_for.
aspect_ratio string 9:16 · 16:9 16:9 needs an Ultra template. A Pro look is refused rather than quietly turned vertical.
From a link — two calls in total
{
  "video_url": "https://example.com/talk.mp4",
  "template_id": "newsroom",
  "aspect_ratio": "9:16"
}
From an upload
{
  "asset_id": "a059c6f4-604d-4f68-81f6-f839b9c95b0a/7afc924f-a906-4b3f-928c-5a36bed8a6fe.mp4",
  "template_id": "newsroom",
  "aspect_ratio": "9:16"
}
Responses
StatusMeaning
202Accepted. Poll for the result.
400Something in the request cannot be used — see code.
401The key is missing, malformed or revoked.
402Not enough credits. missing says how many short.
403This key is read-only and cannot start renders.
413The clip is longer than this product renders.
415The uploaded file is not a video we can read.
422The asset is not in storage, or is not readable as a recording.
429Too many renders in flight on this account, or too many requests on this key. retry_after says how long to wait.
500Something went wrong on our side. The body is the standard error envelope; X-Request-Id is what support needs.
503Rendering is paused. Retry later; nothing was charged.
202 response
{
  "render_id": "3f1c1f6e-6a1b-4a1e-9f0a-2b7c9d5e8a10",
  "status": "queued"
}
GET/v1/renders/{render_id}

Where a film has got to, and the film itself

Poll about every ten seconds. status is queued, processing, completed or failed, and only the last two are final.

On completed you get video_url and download_url. Both are signed and expire — download the file, do not store the link. On failed you get a short message and a reference: a word and four characters that support can use to find the exact film, which is what to show a person.

Parameters
ParameterInTypeDescription
render_id required path string From POST /v1/renders.
Responses
StatusMeaning
200The render's state. A render belonging to another account is not found, never forbidden.
401The key is missing, malformed or revoked.
404No render with that id on this account.
500Something went wrong on our side. The body is the standard error envelope; X-Request-Id is what support needs.
200 — Still working
{
  "render_id": "3f1c1f6e-6a1b-4a1e-9f0a-2b7c9d5e8a10",
  "status": "processing",
  "progress": 62
}
200 — Finished
{
  "render_id": "3f1c1f6e-6a1b-4a1e-9f0a-2b7c9d5e8a10",
  "status": "completed",
  "progress": 100,
  "output": {
    "video_url": "https://remotionlambda-uswest2.s3.us-west-2.amazonaws.com/renders/n0fecl6yho/out.mp4?X-Amz-Signature=example",
    "download_url": "https://remotionlambda-uswest2.s3.us-west-2.amazonaws.com/renders/n0fecl6yho/out.mp4?response-content-disposition=attachment&X-Amz-Signature=example",
    "expires_in": 7200,
    "duration_seconds": 20,
    "aspect_ratio": "9:16"
  },
  "credits_used": 20,
  "template_id": "newsroom"
}
200 — Did not finish
{
  "render_id": "3f1c1f6e-6a1b-4a1e-9f0a-2b7c9d5e8a10",
  "status": "failed",
  "progress": 0,
  "error": {
    "code": "RENDER_FAILED",
    "message": "We couldn't finish step 3 of 5.",
    "reference": "Green 7F3A"
  }
}

Account

Who the key belongs to and what it can spend.

GET/v1/me

Who this key belongs to and what it can spend

Check credits_remaining before submitting a render you cannot pay for. One key belongs to one user; there are no workspaces.

Responses
StatusMeaning
200The account behind this key.
401The key is missing, malformed or revoked.
500Something went wrong on our side. The body is the standard error envelope; X-Request-Id is what support needs.
200 response
{
  "user_id": "a059c6f4-604d-4f68-81f6-f839b9c95b0a",
  "plan": "weekly",
  "credits_remaining": 281,
  "permissions": {
    "render": true,
    "read": true
  }
}

Reference

Objects

The shapes above, consolidated — useful when you already know the endpoint and just need a field.

Template

FieldTypeValuesDescription
id required string newsroom · scrapbook · editorial · broadsheet · blueprint · dossier · briefing · prism · ultraEditorialPop · ultraFinancialJournal · ultraBusinessDocumentary · ultraInvestigativeDossier · ultraEvidence · ultraMindmap · ultraStoryboard · ultraMap · ultraData · ultraMindnet · ultraCountdown · ultraPlaybook Pass this as template_id.
name required string What a person calls this look.
tier required string pro · ultra pro is captioned and speaker-led, 9:16 only. ultra is a directed film and works in either canvas.
description required string What kind of film this look makes.
best_for required array of string Subjects this look was built for.
avoid_for required array of string Where it will disappoint. Read this half — it is what stops a bad choice.
aspect_ratios required array of string 9:16 · 16:9 The canvases this look really renders. Asking for another is refused, not corrected.
preview_url string (uri) A short example film in this look, 9:16.
preview_url_16x9 string (uri) The wide cut. Ultra looks only — a Pro look has no 16:9 version because it does not render one.

Account

FieldTypeValuesDescription
user_id required string (uuid)
plan required string The account's current plan, or free.
credits_remaining required integer Spendable credits. One credit is one second of finished film; every render costs at least 15.
permissions required object
permissions.render boolean This key may start renders.
permissions.read boolean This key may read templates, renders and this account.

Upload

FieldTypeValuesDescription
asset_id required string Pass this to POST /v1/renders once the PUT has succeeded.
upload_url required string (uri) One-time URL. PUT the bytes here.
method required string PUT
headers object Send these with the PUT.
expires_in required integer Seconds this URL is valid for.

RenderRequest

Give the video one of two ways: asset_id if you uploaded it, or video_url if you have a link.

Exactly one of: asset_id or video_url.

FieldTypeValuesDescription
asset_id string From POST /v1/uploads, after the bytes are uploaded.
video_url string (uri) An https link we fetch ourselves — use this instead of asset_id and skip the upload step entirely. It must serve video/mp4, video/quicktime or video/x-m4v, be reachable without credentials, and stay valid for a couple of minutes. Up to 2 GB.
template_id required string newsroom · scrapbook · editorial · broadsheet · blueprint · dossier · briefing · prism · ultraEditorialPop · ultraFinancialJournal · ultraBusinessDocumentary · ultraInvestigativeDossier · ultraEvidence · ultraMindmap · ultraStoryboard · ultraMap · ultraData · ultraMindnet · ultraCountdown · ultraPlaybook A look from GET /v1/templates. Choose it by best_for and avoid_for.
aspect_ratio string 9:16 · 16:9 16:9 needs an Ultra template. A Pro look is refused rather than quietly turned vertical.

Render

FieldTypeValuesDescription
render_id required string (uuid)
status required string queued · processing · completed · failed Only completed and failed are final.
progress required integer Never reaches 100 before completed, so do not treat 99 as done.
output object On completed only. This is where the film is — not at the top level.
output.video_url required string or null (uri) Signed, for playback. Expires.
output.download_url required string or null (uri) Signed, with a filename attached, for saving. Expires.
output.expires_in required integer Seconds until both URLs stop working (7200). Download the file rather than storing the link.
output.duration_seconds integer or null The finished film's length.
output.aspect_ratio string 9:16 · 16:9
credits_used integer or null On completed: what this film actually cost.
template_id string On completed: the look it was rendered in.
error object On failed only.
error.code required string RENDER_FAILED
error.message required string What to show a person. Deliberately carries no internal detail.
error.reference required string A word and four characters, e.g. Green 7F3A. Support finds the film by this.

Error

FieldTypeValuesDescription
error required object
error.code required string INVALID_API_KEY · FORBIDDEN · NOT_FOUND · INVALID_REQUEST · INVALID_TEMPLATE · INVALID_ASPECT_RATIO · INVALID_ASSET · UNSUPPORTED_VIDEO · FILE_TOO_LARGE · CLIP_TOO_LONG · INSUFFICIENT_CREDITS · RATE_LIMITED · TOO_MANY_RENDERS · RENDER_FAILED · RENDERING_PAUSED · INTERNAL A stable machine code. Branch on this, never on message — the wording can improve, the code cannot change.
error.message required string One sentence for a person reading a log.
error.required integer On INSUFFICIENT_CREDITS: credits this render needs.
error.available integer On INSUFFICIENT_CREDITS: credits the account holds.
error.missing integer On INSUFFICIENT_CREDITS: how many short. Buy at least this many.
error.retry_after integer On RATE_LIMITED and TOO_MANY_RENDERS: seconds to wait before retrying. Sent as the Retry-After header too.
error.limit_bytes integer On FILE_TOO_LARGE: the largest source this API accepts.
error.bytes integer On FILE_TOO_LARGE: how big the source actually is.
error.aspect_ratios array of string On INVALID_ASPECT_RATIO: the ratios this template does support.

Renders

Statuses and polling

A render's status is one of queued, processing, completed, failed. Only completed and failed are final; poll GET /v1/renders/{render_id} about every ten seconds until you see one of them, and never treat a progress of 99 as done. On completed, output.video_url and output.download_url are signed and expire in 2 hours — download the file rather than storing the link. On failed, error.reference is a word and four characters (for example Green 7F3A): show it to whoever hit the failure, since it is what support uses to find the exact film. A failed film is never charged.

Billing

Credits

One credit buys one second of finished film, with a minimum of 15 credits per render. The cost is held on the account when the render is admitted, and charged for real only on delivery. A render that fails instead is never charged, and its hold is released.

Limits

Plans and rate limits

Plan ceilings come from the same contract the apps enforce; a key inherits its account’s plan.

Starter
  • Films up to 3 minutes long
  • 3 renders at once
  • Standard queue
Creator
  • Films up to 6 minutes long
  • 6 renders at once
  • Standard queue
Pro
  • Films up to 10 minutes long
  • 10 renders at once
  • Priority rendering
LimitValue
Render callsPOST /v1/renders — 20 per minute, per key
Read callsEverything else — 120 per minute, per key
Source file size2 GB
Source lengthUp to 60 minutes (a plan's own film-length limit above may be shorter)

A rate-limited or too-many-renders refusal carries retry_after seconds in the body and in the Retry-After header. It is never charged.

When it says no

Errors

Branch on code, never on message — the wording can improve, the code cannot change. Every response, success or failure, carries an X-Request-Id header.

CodeHTTP statusWhat to do
INVALID_API_KEY401Check the key was copied in full and has not been revoked in Settings → API keys. Mint a new one if in doubt.
FORBIDDEN403This key is not allowed to do that — for example a read-only key used on a write call. Use a key with the right scope.
NOT_FOUND404Nothing on this account matches that id or path. A render that belongs to a different account also answers this, never 403, so an id cannot be used to probe someone else’s films.
INVALID_REQUEST400Something in the request body could not be used. Check field names, types and required fields against this page.
INVALID_TEMPLATE400That template_id does not exist. Call GET /v1/templates for the current list rather than hard-coding one.
INVALID_ASPECT_RATIO400That template does not render this aspect ratio. aspect_ratios on the error, and on each template from GET /v1/templates, says which ones it does — a Pro look is 9:16 only.
INVALID_ASSET422The asset_id is not in storage, or is not readable as a video. Confirm the PUT to upload_url finished before calling POST /v1/renders.
UNSUPPORTED_VIDEO415The file is not a video type this API reads. Use video/mp4, video/quicktime or video/x-m4v, and declare the real type.
FILE_TOO_LARGE413limit_bytes and bytes on the error say the cap and the actual size. Compress or trim the source and try again.
CLIP_TOO_LONG413The clip is longer than this product renders. Trim it and try again.
INSUFFICIENT_CREDITS402required, available and missing say exactly how many credits are short. Nothing was charged; buy at least missing more.
RATE_LIMITED429Too many requests on this key. Wait retry_after seconds — also sent as the Retry-After header — and retry.
TOO_MANY_RENDERS429Too many renders already in flight on this account. Wait retry_after seconds and retry; nothing was charged.
RENDER_FAILED500The render did not finish. message and reference from GET /v1/renders/{render_id} are what to show a person and what support needs to find it — never retry automatically, a retry is another charge.
RENDERING_PAUSED503Rendering is paused for everyone for a moment. Nothing was charged; wait and retry later.
INTERNAL500Something went wrong on our side. The X-Request-Id response header is what support needs to find this exact call.

The error envelope

Every refusal, on every endpoint, is this same shape.

FieldTypeValuesDescription
error required object
error.code required string INVALID_API_KEY · FORBIDDEN · NOT_FOUND · INVALID_REQUEST · INVALID_TEMPLATE · INVALID_ASPECT_RATIO · INVALID_ASSET · UNSUPPORTED_VIDEO · FILE_TOO_LARGE · CLIP_TOO_LONG · INSUFFICIENT_CREDITS · RATE_LIMITED · TOO_MANY_RENDERS · RENDER_FAILED · RENDERING_PAUSED · INTERNAL A stable machine code. Branch on this, never on message — the wording can improve, the code cannot change.
error.message required string One sentence for a person reading a log.
error.required integer On INSUFFICIENT_CREDITS: credits this render needs.
error.available integer On INSUFFICIENT_CREDITS: credits the account holds.
error.missing integer On INSUFFICIENT_CREDITS: how many short. Buy at least this many.
error.retry_after integer On RATE_LIMITED and TOO_MANY_RENDERS: seconds to wait before retrying. Sent as the Retry-After header too.
error.limit_bytes integer On FILE_TOO_LARGE: the largest source this API accepts.
error.bytes integer On FILE_TOO_LARGE: how big the source actually is.
error.aspect_ratios array of string On INVALID_ASPECT_RATIO: the ratios this template does support.

AI assistants

Connect over MCP

Claude, ChatGPT and most agent frameworks can use Storias as four tools over one JSON-RPC (MCP) endpoint, authenticated with the same key as the REST API:

POST https://swpjvbgpnqbprqtbaksk.supabase.co/functions/v1/mcp
Authorization: Bearer $STORIAS_API_KEY
Content-Type: application/json

Send the standard MCP handshake (initialize, then tools/list or tools/call) as JSON-RPC 2.0 request bodies. In Claude Desktop or Claude Code, add it as a custom connector with that URL and an Authorization header carrying the same bearer key described above; any MCP-compatible client works the same way.

ToolWhat it doesParameters
list_looks The looks a film can be made in, each with what it is for and what it is wrong for.
make_film Turn one recording into a finished, edited, captioned film. Spends credits; returns an id, not a video. video_url required — An https link to the video. Fetched immediately, so it must still be valid when this is called.
look required — The `id` of a look from `list_looks`.
aspect_ratio — `9:16` (default) or `16:9`. `16:9` needs an Ultra look.
check_film Where a film has got to, and the film itself once it is done. render_id required — The id `make_film` returned.
check_credits How many credits the account has left, and about how many films that buys.

A tool's failure comes back as a normal result with isError set, carrying a sentence the model can act on (for example, exactly how many credits are missing) — not a protocol error. make_film is safe to retry: calling it again with the same video and look within the hour returns the film already being made rather than starting a second one; after that hour the same call starts, and charges for, a new film.

00:00:00:00 · New project

Build the
next integration.

The full OpenAPI document is published at https://api.storiasai.com/v1/openapi.json, no key required — read it straight into any client generator.