Developers
Video API
One POST turns a sentence into a short film with native audio. It runs on our own GPU, not a reseller account: MiniMax H3, 20 steps, no distilled shortcut. Billed by the second of finished video — $0.08 a second, and nothing else.
Three steps
1
Get a key
Sign in, open your account page, create an API key. It is shown once — save it. The same key works for the Director API.
2
Top up
Credits are prepaid on the same page. 1 credit = $0.01. Nothing expires.
3
Call it
POST a prompt, get a job_id back immediately, poll until it is done. Rendering takes minutes — write it as a job, not as a request.
What it costs
Eight credits per second of finished video. No per-request fee, no resolution tiers, no charge for a failed render.
| Length | Credits | Roughly | Render time |
|---|---|---|---|
| 4 s | 32 | $0.32 | about 3 min |
| 8 s | 64 | $0.64 | about 8 min |
| 10 s | 80 | $0.80 | about 10 min |
| 15 s | 120 | $1.20 | about 17 min |
All three modes cost the same. A reference render is about 25 seconds of extra setup — a fixed cost, not a multiplier — which is 10% of a 4-second clip and 2% of a 15-second one, so charging a premium for it would be inventing a number. Credits are held when you submit and become the charge on success. A failed or cancelled job is refunded in full, same minute. Render times are measured on our current card and are estimates, not promises — the number that matters is deliver_by in the response.
Three ways to make a clip
Same model, same price, same endpoint — mode picks which one you get.
| mode | What it does | What you send |
|---|---|---|
text | Words in, film out. The default; omit mode and you get this. | prompt |
animate | A photo starts moving. Give a first frame; add a last frame and it moves between the two. | first_frame, optional last_frame |
cast | Put a specific person, object or look in the shot. Point at them in the prompt as <Picture 1>, <Picture 2>. | ref_images: 1 to 4 images |
GET /v1/models tells you which modes are open right now. We turn them on one at a time; a mode that is not open returns mode_unavailable rather than pretending.
How long it takes, honestly
- · One GPU, strictly serial. Your job renders after everything submitted before it. Order is submission order — there is no priority tier to buy.
- · Cost in time grows faster than length: a 15-second clip is not four 4-second clips, it is about ten of them. This is the model, not our queue.
- · Every response carries deliver_by — a timestamp that already includes the queue ahead of you. Poll every 15–30 seconds; polling faster changes nothing.
- · If the queue is deeper than six hours we refuse the submission with capacity_deadline_infeasible rather than accept it and miss the date.
Submit a job
curl -X POST https://spaceskills.shop/v1/videos \
-H "Authorization: Bearer $SPACESKILLS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-clip-0001" \
-d '{
"prompt": "A red paper boat drifting on a calm pond at sunrise, soft golden light, gentle ripples on the water, slow push-in, cinematic",
"duration_sec": 5
}'HTTP/1.1 202 Accepted
{
"job_id": "vid_8Qk2ZrX1c0Nn",
"status": "queued",
"model": "h3/768p",
"duration_sec": 5,
"credits_held": 40,
"balance": 460,
"queue_position": 2,
"est_render_sec": 168,
"deliver_by": "2026-09-09T14:31:07.000Z",
"poll": "/v1/videos/vid_8Qk2ZrX1c0Nn"
}Returns 202 with a job_id and the credits held. Send an Idempotency-Key and a retried POST returns the same job instead of charging twice.
curl https://spaceskills.shop/v1/videos/vid_8Qk2ZrX1c0Nn \ -H "Authorization: Bearer $SPACESKILLS_API_KEY"
{
"job_id": "vid_8Qk2ZrX1c0Nn",
"status": "succeeded",
"duration_sec": 5,
"seed": 774512900183,
"credits_held": 0,
"credits_charged": 40,
"output": {
"url": "https://media.spaceskills.shop/relay/vid_8Qk2ZrX1c0Nn.mp4",
"stored": true
},
"error": null,
"created_at": "2026-09-09T14:21:44.910Z",
"updated_at": "2026-09-09T14:25:12.338Z"
}status is one of queued, running, succeeded, failed, cancelled. On succeeded, output.url is the finished MP4 with audio muxed in.
Sending images
Same shape as the Director API: each image is inline base64 or an https URL we fetch.
- · A file is { "name": "hero.jpg", "data_base64": "…" } or { "name": "hero.jpg", "url": "https://…" }. URLs must be https and must not resolve to a private address.
- · 8 MB per image, 16 MB of images per request, 24 MB request body. JPEG, PNG and WebP — we check the actual bytes, not the name or the declared type.
- · Your images are stored on our own server, never in the public bucket the finished clips live in, and the bytes are deleted the moment the job finishes or fails. The job keeps a manifest — slot, size and sha256 — so you can always check what we received.
- · In cast mode the prompt has to refer to the pictures. "A woman walks through rain" ignores your references; "The woman from <Picture 1> walks through rain" does not.
# cast: put a specific face, object or look in the shot
curl -X POST https://spaceskills.shop/v1/videos \
-H "Authorization: Bearer $SPACESKILLS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "cast",
"prompt": "The woman from <Picture 1> walks slowly through a rainy neon street at night, reflections on wet asphalt, rain and distant traffic, slow tracking shot",
"duration_sec": 6,
"ref_images": [
{ "name": "her.jpg", "url": "https://example.com/her.jpg" }
]
}'
# animate: start from a photo
curl -X POST https://spaceskills.shop/v1/videos \
-H "Authorization: Bearer $SPACESKILLS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "animate",
"prompt": "The lake ripples as the sun sinks lower, drifting clouds, distant birdsong, slow push-in",
"duration_sec": 5,
"first_frame": { "name": "lake.jpg", "url": "https://example.com/lake.jpg" }
}'Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/models | Catalogue: length range, price per second, current queue depth. Free, no key needed. |
POST /v1/videos | Submit a prompt. Holds credits, returns job_id. |
GET /v1/videos/{id} | Status, and the file once it is ready. |
GET /v1/videos | Your last 30 jobs and your balance. Useful for reconciliation. |
POST /v1/videos/{id}/cancel | Cancel while still queued. Full refund. Once it is running the GPU time is already spent, so it cannot be cancelled. |
Request fields
| Field | Type | Notes |
|---|---|---|
prompt | string | What the shot should show. 8 to 20000 characters. Subject, motion, setting, light, camera — in that order works well. |
duration_sec | integer | 4 to 15. This is the pricing basis. Defaults to 5. |
mode | string | text, animate or cast. Defaults to text. |
first_frame | file | animate only, required. The frame the clip starts on. |
last_frame | file | animate only, optional. The frame it ends on. |
ref_images | file[] | cast only, 1 to 4. Refer to them in the prompt as <Picture 1> onwards, in the order you sent them. |
model | string | Defaults to h3/768p, currently the only active model. |
seed | integer | Optional. Same seed, prompt and images reproduce the same clip. Omit it and one is drawn and echoed back on the job. |
Headers: Authorization: Bearer sk_live_… and, recommended, Idempotency-Key: <any string you generate>. A key is remembered per account, so a network retry can never buy the same clip twice.
What comes back
The same job object from submit, poll and list — only the fields that are known yet are filled in.
- · output.url is an MP4 at 1152×640 with native stereo audio already muxed in. There is no separate audio file to fetch.
- · output.stored: true means it sits on our own storage and the link is stable. On the rare false, the render machine is serving it directly and the link dies when that machine is recycled — download it promptly.
- · credits_held becomes credits_charged on success, and drops to zero on failure with a matching refund in your ledger.
- · The finished clip snaps to the model frame grid, so it can land up to 0.35 s short of the seconds you asked for. You are billed on the seconds you asked for.
Writing a prompt that works
The model reads plain prose, English or Chinese. It rewards specificity and punishes lists of adjectives.
- · Name the subject, then what it does, then where, then the light, then the camera. One clause each.
- · One camera move per clip. Two moves in four seconds looks like a mistake, not like style.
- · Say what should be heard. Native audio is generated from the prompt too — "wind and distant traffic" gets you wind and distant traffic.
- · Changing everything at once tells you nothing. Fix the seed and rewrite one clause at a time.
When things go wrong
| Code | Means |
|---|---|
unauthorized | Missing or wrong key. Use Authorization: Bearer sk_live_… |
insufficient_credits | Balance below the price. The response tells you both numbers. |
bad_params | No prompt, prompt too long, or duration_sec outside 4–15. |
unknown_model | No such active model. Check GET /v1/models. |
mode_unavailable | That mode is not open yet. The response lists the ones that are. |
bad_material | An image was too large, not a real JPEG/PNG/WebP, unreachable, or not https. The message says which slot. |
payload_too_large | The request body is over 24 MB. Send images as https URLs instead of inline base64. |
model_unavailable | No render backend right now — usually a machine swap. Retry in a few minutes; nothing was charged. |
capacity_deadline_infeasible | The queue is deeper than six hours. Retry later; nothing was charged. |
idempotency_conflict | That Idempotency-Key is in use by a different request body. |
not_cancellable | The job is already running or finished. |
rate_limited | Too many requests. Retry-After says how long to wait. |
Limits
- · 30 submissions per 10 minutes per IP; 120 catalogue reads in the same window. Polling a job is not rate limited.
- · One job renders at a time across the whole service — that is the honest constraint of a single card, and it is why deliver_by exists.
- · Five API keys per account. Revoking is immediate.
- · Finished clips stay on our storage; keep your own copy of anything you intend to ship.
Looking for the Director API — brief and material in, a shot-by-shot plan out? That is documented here
Questions, or something behaving oddly? Tell us