Elyum API reference

One endpoint for every video, image and audio model. Name a model, attach your inputs, choose the settings, and get the finished file back. Priced in dollars, charged only when the file is delivered.

Base URLhttps://api.elyum.ai/v1AuthAuthorization: Bearer ek_live_…Plain text/docs/api/v1
On this page

1.Getting started

Every model, from Seedance 2.5 to Nano Banana 2, is one POST to the same endpoint. The request names the model; the answer is a generation you wait for or poll until its file is ready.

How it works

  1. 1
    Send POST /v1/generations
    with a model id, a prompt, your inputs and the settings. Anything the model cannot render is refused with a 400 before anything is charged.
  2. 2
    Wait or poll
    Add Prefer: wait=55 and a render that finishes within 55 seconds comes back done. A longer one answers 202: ask GET /v1/generations/{id} until its status is succeeded, failed or canceled.
  3. 3
    Download the file
    Each output has a signed link, valid for 24 hours. Fetch the generation again for a fresh one.
You pay for delivered files only

API renders are final: delivered at full quality and charged once the file is ready. A render that fails or that you cancel costs nothing. The price of a request is fixed when it is submitted.

Quick start

  1. 1
    Create an API key
    On Keys, with the generate and spend scopes. Keep it on your server: it spends your balance.
  2. 2
    Add funds
    On Billing. The API spends your API balance; nothing is charged until a file is delivered.
  3. 3
    Send your first request
    Below, Seedance 2.5 animating a still. Every model's page under Models has its own request, ready to copy, and a Playground to try it.
curl https://api.elyum.ai/v1/generations \
  -H "Authorization: Bearer $ELYUM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=55" \
  -d '{
  "model": "bytedance/seedance-2.5/image-to-video",
  "prompt": "The camera slowly pushes in as she turns toward the window",
  "inputs": [
    {
      "role": "first_frame",
      "url": "https://example.com/frame.jpg"
    }
  ],
  "resolution": "720p",
  "duration": 5
}'
Response · 200
{
  "id": "g_81c2…",
  "object": "generation",
  "model": "bytedance/seedance-2.5/image-to-video",
  "task": "image-to-video",
  "status": "succeeded",
  "delivery": "final",
  "created_at": "2026-10-10T12:00:00.000Z",
  "finished_at": "2026-10-10T12:01:24.000Z",
  "settings": {
    "duration": 5,
    "resolution": "720p",
    "aspect_ratio": "16:9",
    "audio": true
  },
  "outputs": [
    {
      "index": 0,
      "url": "https://elyum.ai/media/…?e=…&s=…",
      "type": "video",
      "content_type": "video/mp4",
      "width": 1280,
      "height": 720,
      "duration": 5,
      "expires_at": "2026-10-11T12:01:24.000Z"
    }
  ],
  "usage": {
    "usd": 1.42,
    "tokens": 108900,
    "billed": "charged"
  },
  "error": null,
  "metadata": {},
  "fingerprint": "fp_6a0c…"
}

Requests and responses

  • Bodies are JSON (Content-Type: application/json), except a file upload, which is a multipart form.
  • Every answer carries an X-Request-Id header (req_…). Quote it when you write to support.
  • Times are ISO 8601 in UTC. Ids carry their kind: g_… a generation, file_… an upload, req_… a request.
  • Amounts are US dollars, as numbers (1.42), to the hundredth of a cent where a price per second needs it.

2.Authentication

Send your API key as a bearer token on every request.

API keys

Header
Authorization: Bearer ek_live_…

Keys start with ek_live_ and are made on Keys in your console, where you can also revoke one at once. Never put a key in browser code or commit it: call the API from your server, with the key in an environment variable (ELYUM_API_KEY in every sample here).

Your key also opens the API's MCP server at https://api.elyum.ai/v1/mcp, for an agent that calls these endpoints as tools: see API MCP server. An MCP token (ek_mcp_…) belongs to the studio's own MCP server; sent here it is refused with 401 auth, and the error names the page for an API key.

Scopes

ScopeLets the key
readList models, read generations, files and the account.
generateStart renders and upload files.
spendPay for them. A render is paid on delivery, so a key that renders needs generate and spend; without one the answer is 403 scope, naming it in needs.

Daily spend cap

A key can carry a daily cap in dollars, set on Keys: what it may spend in a rolling 24 hours. A render that would go over it is refused with 429 key_cap and nothing is held, so a runaway loop stops at the cap, not at an empty balance. GET /v1/account shows the cap and what the key spent.

