Quickstart
One key, one request shape, every image and video model. Submit a job, poll it, get the file.
/data/models.json.| Base URL | https://api.revana.si/v1 |
| Auth | Authorization: Bearer <key> |
| Submit | POST /v1/generations |
| Poll | GET /v1/generations/{id} |
| Models | GET /v1/models (no key): every model you can generate with right now |
| Format | JSON in, JSON out. Prices in USD; 100 credits = $1. |
| Machine-readable | /openapi.json · /llms.txt |
Authentication
Create a key from your account, then send it as a bearer token on every request. Keep it on your server; never ship it in a browser or mobile app.
Submit a generation
Send the model id and its input. The response comes back straight away with a generation id and status queued; the work happens in the background.
| Field | Type | Description |
|---|---|---|
model | string, required | A model id from the models page, e.g. seedance-2.5. |
input | object, required | The prompt and settings; see input fields. Unknown fields are refused. |
max_cost_usd | number, optional | The most you will pay for this generation, up to 100 (the default). Routes priced above it are skipped; if none is left the request answers 422 max_cost_exceeded. |
max_wait_s | integer, optional | Routes expected to take longer than this many seconds are skipped. Once it has passed no new route is tried, but one already running is allowed to finish (up to 40 minutes) and is charged if it delivers. |
Idempotency-Key | header, optional | Up to 200 characters. Send the same key and body again (after a timeout or a 503) and you get the same generation back, not a second one. The same key with a different body is 409 idempotency_conflict. |
Get the result
Poll every few seconds until status is succeeded or failed, then download the files in output.urls with your key: GET /v1/generations/{id}/content for the first, and /content/2, /content/3… when a picture model returns several. Images usually take seconds; video takes from under a minute to several minutes depending on the model and length.
A finished generation:
Input fields
The same names are used across models. Which fields and values each model takes, and its defaults, are listed per model in /data/models.json (options, tasks). If a model doesn’t support a field or value, the request is rejected with 422 invalid_input before anything is charged.
| Field | Type | Used for |
|---|---|---|
prompt | string | What to generate. Required for text-to-image and text-to-video. |
image_url | string (URL) | Start frame for image-to-video, or the image to edit for image-to-image. |
end_image_url | string (URL) | Last frame, for models with first + last frame. |
image_urls | string[] | Reference images, up to the model’s limit. |
resolution | string | 720p, 1080p, 2K... one of the model’s resolutions. |
duration | number | Video length in seconds, one of the model’s durations. |
aspect_ratio | string | 16:9, 9:16, 1:1... |
audio | boolean | Generate sound with the video, where the model supports it. May change the price. |
negative_prompt | string | What to avoid, on models that take it. |
video_url | string (URL) | A clip to transform, extend or upscale. Its length is measured and, on per-second models, billed. |
audio_url | string (URL) | The voice or sound a talking-avatar or lip-sync model animates to. |
quality | string | A quality tier on models priced by one (e.g. low, medium, high). |
seed | integer | A seed, on models that take one. |
Seedance 2.5 defaults to 720p, 5 seconds (4–30), 16:9 and no sound, and also takes:
| Field | Type | Used for |
|---|---|---|
video_url | string (URL) | A 4–30 s clip to edit. The result keeps its length, so leave out duration. |
video_urls | string[] | Reference clips, up to 10 and 30 seconds in all. Their seconds are billed at the video-input rate. |
audio_urls | string[] | Reference sound, up to 10 and 30 seconds in all. |
image_url is an exact first frame, so it can’t be combined with reference media; use image_urls for a looser visual reference. end_image_url needs image_url. Media must be public https URLs that download directly.
Generation object
| Field | Type | Description |
|---|---|---|
id | string | Generation id (gen_…), used to poll. |
object | string | Always generation. |
model | string | The model you asked for. |
status | string | See statuses. |
message | string | Only while a result is being confirmed with a provider: what is happening. |
quoted_usd | number | The price on the cheapest route, which is the one tried first. |
reserved_usd | number | What is held from your balance: the dearest route that may run. The difference is released when it settles. |
output | object | null | When succeeded: urls, the result files (a picture model can return several), with width and height; video also has duration_s and has_audio. Download them promptly; files expire. |
cost_usd | number | null | What was charged. Set once the generation succeeds; 0 when it fails. |
attempts | number | Routes tried, including fallbacks. Only the successful one is billed. |
error | object | null | When failed: code and message. |
created_at, completed_at | string | ISO 8601 timestamps. |
Statuses
| Status | Meaning |
|---|---|
queued | Accepted, waiting for a route. |
running | Generating. If a route fails, the job stays running while it moves to the next one, and while a result is being confirmed (see message). |
succeeded | Done. output.urls holds the files and cost_usd what you paid. |
failed | Every route failed or the input was refused. Nothing is charged. |
Billing
- You pay from prepaid credits, 100 to the dollar, bought in packs from $10 to $5,000. Every price is a whole number of credits.
- When you submit,
reserved_usdis held from your balance. If the generation fails, all of it is released. - Only the route that succeeds is charged, at the price quoted for it when you submitted. Failed attempts on other routes cost nothing.
- There is no monthly quota or per-minute cap: your balance and
max_cost_usdare the limit. - Each model’s price is on its page and in the price table.
Errors
Errors use HTTP status codes and a JSON body: {"error": {"code": "...", "message": "..."}}.
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | The body isn’t JSON, is missing model or input, or has a field the API doesn’t know. |
| 401 | unauthorized | Missing or invalid API key. |
| 402 | insufficient_credits | Your available balance doesn’t cover reserved_usd. Top up and retry. |
| 403 | account_disabled | The account is paused. Contact support. |
| 403 | generation_not_enabled | Your key can read prices but can’t generate yet. |
| 404 | not_found | No generation with this id on your account. |
| 409 | idempotency_conflict | This Idempotency-Key was already used with a different body. |
| 409 | not_ready | Asked for the file before the generation succeeded. |
| 413 | body_too_large | The body is over 1 MB. Send media as URLs. |
| 422 | invalid_input | A field, value or combination the model doesn’t support, or media that can’t be read. Nothing is charged. |
| 422 | max_cost_exceeded | Every route costs more than max_cost_usd. |
| 422 | model_not_available | No model with this id on the API. Ids are on the models page. |
| 429 | rate_limited | Too many requests in the same second from one account (a guard against runaway loops, not a quota). Wait the Retry-After seconds. |
| 429 | concurrency_limit | The account already has its maximum of generations in progress. Retry when one finishes. |
| 503 | restarting, busy | A restart or a moment of load. Retry after Retry-After with the same Idempotency-Key. |
| 503 | model_unavailable | No route for this model is up right now. Retry shortly. |
| 500 | server_error | Something broke on our side. Safe to retry with the same Idempotency-Key. |
Model list
Every model, its options and prices are published as JSON at /data/models.json, the same file this site renders from.