Reference
Developer documentation
One key for image, video, voice and LLMs, with the exact charge on every response.
01
Quickstart
A key, a POST, a finished image with its price attached.
- 01Create an account and add prepaid credit, from $5.
- 02Create an API key in the dashboard. It is shown once.
- 03Send the request below with that key.
curl -X POST https://api.corent.tech/v1/images/generate \ -H "Authorization: Bearer $CORENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "a fox reading a book", "tier": "air"}'{ "id": "...", "status": "completed", "images": [{ "url": "https://...", "width": 1024, "height": 1024 }], "meta": { "model": "image-air", "cost_cents": 2, "duration_ms": 1500 }}Images usually return inline with the charge. A slow render comes back as a job id to poll instead.
02
Authentication
Every request carries a bearer token from a key you created in the dashboard.
Authorization: Bearer co_live_...- Base URL
- https://api.corent.tech
- Scope
- Server-side only, never in browser code.
A key can be locked to specific models, or to tiers only, when you create it.
Keys are created, listed and revoked in the dashboard, where you also set each key's limits. The account endpoints behind that screen take a dashboard login, not an API key, so a leaked key can never create more keys or raise its own cap.
03
Images
POST/v1/images/generate
Generate an image. Returns inline unless you ask for a job.
| Field | Type | Required | Description |
|---|---|---|---|
| prompt | string | yes | What to generate. |
| tier | string | no | air, lite, premium, pro or max_pro. Omit it if you pass model. |
| model | string | no | Pin an exact model from GET /v1/models. Never substituted. |
| style | string | no | Style hint, for example photorealistic. |
| aspect_ratio | string | no | For example 1:1, 16:9 or 9:16. |
| async | boolean | no | true returns 202 with a job id. Use it for max_pro and other slow renders. |
| reference_image_urls | string[] | no | One to four public https images. The prompt is applied as an EDIT of them, so a face, character or product stays the same across generations. Served by premium and up; air and lite refuse it with a 400 naming the tiers that work. |
| n | number | no | 1 to 10. Each one is a real render at the normal price, so n=4 costs four images. meta.cost_cents is the total. |
| seed | number | no | Same seed, prompt and model gives the same image, so you can render it again and change one thing. The seed used comes back in meta.seed even when you did not send one. |
| negative_prompt | string | no | What must NOT appear, for example text or a watermark. |
| source_image_url | string | no | Start from this picture instead of from noise. Pair it with strength. |
| strength | number | no | 0 to 1. How far the render may travel from source_image_url. 0 keeps it, 1 ignores it. |
| output_format | string | no | png, jpeg or webp. png is the one that carries transparency. |
| transparent | boolean | no | Transparent background for logos and cutouts. Forces png. |
| width / height | number | no | Explicit pixel size. Pass both or neither. |
| enhance_prompt | boolean | no | Corent rewrites your prompt before dispatch to get a better render. false sends it exactly as typed. |
| customer_id | string | no | 1 to 128 characters. Your own id for the end customer this render is for. Comes back on the receipt and filters GET /v1/account/usage. |
| Idempotency-Key | header | no | Any unique string up to 255 chars. A repeat returns the original job instead of billing twice. |
Not every model takes every setting. Whatever the chosen model could not honour comes back in meta.unsupported_options, so an ignored setting never reads as an applied one.
{ "prompt": "a fox reading a book in a cozy library", // one of air, lite, premium, pro, max_pro "tier": "air", // both optional "style": "photorealistic", "aspect_ratio": "1:1", // optional; true returns 202, then poll /v1/jobs/{id} "async": false}POST/v1/images/edit
Regenerate only the masked region and leave every other pixel alone.
Different from reference_image_urls, which REGENERATES your subject from a reference, so its logo and colour are a likeness rather than a copy. Here the unmasked pixels are your own file, byte for byte, which is what an advertisement needs when the product carries someone else’s trademark. Send image_url, a black-and-white mask_url the same size (white is regenerated, black is preserved) and a prompt.
POST/v1/images/upscale
Make an existing image bigger and sharper. Send image_url, and an optional prompt to guide the added detail. Flat price, no tier.
POST/v1/images/remove-background
Cut the subject out. Returns a PNG with a real transparent background, for logos, product shots and compositing. Send image_url.
04
Video and jobs
POST/v1/videos/generate
Returns 202 with an id. Video takes one to five minutes and bills per second of output.
| Field | Type | Required | Description |
|---|---|---|---|
| prompt | string | yes | What to generate. |
| tier | string | no | air, lite, premium, pro or max_pro. Omit it if you pass model. |
| model | string | no | Pin an exact model from GET /v1/models. Never substituted. |
| duration_s | number | no | Seconds of output. The tier menu carries the allowed range; corent-seedance-2.5 takes 4 to 30. |
| resolution | string | no | 720p, 1080p or 4k. Above the tier cap it clamps down, it never errors. corent-seedance-2.5 goes up to 1080p. |
| aspect_ratio | string | no | For example 16:9 or 9:16, or adaptive to follow the input frame or clip. |
| image_url | string | no | Animate an existing image instead of generating from text alone. |
| audio | boolean | no | Ask for native sound. true routes only to models that actually render audio, so a silent model can never quietly serve the request; false prefers a silent one. GET /v1/tiers reports supports_audio per tier. |
| end_image_url | string | no | The frame the clip should END on. With image_url this is the go-from-A-to-B effect. |
| camera | string | no | static, pan_left, pan_right, zoom_in, zoom_out, orbit_left, orbit_right, tilt_up or tilt_down. |
| negative_prompt | string | no | What must NOT appear in the clip. |
| seed | number | no | Reproducibility, same contract as images. |
| fps | number | no | Frames per second, where the model offers a choice. |
| customer_id | string | no | 1 to 128 characters. Your own id for the end customer. Rides on the job and its receipt. |
{ "prompt": "a small sailboat crossing a calm harbor", "tier": "premium", "aspect_ratio": "16:9", "duration_s": 5, // tier-capped: clamped down, never rejected "resolution": "720p", // optional, image-to-video "image_url": "https://..."}GET/v1/jobs/{id}
Poll a job. The charge lands here when the video completes, next to the delivered pixel dimensions measured from the stored file.
Prefer GET /v1/jobs/{id}/stream, which is server-sent events, to avoid polling.
Or pass webhook_url on the request and the full result is POSTed to you when the job finishes. Add webhook_secret and every delivery carries X-Corent-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of the secret over "t.rawbody" (Stripe's scheme). Verify over the raw bytes, compare in constant time, and reject a t older than 300 seconds. Corent tries up to three times, at about 0, 2 and 6 seconds, and GET /v1/jobs/{id}/webhook_deliveries lists every attempt. An unsigned delivery only means no secret was given.
{ "id": "...", "status": "completed", "videos": [{ "url": "https://...", "width": 1920, "height": 1080, "resolution": "1080p", "duration_s": 5 }], "meta": { "model": "video-premium", "cost_cents": 162, "duration_ms": 94000 }}POST/v1/jobs/{id}/cancel
Stop a job that has not finished and release its money hold. Charges nothing. A job that completed just before the cancel landed stays completed and stays billed for what was actually delivered, and the cancelled flag in the reply tells you which happened. Cancelling is best effort against the supplier: the render may already be running on their side, and that cost is ours to absorb, not yours.
Consistent characters, editing, and extending
Six more fields on POST /v1/videos/generate keep a character, product or place the same from shot to shot, change a clip you already have, or carry one forward. Name a reference in the prompt by its position in the list: @Image 1, @Video 2, @Audio 1.
| Field | Type | Required | Description |
|---|---|---|---|
| reference_image_urls | array | no | Up to 30 urls. Keep a character, product or place consistent. Refer to them in the prompt as @Image 1, @Image 2, in the order given. |
| reference_video_urls | array | no | Up to 10 urls. Copy motion, camera or style from a clip, and the source for edit and extend. @Video 1 in the prompt. |
| reference_audio_urls | array | no | Up to 10 urls. Follow a voice or a music track. @Audio 1 in the prompt. |
| task | string | no | auto (default), reference, edit or extend. edit changes an existing clip: at least one reference video, and words like add, remove, replace or change in the prompt. extend continues a clip forward or backward: a reference video, and words like extend or continue. |
| return_last_frame | boolean | no | true adds last_frame_url to the finished job, a PNG of the final frame stored by Corent. Pass it as image_url on the next call and the next shot starts exactly where this one ended. |
| output_format | string | no | mp4 (default) or mov, which keeps more colour precision for grading. |
Four rules. First and last frame (image_url and end_image_url) and the reference_* fields cannot be mixed in one call. On first/last-frame, edit and extend tasks the aspect ratio follows the input, so omit aspect_ratio or pass adaptive. An edit keeps the length of the source clip, so omit duration_s. These fields work today on corent-seedance-2.5, so pin it with model; any other model answers with a clear 400 and bills nothing.
GET /v1/models reports what each video model can do: supports_reference_images, max_reference_images, supports_reference_videos, supports_reference_audio, supports_video_edit, supports_video_extend, supports_last_frame and supports_first_last_frame. Build the picker from those flags, not from this page.
Real faces: the supplier refuses photos or videos of real people as references. It accepts characters made inside Corent, meaning any image you generated through the API in the last 30 days. Make the character first, then reference it.
curl -X POST https://api.corent.tech/v1/videos/generate \ -H "Authorization: Bearer $CORENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "@Image 1 steps out of the cafe into light rain, handheld, dusk", "model": "corent-seedance-2.5", "reference_image_urls": ["https://..."], "duration_s": 8, "return_last_frame": true }' // the finished job carries the final frame as a PNG stored by Corent:// "last_frame_url": "https://..."curl -X POST https://api.corent.tech/v1/videos/generate \ -H "Authorization: Bearer $CORENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "she opens an umbrella and crosses the street", "model": "corent-seedance-2.5", "image_url": "https://.../last_frame.png", "duration_s": 8, "return_last_frame": true }' // image_url is the last_frame_url of the previous job, so shot two// starts on the exact frame shot one ended on. No aspect_ratio: it// follows the frame.curl -X POST https://api.corent.tech/v1/videos/generate \ -H "Authorization: Bearer $CORENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "continue @Video 1 forward: she reaches the far kerb and looks back", "model": "corent-seedance-2.5", "task": "extend", "reference_video_urls": ["https://..."], "duration_s": 5 }' // the new footage continues the clip; the prompt says forward or backwardcurl -X POST https://api.corent.tech/v1/videos/generate \ -H "Authorization: Bearer $CORENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "in @Video 1 replace the red umbrella with a yellow one, keep everything else", "model": "corent-seedance-2.5", "task": "edit", "reference_video_urls": ["https://..."] }' // no duration_s: an edit keeps the length of the source clipThe connector (0.9.0) takes the same fields on generate_video and adds edit_video and extend_video. The SDKs (0.5.0) take them on videos.generate and add videos.edit(...) and videos.extend(...).
05
Films
One sentence in, one finished film with sound out.
Describe a film in one sentence and Corent makes the whole thing. It writes the script, creates the characters once, and shoots every scene with those same characters speaking their lines. It checks each scene, reshoots a bad one once, and joins everything into one video with sound.
| Endpoint | Method | What it does |
|---|---|---|
| /v1/films/plan | POST | Writes the film and prices it. Free. The plan is kept for 24 hours. |
| /v1/films | POST | Starts making a planned film, or plans and makes one from a brief. Returns 202 with a film_id. |
| /v1/films/{film_id} | GET | The stage, progress, each scene, and when done the finished video_url and the charge. |
| /v1/films/{film_id}/stream | GET | The same, pushed as server-sent events on every change, instead of polling. |
| /v1/films | GET | Your films, newest first, one page at a time. Takes ?customer_id= to narrow it. |
| /v1/films/{film_id}/cancel | POST | Stops a film that has not finished. A cancelled film is not billed. |
POST/v1/films/plan
| Field | Type | Required | Description |
|---|---|---|---|
| brief | string | yes | The film in a sentence, up to 2000 characters. |
| duration_s | number | no | Length of the finished film, 10 to 120 seconds. Default 30. |
| aspect_ratio | string | no | 16:9 (default), 9:16 or 1:1. |
| resolution | string | no | 720p (default) or 1080p. 1080p costs more. |
| language | string | no | The language the characters speak, for example en. |
| customer_id | string | no | 1 to 128 characters. Your own id for the end customer. |
curl -X POST https://api.corent.tech/v1/films/plan \ -H "Authorization: Bearer $CORENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "brief": "a 50 second parody where 5 founders start a startup, know nothing, and the AI part is easy because they used Corent", "duration_s": 50, "aspect_ratio": "16:9" }'{ "plan_id": "...", "title": "...", "logline": "...", "style": "...", "characters": [{ "id": "c1", "name": "...", "description": "..." }], "scenes": [{ "index": 0, "duration_s": 6, "setting": "...", "action": "...", "camera": "...", "characters": ["c1"], "dialogue": [{ "character_id": "c1", "line": "..." }] }], "total_duration_s": 50, "estimated_cost_cents": 1400, "estimated_cost_range": { "min": 1000, "max": 2000 }, // the plan is kept for 24 hours "expires_at": "..."}POST/v1/films
| Field | Type | Required | Description |
|---|---|---|---|
| max_cost_cents | number | yes | The most this film may cost. If the estimate is above it the request is refused with a 400 and nothing is spent. |
| plan_id | string | no | The plan from POST /v1/films/plan: the story and price you showed. Send this or a brief. |
| brief | string | no | Instead of plan_id: the film in a sentence, with the same options as the plan call, planned and made in one go. |
| webhook_url | string | no | Receives the finished film, signed like every other webhook when webhook_secret is set. |
| webhook_secret | string | no | Signs the delivery with X-Corent-Signature. |
| customer_id | string | no | 1 to 128 characters. Rides on the film and its receipt. |
| Idempotency-Key | header | no | A repeat returns the original film instead of starting a second one. |
curl -X POST https://api.corent.tech/v1/films \ -H "Authorization: Bearer $CORENT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: film-order-42" \ -d '{ "plan_id": "...", "max_cost_cents": 1600 }' // 202: { "film_id": "...", "status": "queued", "estimated_cost_cents": 1400 }A 402 means the balance is too low for the estimate. A 50 second 720p film with five characters costs roughly $10 to $20 and takes about 15 to 30 minutes, so follow it on the stream, poll GET /v1/films/{film_id} about once a minute, or pass webhook_url.
The stages
- queued
- Waiting to start.
- writing
- Writing the script: the story, the scenes and every line of dialogue.
- casting
- Creating the characters, once, so each one looks the same in every scene.
- shooting
- Shooting the scenes with those characters speaking their lines.
- checking
- Checking each scene, and reshooting a bad one once.
- editing
- Joining the scenes into one video with sound.
- completed
- Done. video_url is the finished film and cost_cents is the charge.
- failed
- It could not be finished. Not billed. error_message says why.
- cancelled
- Stopped on request. Not billed.
progress_percent and stage_detail say how far along it is, and each scene carries its own status, its clip and how many times it was reshot. quality_notes lists anything the checks found.
{ "film_id": "...", "status": "completed", "progress_percent": 100, "title": "...", "video_url": "https://...", "duration_s": 50, "cost_cents": 1312, "billed": true, "quality_notes": ["..."], "scenes": [{ "index": 0, "status": "completed", "video_url": "https://...", "reshoots": 0 }]}The billing promise. The price is shown before anything is paid: planning is free. The charge never goes above max_cost_cents. A film that fails or is cancelled is not billed, and reads billed: false.
POST /v1/intent now answers task_type film, with next_step POST /v1/films/plan, for anything one clip cannot deliver: longer than one take, or several characters telling a story. /v1/intent/execute will not make a single clip for it. The connector (0.10.0) has plan_film, make_film, get_film and cancel_film. The SDKs (0.6.0) have films.plan, films.create, films.get, films.wait, films.cancel and films.list.
06
Speech
POST/v1/audio/speech
Synchronous. Returns the finished audio file and the exact charge.
| Field | Type | Required | Description |
|---|---|---|---|
| text | string | yes | The text to speak. Billed per 1,000 characters, started thousands count in full. |
| voice_id | string | no | From GET /v1/voices. Omit it for the default narration voice. |
| model | string | no | Pins an exact speech model, for example corent-eleven-multilingual-v2. |
| stability | number | no | 0 to 1. Low is more expressive and varies more take to take; high is steadier. |
| similarity | number | no | 0 to 1. How closely to hold the original voice's character. |
| style | number | no | 0 to 1. Extra expressiveness. |
| speed | number | no | Playback speed. 1.0 is natural pace. |
| language | string | no | ISO code for the multilingual models, for example en or pt-BR. |
| output_format | string | no | mp3, wav or pcm. |
| customer_id | string | no | 1 to 128 characters. Your own id for the end customer. Rides on the receipt. |
{ "id": "...", "status": "completed", "audio_url": "https://...", "meta": { "model": "corent-speech-tts", "cost_cents": 14, "duration_ms": 1840 }}corent-speech-tts is a tier name, not a model. Sending it back as model returns 404. Pinnable names come from GET /v1/models.
{ "text": "Welcome to Corent. One API for image, video, and voice.", // optional "voice_id": "...", // optional; pins a speech model "model": "corent-eleven-multilingual-v2"}GET/v1/voices
Every voice this endpoint accepts as voice_id, with a display name, a preview clip and neutral descriptors you can filter on. Call it before naming a voice: voice_id cannot be guessed, and until this existed the only reliably reachable voice was the default.
07
Chat completions
POST/v1/chat/completions
OpenAI-compatible. Point any OpenAI SDK at the base URL and keep your code.
| Field | Type | Required | Description |
|---|---|---|---|
| model | string | yes | A tier, corent/text-premium, or an exact model from GET /v1/models. |
| messages | array | yes | The conversation, in the OpenAI shape. |
| max_tokens | number | no | Upper bound on the reply. |
| stream | boolean | no | true streams the reply as server-sent events. |
| customer_id | string | no | 1 to 128 characters. Your own id for the end customer. Rides on the receipt, same as every other generating call. |
{ // a tier, or an exact model such as "corent-gpt-5.6-sol" "model": "corent/text-premium", "messages": [{ "role": "user", "content": "Write a haiku about shipping fast." }], "max_tokens": 200, "stream": false}{ "id": "chatcmpl-...", "object": "chat.completion", "model": "corent/text-premium", "choices": [{ "message": { "content": "..." }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 14, "completion_tokens": 19, "total_tokens": 33 }, "corent": { "cost_cents": 1 }}Streaming, tool calling and JSON mode pass through. Billed per token, with the charge on the corent.cost_cents extension.
08
Uploads
POST/v1/uploads
Send a file, get a url. Every other endpoint accepts that url wherever it asks for an image.
Two ways to send it: multipart with a single field named file, or JSON with the bytes in base64. Up to 25 MB either way. The url comes back ready to pass as image_url, mask_url, source_image_url, end_image_url or a reference, so a file on your side never has to be public first.
| Field | Type | Required | Description |
|---|---|---|---|
| file | multipart | no | The file itself, as a multipart form field. Use this form or the JSON form, not both. |
| data_base64 | string | no | The file, base64-encoded, for the JSON form. |
| content_type | string | no | The MIME type, for example image/png. Required with data_base64. |
| filename | string | no | Optional. Kept for your own reference. |
curl -X POST https://api.corent.tech/v1/uploads \ -H "Authorization: Bearer $CORENT_API_KEY" \ -F "file=@product.png"{ // the file, base64, no data: prefix "data_base64": "iVBORw0KGgo...", "content_type": "image/png", // optional, kept for your own reference "filename": "product.png"}{ "id": "...", "url": "https://...", "content_type": "image/png", "bytes": 184322}| Field | Type | Description |
|---|---|---|
| id | string | The upload id. |
| url | string | The address every other endpoint accepts as image_url, mask_url or a reference. |
| content_type | string | The type as stored. |
| bytes | number | The size as stored. |
{ "prompt": "the same bottle on a wet slate counter", "tier": "premium", // the url from POST /v1/uploads "reference_image_urls": ["https://..."]}An upload is not a generation. It costs nothing and appears on no receipt.
09
Tiers and pinning
Name a tier and the router picks the model. Name a model and it runs exactly that.
Tiers
Five tiers: air, lite, premium, pro, max_pro. Send tier on image and video, and call a language tier as model: corent/text-premium.
Video adds resolution: premium and pro reach 1080p, max_pro reaches 4K. Receipts and status spell tiers out in full, as image-air or video-max_pro.
Preferences
One word instead of a tier. It resolves like this.
| Preference | Optimizes for | Image tier | Video tier |
|---|---|---|---|
| cheap | Cost first | Air | Air |
| fast | Latency first | Air | Air |
| balanced | Cost, speed and quality equally | Lite | Premium |
| quality | Output quality first | Premium | Pro |
Pinning a model
Pass model instead of tier for direct access: no routing, no substitution. If it cannot deliver, the request fails and you pay nothing.
curl -X POST https://api.corent.tech/v1/images/generate \ -H "Authorization: Bearer $CORENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "a fox reading a book", "model": "corent-flux-schnell"}' // the response names the model you pinned:// "meta": { "model": "corent-flux-schnell", "cost_cents": 2, ... }Names read corent-flux-schnell. The bare name flux-schnell still resolves, so older code keeps working.
- Models
- GET /v1/models, public, no authentication.
- Tiers
- GET /v1/tiers, with per-resolution prices and live status.
- Status
- GET /v1/status, one line per tier.
10
Response and receipt
Every success says what served it and what it cost. The meta block is the receipt.
| Field | Type | Description |
|---|---|---|
| id | string | The request or job id. |
| status | string | completed, processing or failed. |
| meta.model | string | The tier that served the request, or the exact model you pinned. |
| meta.cost_cents | number | The exact charge for this request, in cents. |
| meta.duration_ms | number | How long the generation took. |
Chat completions carries the same number as corent.cost_cents. Video bills on completion, so its charge appears on GET /v1/jobs/{id}.
GET/v1/requests
Every past request with its charge, the tier that served it, how many candidates were considered and whether failover fired. Failed requests show billed: false.
{ "requests": [{ "request_id": "...", "status": "completed", "model": "image-air", "cost_cents": 2, "billed": true, "candidates_considered": 3, "failover_occurred": false, "reasoning": "Optimized for cost and speed.", "output_url": "https://..." }], "next_before": null}GET /v1/routing/explain?request_id=... answers the same question for one request.
11
Per-customer usage
Tag a request with your own customer id and the charge follows that id, so you can bill your users from our receipts.
The tag
customer_id is an optional string, 1 to 128 characters, accepted on every generating call: images, edits, upscales, background removal, video, speech and chat completions. Corent never reads it. It is stored with the job and printed back on the receipt exactly as sent.
{ "prompt": "a fox reading a book", "tier": "air", // your end customer, your own id for them "customer_id": "acct_8f2c"}The filter
GET/v1/account/usage?customer_id=
The same usage report, narrowed to one customer: their recent jobs and their total spend, in cents. Without the filter the report covers the whole account.
curl "https://api.corent.tech/v1/account/usage?customer_id=acct_8f2c" \ -H "Authorization: Bearer $CORENT_API_KEY"The receipt
Every receipt from GET /v1/requests carries customer_id when the request did, so a charge can always be traced to the customer it was made for.
{ "request_id": "...", "status": "completed", "model": "image-air", "cost_cents": 2, "billed": true, "customer_id": "acct_8f2c"}12
Errors and retries
Nothing in this table is billed. Charging happens only after a successful result.
| Status | Meaning | Retry | What to do |
|---|---|---|---|
| 400 / 422 | Invalid request | No | Fix the highlighted field and resend. |
| 401 | Invalid API key | No | Check the header: Authorization: Bearer co_live_... |
| 402 | Insufficient balance | No | Add funds in the dashboard. |
| 404 | Resource not found | No | Check the job or batch id. |
| 429 | Rate limited | Yes | Wait the seconds given in the Retry-After header. |
| 500 | Internal error | Once | Retry once. If it persists, contact support. |
| 502 / 503 | Temporary upstream failure | Yes | Safe to retry: routing already failed over internally. |
A provider failure after internal failover is still a failure you are not charged for. Your balance is untouched.
Send an Idempotency-Key on image or video generation and a repeat returns the original job. A timed-out request can be retried blindly and can never double-bill.
13
Limits and balance
Every key carries its own ceiling, and every balance is prepaid.
Limits
Requests per minute, concurrent jobs and an optional daily cost cap, per key. A new key gets 60 requests a minute and 50 concurrent jobs, with no daily cap: your prepaid balance is the ceiling, and a per-key daily cap is yours to switch on. Across all keys, an account is limited to 3000 requests a minute.
PUT/v1/account/api-keys/{id}/limits
Deliberately requires a dashboard login, not an API key: a leaked key must never raise its own cap.
{ "requests_per_minute": 60, "concurrent_jobs": 50, "daily_cost_cap_cents": 5000 }requests_per_minute 1 to 6000, concurrent_jobs 1 to 5000, daily_cost_cap_cents an integer or null for no cap.
On 429, Retry-After gives the wait in seconds. Batches take up to 50 items and run five at a time, each billed at the normal rate.
Balance
GET/v1/account/balance
{ "balance_cents": 998, "held_cents": 40, "available_cents": 958 }held_cents is reserved against jobs still running. A 402 checks available_cents, so a request can be refused while balance_cents still looks healthy.
GET /v1/account/usage returns recent jobs and total spend, and takes ?customer_id= to narrow it to one of your customers. POST /v1/account/deposit opens a card top-up from $5, and /deposit/crypto from $10.