3.Models

Each model is listed with the tasks it runs, the inputs and settings each task takes, and its price.

List models

GET/v1/modelsevery model your key can call
GET/v1/models/{id}one task, or every task of a family
curl https://api.elyum.ai/v1/models \
  -H "Authorization: Bearer $ELYUM_API_KEY"

The same catalog, with a Playground and a ready request per model, is at Models.

Model ids

A model id is owner/model/task, for example bytedance/seedance-2.5/reference-to-video. Tasks: text-to-video, image-to-video, reference-to-video, video-edit, video-extend, motion-control, video-upscale, text-to-image, image-edit, image-upscale, text-to-speech, text-to-music, text-to-sound-effects.

Short ids. Send owner/model (bytedance/seedance-2.5) and the task is worked out from your inputs: no inputs is text to video, a first_frame is image to video, references are reference to video, a source clip is an edit. The price is always the price of the task that runs.

The model object

FieldTypeRequiredDefaultDescription
idstringAlways—The model id, owner/model/task.
taskstringAlways—The task this entry runs.
promptstringAlways—required, optional or none.
inputsobjectAlways—The input roles the task takes, the media types of each and their limits.
settingsobjectAlways—Every setting the model renders, with its allowed values and its default.
optionsstring[]Always—Model-specific settings that go in options; empty for most models.
pricingobjectAlways—The price in dollars (Prices).
typical_secondsnumberNo—How long a render usually takes, once we have enough of them.

4.Create a generation

One call for every model and task. Only the model is always required; the model's entry says what else it needs.

POST/v1/generations

Request parameters

ParameterTypeRequiredDefaultDescription
modelstringYes—A full model id, or a short one whose task is worked out from the inputs.
promptstringMost models—Required unless the model says optional or none. Sent to the model as written, never shortened.
inputsobject[]No—The files the task takes, each with a role (Inputs). Up to 40.
durationnumberNodefault per modelSeconds of output.
resolutionstringNodefault per model480p, 720p, 1080p, 1K, 2K… as the model lists.
aspect_ratiostringNodefault per model16:9, 9:16, 1:1… auto follows the first frame, reference or clip where the model supports it.
audiobooleanNodefault per modelRender sound, on models that can.
voicestringNo—Speech models: a voice the model lists, or a voice id from its library.
targetstringNo—Upscalers: 2K, 4K, 6K or 8K, as the model's target values list.
optionsobjectNo—Model-specific settings listed under the model's options.
nintegerNodefault 1One output per request for now. Send several requests for several outputs.
max_usdnumberNo—The most you will pay, in dollars. A render that would cost more is refused with 400 max_usd and nothing is held.
metadataobjectNo—Up to 16 string pairs (keys up to 64 characters, values up to 512), returned on the generation.
Strict, never silent

An unknown field, a wrong type, an input the task cannot take or a value the model does not render is a 400 naming the field in param, before anything is held. We never drop a setting or round it to a nearby value. A setting you leave out takes the default the model lists.

Inputs

Each input has a role (reference when you leave it out) and exactly one source:

Input sources
{ "role": "first_frame", "url": "https://example.com/frame.jpg" }
{ "role": "reference", "file": "file_3f9a…" }
{ "role": "source", "generation": "g_81c2…", "output": 0 }
{ "role": "reference", "type": "character", "id": "cr_5d1e…" }
FieldTypeRequiredDefaultDescription
rolestringNodefault referencefirst_frame, last_frame, reference, source or motion (below).
urlstringNo—An https URL, a base64 data: URI, or one of your /media/… paths.
filestringNo—An id from POST /v1/files.
generationstringNo—One of your earlier renders, with output its index.
outputintegerNodefault 0Which output of generation.
idstringNo—A character from your library (type: "character"): every photo of that person rides along.
typestringNo—image, video, audio or character. Needed only when a URL does not say what it is.
namestringNo—A word you cite in the prompt as @name (below).
RoleUsed by
first_frame, last_frameimage-to-video
referencereference-to-video and video-edit (images, clips, audio), image-edit (images), motion-control (the character image)
sourcevideo-edit, video-extend and the upscalers
motionmotion-control: the clip whose movement is copied

Citing inputs in the prompt

References are numbered by type in the order you send them: @image1, @image2, @video1, @audio1. Give an input a name and cite it by that name instead.

