1.Overview
What it is
The server is the REST API as tools, one per endpoint. It takes the same keys, charges the same prices and answers the same objects as /v1, so everything an agent does here is a /v1 request: listed in your console's Logs under its key and paid from your API balance. Use it when an agent builds your integration, or renders in a pipeline nobody watches.
Endpoint
Streamable HTTP at https://api.elyum.ai/v1/mcp, stateless, with a bearer token on every request. A client that supports MCP sign-in finds our authorization server on its own: the first answer, a 401, names it. The others send an API key.
The studio's MCP server
Elyum's studio has its own MCP server, for an agent that works with a person in the studio: renders come back as previews to keep or kill, paid from studio credits. It is documented on its own: Studio MCP. Its MCP tokens (ek_mcp_…) are refused here with 401 auth.
2.Connect a client
With OAuth there is no key to paste: the client opens our consent page, you choose what it may do, and it is connected. A client without it takes an API key from your console in place of ek_live_….
Claude Code
One command in your terminal. Claude Code signs in through our consent page (OAuth); or pass an API key in a header.
claude mcp add --transport http elyum-api https://api.elyum.ai/v1/mcp
# or, with an API key:
claude mcp add --transport http elyum-api https://api.elyum.ai/v1/mcp --header "Authorization: Bearer ek_live_…"Claude.ai and Desktop
Settings → Connectors → Add custom connector. Paste the URL; Claude signs in through our consent page (OAuth), where you pick scopes and a daily cap in dollars.
Name: Elyum API
URL: https://api.elyum.ai/v1/mcpCursor
Click the button (deep link) or add it to ~/.cursor/mcp.json. Leave out headers and Cursor signs in through our consent page instead.
{
"mcpServers": {
"elyum-api": { "url": "https://api.elyum.ai/v1/mcp", "headers": { "Authorization": "Bearer ek_live_…" } }
}
}Codex
Codex CLI and IDE read the key from the environment variable named here.
# ~/.codex/config.toml
[mcp_servers.elyum-api]
url = "https://api.elyum.ai/v1/mcp"
bearer_token_env_var = "ELYUM_API_KEY" # export ELYUM_API_KEY=ek_live_…Other clients
Windsurf, VS Code, Cline, Zed… any client that takes an mcpServers or servers block. Clients with MCP sign-in need only the URL; the others send the key as a bearer header.
{
"mcpServers": {
"elyum-api": {
"type": "http",
"url": "https://api.elyum.ai/v1/mcp",
"headers": { "Authorization": "Bearer ek_live_…" }
}
}
}Signed in, MCP in your console shows the same setup. Keep the key out of anything you commit: most clients read it from an environment variable.
3.Authentication
Two ways in, one set of rules: an OAuth connection and an API key have the same scopes, the same daily cap in dollars and the same prices.
OAuth
Claude.ai, Claude Desktop, Claude Code, Cursor and most current clients sign in with OAuth. The client registers itself and opens the consent page on elyum.ai, where you choose the scopes, a daily cap in dollars and, inside a business, which client the connection works for. It then holds a token for this server that renders and pays like an API key and renews itself. Connected apps are listed under Keys in your console; disconnecting one there signs it out at once.
API keys
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; a revoked key answers 401 auth from then on.
Scopes
| Scope | Lets the agent |
|---|---|
read | List models, read generations and the account. |
generate | Start renders and upload files. |
spend | Pay for them. A render is paid on delivery, so a key that renders needs generate and spend. |
Daily spend cap
A key or a connection can carry a daily cap in dollars: 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.
4.How a render works
The workflow
- 1
get_accountThe balance the API may spend, and the key's scopes and cap. - 2
list_models,get_modelPick a model and read what it takes: inputs, settings and their allowed values. - 3
create_generationwithdry_run: trueThe price inusage.usd, with nothing held. Free. - 4
create_generationwithwait: 55Starts the render and holds the answer until it ends; else callget_generationwithwaituntilstatusissucceeded. - 5The outputsSigned links to the final files, at full quality.
Prices and billing
A render the server starts is a /v1 render in every way: priced like the API, in US dollars, and paid from your API balance, the funds you add on Billing. It is charged when it is delivered and free when it fails or is canceled. It is refused before anything is held when it would cost more than max_usd, the key's daily cap or the balance.
There are no previews and nothing to keep or kill: the file a render returns is the final one.
5.Tools
Every tool the server registers, with the REST endpoint it stands for. Arguments have the REST names and values; one the API does not know is refused by name (unknown_parameter), never dropped.
Every model the key may call, with its kind, task, price in US dollars and typical delivery time. Each model is a summary unless full is true; get_model has the rest.
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "video" | "image" | "audio" | "upscale" | No | — | Only models of this kind. |
task | string | No | — | Only this task, e.g. text-to-video, image-to-video, text-to-image, image-edit. |
full | boolean | No | default false | Every field of each model (inputs, settings, options). Large: narrow it with kind or task. |
One model (owner/model/task), or every task of a model (owner/model): its inputs, settings with their allowed values and defaults, options and price.
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | Yes | — | A model id, e.g. bytedance/seedance-2.5/image-to-video, or bytedance/seedance-2.5 for all its tasks. |
Render with any model. The render is final and charged on delivery; dry_run: true answers the price with nothing held. The arguments are the request body's fields, plus dry_run, wait and idempotency_key, which REST sends outside the body.
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | Yes | — | A model id: owner/model/task, or owner/model to let the inputs pick the task. |
prompt | string | No | — | What to make. Cite inputs as @image1, @video1, @audio1 or their @name. |
inputs | object[] ≤ 40 | No | — | Images, clips, sounds or characters: a role and exactly one of url, file, generation or id (see Inputs). |
duration | number | No | — | Output seconds (video, audio). |
resolution | string | No | — | One of the model's resolutions, e.g. 720p or 2K. |
aspect_ratio | string | No | — | One of the model's ratios, e.g. 16:9, 9:16, auto. |
audio | boolean | No | — | Sound on or off, where the model renders sound. |
voice | string | No | — | Speech models: the voice. |
target | string | No | — | Upscalers: the target size, e.g. 4K. |
options | object | No | — | Model-specific settings get_model lists under options. |
max_usd | number | No | — | The most this render may cost in US dollars; a higher price is refused before anything is held. |
metadata | object | No | — | Up to 16 string pairs of your own, returned on the generation. |
dry_run | boolean | No | default false | Answer the price (usage.usd) and the settings that would render, with nothing held or charged. |
wait | integer 0–55 | No | — | Seconds to hold the answer until the render ends (REST's Prefer: wait). Call again until status is succeeded, failed or canceled. |
idempotency_key | string ≤ 255 | No | — | Any unique string (printable ASCII, no spaces). A retry with the same key and body answers the first generation instead of rendering twice. |
A render's status, its outputs as signed links once it succeeds, and usage.usd: held while it runs, charged once delivered, refunded when it fails.
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | The generation id (g_…) create_generation answered. |
wait | integer 0–55 | No | — | Seconds to hold the answer until the render ends (REST's Prefer: wait). Call again until status is succeeded, failed or canceled. |
Stop a render that has not finished. Its hold is refunded and nothing is charged. A render that already ended is refused (not_cancelable).
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | The generation id (g_…) create_generation answered. |
Bring an image, a clip (up to 60 MB) or a sound in from a public URL or base64 bytes. Returns a file_… id to use as an input. Free; needs the generate scope.
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | No | — | A public http(s) URL to fetch. Give url or base64. |
base64 | string | No | — | The file's bytes, base64 (a data: prefix is fine). |
content_type | string | No | — | Required with base64, e.g. image/png. |
filename | string ≤ 200 | No | — | The file's name; its extension sets the type when nothing else does. |
The balance in US dollars and the part of it the API may spend (the funds you added), the plan, and the key's scopes and rolling 24-hour cap.
No arguments.6.Answers and errors
Answers
Every answer is the /v1 object as it is: a model, a quote, a generation, a file, the account. What REST puts in headers rides the result's _meta: request_id, and idempotent_replayed when an idempotency key answered the first generation.
Errors
A refusal is a tool error that carries the error object (code, message, param, fix, request_id) and a line telling the agent whether to fix the call, retry, or stop and ask you. Every code is in Error codes; these stop the agent:
| Code | Meaning |
|---|---|
insufficient_balance | Insufficient funds. Add funds on Billing. |
key_cap | The key's daily cap is reached. |
scope | The key lacks a scope the tool needs; the error names it. |
auth | No valid API key or connection. An expired OAuth token is renewed by the client. |
member_budget | Over your monthly limit in this business. |
client_budget | Over the client's monthly budget. |
7.Businesses and clients
As on the REST API: a key for one client works for that client, and a business-level key for the business. A key that may act for any client either connects with the Elyum-Client header (the client's id or exact name) and works for that client throughout, or names it per call: without the header, create_generation, get_generation, cancel_generation and upload_file take a required client argument, and an unknown one is refused with client_required and the list of clients. Monthly budgets per member and per client hold, as 429 member_budget and 429 client_budget.