Images & video
POST /v1/images/generations, /v1/images/edits and /v1/videos.
Generate and edit pictures — and, where a video model is listed, short videos — with the same key, balance and plan as your text requests. The endpoints follow the OpenAI format, so the OpenAI SDKs work unchanged.
Models that generate media are marked image output or video output on the Models page, for example gpt-image-2. A text model on these endpoints is refused with a clear error, before anything is charged.
Which model runs where
AivoKey serves media from more than one backend. You always send the same request to the same AivoKey URL: the gateway routes it to whichever backend serves the model you named and translates the answer back to the OpenAI shape. Nothing in your code changes when a model moves between backends, and GET /v1/models is the authoritative list of what is available right now.
Generate an image
curl https://api.aivokey.dev/v1/images/generations \
-H "Authorization: Bearer sk-aivo_..." \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"prompt": "A single red apple on a plain white studio background.",
"size": "1024x1024",
"quality": "low"
}'
The picture comes back inline as base64 in data[0].b64_json — that is the default, and it is what you get when you leave response_format out. Save it with, for example:
… | jq -r '.data[0].b64_json' | base64 --decode > apple.png
An explicit "response_format": "url" is honoured: the answer then carries a url instead of b64_json, pointing at the backend that rendered the picture. That link is short-lived, so download the file straight away rather than storing the link. Leaving the field out is the safest option — inline base64 cannot expire.
size and quality are passed to the model, and what they cost depends on how the model is priced. On a per-image model the price is per picture, so a larger size or a higher quality is still one unit. On a per-megapixel model you pay for the pixels it actually renders, so a larger size does cost more and a smaller one costs less. The Models page shows which unit each model uses.
Edit an image
POST /v1/images/edits takes the same fields plus the picture to change, as an image URL or data URL. Send it as JSON:
curl https://api.aivokey.dev/v1/images/edits \
-H "Authorization: Bearer sk-aivo_..." \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"prompt": "Make the apple green",
"image": "https://example.com/apple.png"
}'
- One picture per request.
nmust be 1; send several requests for more. - No mask or inpainting. A request carrying
maskormask_urlis refused with400(mask_not_supported) and nothing is charged — the edit is never run half-masked. - A
multipart/form-dataupload fromclient.images.edit(...)in the [OI] SDKs also works: AivoKey converts it to the JSON form above.
Each uploaded file can be up to 20 MB (40 MB together).
With the [OI] SDK
import base64
from openai import [OI]
client = [OI](base_url="https://api.aivokey.dev/v1", api_key="sk-aivo_...")
img = client.images.generate(model="gpt-image-2", prompt="A watercolor fox", size="1024x1024")
open("fox.png", "wb").write(base64.b64decode(img.data[0].b64_json))
Limits
- One picture per request:
nmust be 1. Send several requests for more. - No streaming:
"stream": trueis refused. - Image models also answer on
/v1/chat/completions; there the picture arrives as animage_urlcontent part.
Video
Video generation takes from about a minute to several minutes, so it runs as a job. Model ids with the video output badge on the Models page work here:
# 1. Start the job — the answer is a job, not a video.
curl https://api.aivokey.dev/v1/videos \
-H "Authorization: Bearer sk-aivo_..." \
-H "Content-Type: application/json" \
-d '{ "model": "VIDEO_MODEL_ID", "prompt": "A red cube slowly rotating", "seconds": "8" }'
# → { "id": "video_...", "object": "video", "status": "in_progress" }
# 2. Poll until status is "completed" (or "failed").
curl https://api.aivokey.dev/v1/videos/video_... -H "Authorization: Bearer sk-aivo_..."
# 3. Download the file.
curl https://api.aivokey.dev/v1/videos/video_.../content -H "Authorization: Bearer sk-aivo_..." -o out.mp4
| Endpoint | What it does |
|---|---|
POST /v1/videos | Starts a job and returns its id. |
GET /v1/videos | Lists your own jobs. Only the key's owner can see a job. |
GET /v1/videos/{id} | The job's status: in_progress, completed or failed. |
GET /v1/videos/{id}/content | The finished file, streamed through AivoKey. |
DELETE /v1/videos/{id} | Cancels a running job. Best effort — a job that has already finished may still complete and be billed. |
Download through /content rather than keeping a link. A finished video is kept for about 3 hours after the job completes; after that /content answers 404 and the file is gone. The result URL the backend itself hands out is even shorter-lived — around 15 minutes — so never store it as the place to get the video later. Fetch the file soon after the job completes and save it wherever you need it.
Cancelling with DELETE is best effort: it stops a job that is still running, but a job that finished before the cancel arrived may still complete and be billed. Treat cancel as "stop paying for more rendering", not as a refund.
What it costs
Media models are priced per unit, not per token: per image, per megapixel or per job, depending on the model. The Models page shows the unit and the price for each one.
The price on that page is the listed price, and it is the most one unit can cost you: the platform's margin is already included, so nothing is added at billing time. Concretely:
- Images are charged when the picture is returned, at the per-image (or per-megapixel) price shown. A refused request (wrong model, bad size, no prompt) is not charged.
- Videos hold an amount on your balance while the job renders, then are charged once, at the per-job price, when the job completes. A job that fails, is canceled or expires releases the hold and costs nothing — except that a job which had already finished before the cancel arrived may still complete and be billed.
- Plan allowance, free tokens and balance apply as usual, in the same order as text requests.
Each unit is billed from what that request actually cost us to serve, with the platform margin inside that figure. When the market price for a unit is below the listed price, you are charged the lower amount; the listed price is never exceeded in normal operation. Your balance statement always shows the amount actually charged.
Some media models are billed by reported token usage instead; those follow the per-1M-token price shown on the same page.
Audio, music and 3D (planned)
These are not available yet — there is no endpoint for them today, and a request to one returns 404. When they ship they will follow the same rules: one AivoKey URL, your existing key and balance, and pricing shown on the Models page. Nothing you build against the endpoints above will need to change.