curl https://api.elyum.ai/v1/generations \
  -H "Authorization: Bearer $ELYUM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=55" \
  -d '{
  "model": "bytedance/seedance-2.5/reference-to-video",
  "prompt": "@maya walks through @loft at golden hour, the camera tracking beside her",
  "inputs": [
    {
      "url": "https://example.com/maya.png",
      "name": "maya"
    },
    {
      "url": "https://example.com/loft.jpg",
      "name": "loft"
    }
  ],
  "duration": 8
}'

Upload a file

POST/v1/filesmultipart form, field file

Upload a file once and use its id in any request. PNG, JPG and WebP images, MP4, MOV and WebM clips, MP3 and WAV audio, up to 60 MB.

curl https://api.elyum.ai/v1/files \
  -H "Authorization: Bearer $ELYUM_API_KEY" \
  -F "[email protected]"
Response · 201
{
  "id": "file_3f9a…",
  "object": "file",
  "type": "image",
  "content_type": "image/png",
  "bytes": 482113,
  "name": "frame.png",
  "created_at": "2026-10-10T12:00:00.000Z"
}

5.Getting the result

A render takes from a few seconds to a few minutes. Hold the request open for up to 55 seconds, or submit and come back.

Submit and wait

Send Prefer: wait=N (up to 55) to hold the answer until the render ends or N seconds pass. A finished render answers 200; one still running answers 202 with a Location header pointing at the generation.

curl https://api.elyum.ai/v1/generations \
  -H "Authorization: Bearer $ELYUM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=55" \
  -d '{
  "model": "bytedance/seedance-2.5/image-to-video",
  "prompt": "The camera slowly pushes in as she turns toward the window",
  "inputs": [
    {
      "role": "first_frame",
      "url": "https://example.com/frame.jpg"
    }
  ],
  "resolution": "720p",
  "duration": 5
}'

Poll

GET/v1/generations/{id}status and outputs; also takes Prefer: wait

Without a wait the create call answers at once with status: "queued". Ask for the generation until the status is final; with Prefer: wait=55 on each call you need only a few calls, and no sleep between them.

# 1. Submit (answers 202 with the generation and a Location header)
curl https://api.elyum.ai/v1/generations \
  -H "Authorization: Bearer $ELYUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "bytedance/seedance-2.5/image-to-video",
  "prompt": "The camera slowly pushes in as she turns toward the window",
  "inputs": [
    {
      "role": "first_frame",
      "url": "https://example.com/frame.jpg"
    }
  ],
  "resolution": "720p",
  "duration": 5
}'

# 2. Ask again until status is succeeded, failed or canceled (each call may wait up to 55 s)
curl https://api.elyum.ai/v1/generations/g_81c2… \
  -H "Prefer: wait=55" \
  -H "Authorization: Bearer $ELYUM_API_KEY"

The generation object

Generation
{
  "id": "g_81c2…",
  "object": "generation",
  "model": "bytedance/seedance-2.5/image-to-video",
  "task": "image-to-video",
  "status": "succeeded",
  "delivery": "final",
  "created_at": "2026-10-10T12:00:00.000Z",
  "finished_at": "2026-10-10T12:01:24.000Z",
  "settings": {
    "duration": 5,
    "resolution": "720p",
    "aspect_ratio": "16:9",
    "audio": true
  },
  "outputs": [
    {
      "index": 0,
      "url": "https://elyum.ai/media/…?e=…&s=…",
      "type": "video",
      "content_type": "video/mp4",
      "width": 1280,
      "height": 720,
      "duration": 5,
      "expires_at": "2026-10-11T12:01:24.000Z"
    }
  ],
  "usage": {
    "usd": 1.42,
    "tokens": 108900,
    "billed": "charged"
  },
  "error": null,
  "metadata": {},
  "fingerprint": "fp_6a0c…"
}
FieldTypeRequiredDefaultDescription
statusstringAlways—queued → running → succeeded, failed or canceled.
deliverystringAlways—final: delivered at full quality and charged on delivery.
settingsobjectAlways—What the render was asked for, defaults filled in.
outputs[].urlstringNo—A signed link, valid for 24 hours (expires_at). Fetch the generation again for a fresh one.
outputs[]objectNo—type, content_type, and width, height, duration where they apply. Empty until the render succeeds.
usage.usdnumberAlways—What the render costs: held while it runs, charged once delivered, 0 when it failed or was canceled.
usage.billedstringAlways—held, charged or refunded.
usage.tokensintegerNo—Seedance 2.5 only: the tokens it was billed by (Prices).
errorobjectNo—On a failed render { code, message }: content_refused, timeout or generation_failed; canceled on a canceled one.
metadataobjectAlways—Your metadata, as sent.
fingerprintstringNo—Changes when what serves the model changes, so you can tell when output may differ.

