Reference
Video, image and speech
Three endpoints beyond text. Image and speech follow the OpenAI shapes your SDK already knows. Video is asynchronous, because a render takes longer than any sensible HTTP timeout.
Video generation
Submit a job, poll it, then download the file. Three calls, and the job id from the first is the handle for the other two.
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/video/generations | Submit a render job |
| GET | /v1/video/generations/{task_id} | Poll status and progress |
| GET | /v1/video/generations/{task_id}/content | Download the finished file |
Submit
curl https://router.xark.io/api/v1/video/generations \
-H "Authorization: Bearer $AI_TOKEN_ROUTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "alibaba/wan-2.2-t2v-a14b",
"prompt": "An empty beach at sunset, camera pushing slowly toward the water",
"duration": 5,
"resolution": "720p",
"ratio": "16:9"
}'
| Field | Type | Required | Notes |
|---|---|---|---|
| model | string | yes | Full namespaced id from GET /v1/models |
| prompt | string | yes | What to render |
| duration | integer | no | Fixed at 5s during this rollout phase, so the price you were quoted is exact rather than an average. Omit it or set it to 5 -- any other value is refused before billing, not silently charged at the wrong rate. |
| resolution | string | no | e.g. 720p, 1080p, where the model supports it |
| ratio | string | no | e.g. 16:9, 9:16, 1:1 |
Variable-length billing -- pay for exactly the seconds you request, at any length -- is coming. Fixing the duration for now means every accepted request is billed at precisely the rate on the model’s page, with no averaging.
Poll
Poll every 5 to 10 seconds until status is succeeded or failed. Polling faster does not make a render finish sooner and will hit the rate limit.
curl https://router.xark.io/api/v1/video/generations/$TASK_ID \
-H "Authorization: Bearer $AI_TOKEN_ROUTER_KEY"
content.url is null until the job succeeds — deliberately null rather than a placeholder, so a polling client cannot hand a 404 to a user.
Download
curl -L https://router.xark.io/api/v1/video/generations/$TASK_ID/content \
-H "Authorization: Bearer $AI_TOKEN_ROUTER_KEY" \
-o result.mp4
Available video models: alibaba/wan-2.2-t2v-a14b, alibaba/wan-2.2-i2v-a14b, lightricks/ltx-2.5, tencent/hunyuanvideo-1.5, zhipuai/cogvideox-5b, genmo/mochi-1.
Image generation
OpenAI Images compatible. Generation and editing share the same authentication and error envelope as every other endpoint here.
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/images/generations | Generate from a text prompt |
| POST | /v1/images/edits | Edit an image, with optional references |
curl https://router.xark.io/api/v1/images/generations \
-H "Authorization: Bearer $AI_TOKEN_ROUTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen/qwen-image",
"prompt": "A blue ceramic teapot on a white table, soft studio lighting",
"size": "1328x1328",
"n": 1
}'
| Field | Type | Required | Notes |
|---|---|---|---|
| model | string | yes | Full namespaced id |
| prompt | string | yes | Image description |
| size | string | no | Defaults to 1328x1328 where supported |
| n | integer | no | Defaults to 1. Billed per image actually generated |
| seed | integer | no | Fix it to make a result reproducible |
| negative_prompt | string | no | What to keep out |
The image arrives as base64 in data[].b64_json. Billing is per image, so n multiplies the cost — start at 1.
Available image models: black-forest-labs/flux-2-schnell, qwen/qwen-image, stabilityai/sd-3.5-large, hidream/hidream-i1.
Speech
Synchronous: the response body is the audio file itself, not JSON. On failure it is a JSON error object instead, so branch on the status code before you write bytes to disk.
curl https://router.xark.io/api/v1/audio/speech \
-H "Authorization: Bearer $AI_TOKEN_ROUTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "$SPEECH_MODEL_ID",
"input": "The quick brown fox jumps over the lazy dog."
}' \
--output speech.wav
| Field | Type | Required | Notes |
|---|---|---|---|
| model | string | yes | Full id from GET /v1/models |
| input | string | yes | Text to synthesise |
| response_format | string | no | Defaults to wav |
| ref_audio | string | no | Base64 reference audio, for voice cloning |
| ref_text | string | no | Transcript of the reference audio |
Speech models are not in the published catalogue yet. Call GET /v1/models with your key for the ids your account can actually use — that list is authoritative, and it differs by account.
Errors
Same envelope as the text endpoints, so one handler covers all of them.
| Status | Means | Do |
|---|---|---|
| 400 | Body or parameter is invalid | Fix the request. Do not retry unchanged |
| 401 | Key missing or invalid | Check the Authorization header |
| 403 | Key may not use this model | Check the key's model restrictions |
| 404 | Unknown path or model id | Use the full namespaced id |
| 429 | Out of credit, or rate limited | Read error.code: insufficient_credits means top up, rate_limit_exceeded means back off |
| 5xx | Upstream failure or timeout | Retry with exponential backoff and a cap |
429 carries two very different meanings, which is why the code field matters: backing off does not help an empty balance, and topping up does not help a rate limit.