Create task
POST /v1/tasks: submit an image or video generation task, with request fields, validation rules, pricing and idempotency.
POST https://genkiln.com/v1/tasksCreates a generation task and returns it immediately with 202 Accepted. The task runs in the background; read its result with Get task or receive it on a webhook.
Request headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer sk_..., see Authentication. |
Content-Type | Recommended | application/json. The body is always parsed as JSON. |
Idempotency-Key | No | Any string up to 255 characters. Makes retries safe, see Idempotency. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Model ID, e.g. veo-3.1-fast. See Models. |
input | object | Yes | Model parameters. Each model has its own schema: prompt, image_urls, duration, resolution, aspect_ratio, … |
callback_url | string | No | Public https URL we POST the finished task to. See Webhooks. |
metadata | object | No | Your own data, returned unchanged on the task and in webhooks. At most 16 keys and 2 KB as JSON. |
The body must be a JSON object of at most 64 KB. Unknown top-level fields are rejected with 400 invalid_request and the field name in error.param.
Example: image-to-video with a webhook
curl https://genkiln.com/v1/tasks \
-H "Authorization: Bearer $GENKILN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-abc123-video" \
-d '{
"model": "veo-3.1-fast",
"input": {
"prompt": "The camera slowly pushes in as snow starts to fall",
"image_urls": ["https://example.com/first-frame.png"],
"duration": 8,
"resolution": "720p",
"aspect_ratio": "16:9"
},
"callback_url": "https://your-app.com/webhooks/genkiln",
"metadata": { "order_id": "abc123" }
}'Input validation
inputis checked against the model's schema. Omitted optional fields take their default; unknown fields are rejected. The parameter table for each model is on Models, andGET /v1/models/{id}returns it as JSON Schema.- A validation error names the field, for example
"param": "input.duration". - Every URL in
image_urls, andcallback_url, must:- use
httpson the default port (443), with no username or password in the URL; - be at most 2,048 characters;
- point to a public host. Hostnames that resolve to private, loopback, link-local or other non-public addresses are rejected.
- use
- Image URLs must be directly downloadable by us. A URL that cannot be fetched or is not a supported image fails the task with
invalid_input(refunded). - Prompts are screened before you are charged. Prompts that violate the Acceptable Use Policy are rejected with
403 content_rejected.
Pricing
The cost is computed on our side from the validated input (defaults included) and the model's public price list. It is returned as cost_credits and deducted when the task is created. If your balance is too low, you get 402 insufficient_credits and no task is created.
- Per-video models (Veo) cost the same for any duration; the price depends on resolution.
- Per-second models (Seedance, Kling) cost
duration × price per secondfor the chosen resolution and audio option. - Image models cost a fixed price per image for the chosen resolution.
Failed, timed-out and canceled tasks are refunded in full, automatically.
Response
202 Accepted with a task object. A new task usually starts as queued:
{
"id": "task_01JC8ZQ0E6H2P4N7K9T3W5Y8VA",
"object": "task",
"model": "veo-3.1-fast",
"status": "queued",
"cost_credits": 390,
"refunded": false,
"output": null,
"error": null,
"created_at": "2026-11-03T08:00:00.000Z",
"started_at": null,
"completed_at": null,
"expires_at": null,
"metadata": { "order_id": "abc123" }
}When the task cannot be started
Rarely, a task is created and charged but cannot be started. We then fail it, refund it at once, and answer with an error instead of 202. The failed task is included as error.task so you can record it:
| HTTP | `error.code` | Meaning |
|---|---|---|
| 403 | content_rejected | The prompt or input media was rejected by our content policy. |
| 400 | invalid_request | The input could not be processed (check image URLs and parameters). |
| 503 | service_busy | Temporarily busy. Retry after the Retry-After header (seconds). |
{
"error": {
"code": "service_busy",
"message": "The service is temporarily busy. Your credits have been refunded; please retry shortly.",
"task": {
"id": "task_01JC8ZQ0E6H2P4N7K9T3W5Y8VA",
"status": "failed",
"refunded": true,
"...": "..."
},
"request_id": "req_01JC8ZQ0E8M3B1V6C2D4F7G9HJ"
}
}If you set a callback_url, a task.failed webhook is sent for it too.
Idempotency
Network errors happen. To retry a create safely, send an Idempotency-Key header with a value that is unique per logical task (an order ID, a UUID).
- The same key used again within 24 hours returns the original task instead of creating and charging a new one. The response is
202with the task's current state and the headerIdempotent-Replayed: true. - Reusing a key with a different request body returns
409 idempotency_conflict. The body comparison coversmodel,input,callback_urlandmetadataexactly as you sent them, so send an identical body when retrying. - Keys are scoped to your account (shared by all your API keys) and can be reused after 24 hours.
- Concurrent requests with the same key are safe: only one task is created.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Invalid JSON, unknown field, failed validation, unsafe URL, metadata too large. |
| 401 | invalid_api_key | Missing or invalid key. |
| 402 | insufficient_credits | Balance lower than the task cost. |
| 403 | content_rejected | Prompt rejected by the content policy. |
| 404 | not_found | Unknown model. |
| 409 | idempotency_conflict | Same Idempotency-Key, different body. |
| 422 | model_unavailable | The model is temporarily unavailable. |
| 429 | rate_limited | Too many requests, or too many tasks in progress. See Rate limits. |
The full list is on Errors.