Cancel

POST/v1/generations/{id}/cancel

Stops a render that has not finished. Nothing is charged. A render that already ended answers 409 not_cancelable with its generation.

curl -X POST https://api.elyum.ai/v1/generations/g_81c2…/cancel \
  -H "Authorization: Bearer $ELYUM_API_KEY"

Retry safely

Send Idempotency-Key (up to 255 printable characters, no spaces) and a retry of the same request returns the first request's generation instead of rendering twice, with the header Idempotent-Replayed: true. The same key with a different body is refused with 409 idempotency_conflict.

curl https://api.elyum.ai/v1/generations \
  -H "Authorization: Bearer $ELYUM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4821-shot-3" \
  -d '{
  "model": "bytedance/seedance-2.5/image-to-video",
  "prompt": "The camera slowly pushes in as she turns toward the window",
  "inputs": [
    {
      "role": "first_frame",
      "url": "https://example.com/frame.jpg"
    }
  ],
  "resolution": "720p",
  "duration": 5
}'

6.Prices and balance

Every price is in US dollars: per second of video, per image, per render, or per 1,000 characters of speech.

Prices

Each task's price is in its pricing object in GET /v1/models, and on its page under Models. Pricing compares ours with every other provider's, model by model. Prices follow the market and can change; a render is charged at the price quoted when it was submitted.

FieldTypeRequiredDefaultDescription
currencystringAlways—usd.
unitstringAlways—second, image, render or 1000_characters.
usd_per_secondobjectNo—Video: the price of a second of output by resolution ({ "480p": 0.0207, … }), or under any when one price covers all.
usd_per_imageobjectNo—Images: the price of one image by resolution ({ "1K": 0.03, … }).
usd_per_rendernumberNo—A flat price per render.
usd_per_1000_charactersnumberNo—Speech: the price of 1,000 characters of script.
list_usd_per_second, list_usd_per_imageobjectNo—The list price where ours is below it, at the resolutions it is below.
with_reference_clipobjectNo—usd_per_second_of_clip_and_output: the rate for every second of a reference clip and the output together, by resolution; billed_seconds: how those seconds are counted (output + clip, or output + max(clip, output)).
tokensobjectNo—Seedance 2.5: usd_per_million tokens and the formula below.

A reference clip's seconds are charged (Seedance 2.5, Seedance 2.0, Kling 4.0, Wan 3.0 and Wan 3.0 Prime; an edit's source clip included): they are added to the output's, and every second of the two is billed at with_reference_clip.usd_per_second_of_clip_and_output. Seedance and Kling 4.0 have a lower rate per second with a clip; Wan bills a clip's seconds at its usual rate. On Kling 4.0 a clip shorter than the output counts as the output's length, as every provider bills it. usage.billed_seconds says how many seconds were billed.

Seedance 2.5 counts both in tokens: width × height × (24 × output seconds + 1 + 24 × reference-clip seconds) ÷ 1024, at tokens.usd_per_million (the with_reference_clip rate once a clip is attached).

Quote a request

Add ?dry_run=true to a create call to get its exact price with nothing held and nothing rendered. The same checks run, so a request that would be refused is refused here too.

curl https://api.elyum.ai/v1/generations?dry_run=true \
  -H "Authorization: Bearer $ELYUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "bytedance/seedance-2.5/image-to-video",
  "prompt": "The camera slowly pushes in as she turns toward the window",
  "inputs": [
    {
      "role": "first_frame",
      "url": "https://example.com/frame.jpg"
    }
  ],
  "resolution": "720p",
  "duration": 5
}'
Response · 200
{
  "object": "quote",
  "model": "bytedance/seedance-2.5/image-to-video",
  "task": "image-to-video",
  "settings": {
    "duration": 5,
    "resolution": "720p",
    "aspect_ratio": "16:9",
    "audio": true
  },
  "usage": {
    "usd": 1.42,
    "tokens": 108900
  }
}

Account

GET/v1/accountyour balance and this key's limits
Response · 200
{
  "object": "account",
  "balance": {
    "usd": 53,
    "api_spendable": {
      "usd": 3
    }
  },
  "plan": {
    "code": "pro",
    "name": "Pro"
  },
  "key": {
    "scopes": [
      "read",
      "generate",
      "spend"
    ],
    "can_generate": true,
    "daily_cap_usd": 10,
    "spent_24h_usd": 1.42,
    "remaining_24h_usd": 8.58
  }
}

api_spendable is the part of your balance the API can spend: the funds you added. A plan's monthly allowance is for the Elyum studio. When it runs out a render is refused with 402 insufficient_balance.

Logs and usage

Logs lists every request, from a key or the Playground: filters by key, model, status, day and id, the request and the answer of each, and a CSV export of up to 10,000 rows. Usage shows what was charged per day, key and model. Both are in dollars.

7.Errors

Every refusal comes back in one shape, with a stable code your code can branch on.

The error object

Response · 400
{
  "error": {
    "code": "invalid_parameter",
    "message": "bytedance/seedance-2.5/image-to-video renders 4–30 whole seconds, not 45.",
    "param": "duration",
    "fix": null,
    "retryable": false,
    "retry_after": null,
    "request_id": "req_6f1c…",
    "docs_url": "https://elyum.ai/open/docs#invalid-parameter"
  }
}
FieldTypeRequiredDefaultDescription
codestringAlways—Stable; see Error codes.
messagestringAlways—What went wrong, for a person.
paramstringNo—The request field at fault: duration, inputs[1].url, model.
fixstringNo—The next step, when there is one.
retryablebooleanAlways—The same request can be sent again as is (after retry_after when given).
retry_afternumberNo—Seconds to wait before retrying, when the refusal is temporary.
request_idstringAlways—The request's id, also in X-Request-Id.

Some codes carry more: allowed (the values a field takes), usd (a quote over max_usd), needs (a missing scope).

Error codes

StatusCodeMeaning
400invalid_requestinvalid_jsonThe body is not a request we can read.
400unknown_parameterinvalid_parameterunsupported_parameterA field we do not know, or a value the model does not render. param names it; allowed lists the values where there are a few.
400unknown_modeltask_mismatchambiguous_taskmissing_inputThe model and the inputs do not fit together.
400max_usdThe render costs more than max_usd (usd says how much). Nothing was held.
401authNo key, a key that is not valid, or an MCP token. fix names the page for an API key.
402insufficient_balanceInsufficient funds. Add funds on the Billing page of your console.
403scopeThe key lacks a scope; needs names it.
404not_foundunknown_modelNo such generation, file or model on this account.
409idempotency_conflictnot_cancelableThe key was used with another body, or the render already ended.
413request_too_largeA file over 60 MB, or a body over the limit.
415unsupported_media_typeA file that is not png, jpg, webp, mp4, mov, webm, mp3 or wav, or whose contents are not its declared type.
429key_capconcurrencyrate_limitedSlow down: retry_after says when.
503model_unavailableoption_unavailablemaintenanceWe cannot run this right now. Nothing was charged; retry later.
500internalOur fault. Retry; nothing was charged.

Limits

  • Daily spend cap per key, when you set one: 429 key_cap.
  • Renders in flight per account: past the limit a new render answers 429 concurrency. Wait for one to finish (a Prefer: wait call does it for you), then send again.
  • Request rate: 429 rate_limited with retry_after.
  • Per request: up to 40 inputs, files up to 60 MB, one output (n: 1), waits up to 55 seconds.

8.Businesses

Inside an Elyum business a key belongs to one client, to the business level, or to any client. A key for one client renders for that client. A key that may act for any client names the client on each request with the Elyum-Client header (the client's id or its exact name). Monthly budgets per member and per client are hard limits: 429 member_budget and 429 client_budget hold nothing.

curl https://api.elyum.ai/v1/generations \
  -H "Authorization: Bearer $ELYUM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Elyum-Client: Northwind" \
  -d '{
  "model": "bytedance/seedance-2.5/image-to-video",
  "prompt": "The camera slowly pushes in as she turns toward the window",
  "inputs": [
    {
      "role": "first_frame",
      "url": "https://example.com/frame.jpg"
    }
  ],
  "resolution": "720p",
  "duration": 5
}'

9.Coming next

Not built yet: webhooks, server-sent events, an OpenAPI file, SDK packages and test keys. Until then, the samples here use plain HTTP (fetch, requests), and Prefer: wait stands in for a webhook on most renders.