Generate
POST /v1/generate starts an image, video or audio generation. The request returns a task right away (202). You get the result by polling the task, by passing ?wait=N, or through a webhook.
Request
POST https://jantaris.com/v1/generate
Authorization: Bearer sk-jt-...
Content-Type: application/json| Field | Type | Required | Description |
|---|---|---|---|
model | string | yes | Media model id, for example seedance-2.5, gpt-image-2.5, mureka-v9 |
input | object | yes | Model parameters, validated against the model's input_schema |
webhook_url | string | no | URL that receives a POST when the task succeeds or fails |
| Query parameter | Type | Description |
|---|---|---|
wait | integer, 1-120 | Seconds the server waits for the task to finish before responding |
Response
Without wait, or when the task did not finish in time, you get 202 Accepted:
{
"id": "task_8f2k...",
"object": "task",
"status": "queued",
"model": "seedance-2.5",
"estimated_cost": { "amount": "2.9351", "currency": "USD" },
"poll_url": "https://jantaris.com/v1/tasks/task_8f2k..."
}The amount in this example is for the request from Video with a webhook (Seedance 2.5, 5 s, 720p) and comes from the price list of October 2, 2026. The API always returns the current amount in estimated_cost. To work it out yourself:
- video billed per second: the per-second price at the chosen resolution times
duration; per-second prices for each resolution are inpricing.variantsfromGET /v1/models/{id}; - example: the 720p per-second price times 5 s.
With ?wait=N, if the task finishes within N seconds you get 200 OK with the finished task, the same object as GET /v1/tasks/{id}. A finished task is either succeeded or failed, so always check status.
Cost and holds
- Before the task starts,
estimated_costis held from your balance. Ifavailablebalance is lower than the estimate, the request fails with402 insufficient_balanceand nothing starts. - On
succeeded, exactlycostis charged and the hold is settled. - On
failed, the hold is released in full. You pay 0.
Amounts are strings with a currency: USD for global accounts, PLN for Polish accounts. See Billing.
Input parameters
Each model has its own schema. Get it from GET /v1/models/{id} (field input_schema) before sending a request. Unknown fields, wrong types and out-of-range values return 422 invalid_request with a message naming the field.
Common fields by category (a given model may support a subset):
| Category | Fields |
|---|---|
| Image | prompt, negative_prompt, aspect_ratio (1:1, 16:9, 9:16, 4:3, 3:4), num_images (1-4, only on models that list it in GET /v1/models/{id}), image_urls (editing or references), seed |
| Video | prompt, duration (seconds), resolution (480p, 720p, 1080p depending on the model), aspect_ratio, image_url (first frame), generate_audio |
| Audio / music | prompt, duration, lyrics, instrumental |
Fields you omit take the default from the schema.
Image with wait
Images usually finish within a minute, so ?wait=60 gives you the result in one call.
curl "https://jantaris.com/v1/generate?wait=60" \
-H "Authorization: Bearer $JANTARIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-2-pro",
"input": {
"prompt": "Product photo of a ceramic mug on a concrete table, soft daylight",
"aspect_ratio": "4:3"
}
}'import os, requests
API = "https://jantaris.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['JANTARIS_API_KEY']}"}
r = requests.post(
f"{API}/generate",
params={"wait": 60},
headers=HEADERS,
json={
"model": "flux-2-pro",
"input": {
"prompt": "Product photo of a ceramic mug on a concrete table, soft daylight",
"aspect_ratio": "4:3",
},
},
timeout=130, # longer than wait
)
r.raise_for_status()
task = r.json()
if task["status"] == "succeeded":
for item in task["output"]:
print(item["url"], item.get("width"), item.get("height"))
elif task["status"] == "failed":
print("failed:", task["error"])
else:
print("still running, poll:", task["poll_url"])const res = await fetch("https://jantaris.com/v1/generate?wait=60", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JANTARIS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "flux-2-pro",
input: {
prompt: "Product photo of a ceramic mug on a concrete table, soft daylight",
aspect_ratio: "4:3",
},
}),
});
const task = await res.json();
if (task.status === "succeeded") task.output.forEach((o) => console.log(o.url));
else console.log(task.status, task.poll_url ?? task.error);Set your HTTP client timeout above the wait value, otherwise your client gives up before the server answers.
Image editing
Pass source images in image_urls if the model supports it (check input_schema):
curl "https://jantaris.com/v1/generate?wait=60" \
-H "Authorization: Bearer $JANTARIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "nano-banana-pro",
"input": {
"prompt": "Replace the background with a snowy mountain landscape",
"image_urls": ["https://example.com/photo.jpg"]
}
}'Video with a webhook
Video takes longer, so start the task and let Jantaris notify you.
curl https://jantaris.com/v1/generate \
-H "Authorization: Bearer $JANTARIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.5",
"input": {
"prompt": "Slow dolly shot through a neon-lit night market in the rain",
"duration": 5,
"resolution": "720p",
"aspect_ratio": "16:9"
},
"webhook_url": "https://your-server.example.com/jantar-hook"
}'r = requests.post(
f"{API}/generate",
headers=HEADERS,
json={
"model": "seedance-2.5",
"input": {
"prompt": "Slow dolly shot through a neon-lit night market in the rain",
"duration": 5,
"resolution": "720p",
"aspect_ratio": "16:9",
},
"webhook_url": "https://your-server.example.com/jantar-hook",
},
timeout=30,
)
task = r.json()
print(task["id"], task["estimated_cost"])const res = await fetch("https://jantaris.com/v1/generate", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.JANTARIS_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
model: "seedance-2.5",
input: {
prompt: "Slow dolly shot through a neon-lit night market in the rain",
duration: 5,
resolution: "720p",
aspect_ratio: "16:9",
},
webhook_url: "https://your-server.example.com/jantar-hook",
}),
});
const task = await res.json();
console.log(task.id, task.estimated_cost);Most video models are billed per second of output: Seedance 2.5 costs from $0.261 per second, Kling 3.0 from $0.169 per second. Veo 3.1 (Quality) is billed per clip: from $2.53 per video, see billing units. To animate a still image, add image_url (first frame) if the model's schema has it.
Music
curl https://jantaris.com/v1/generate \
-H "Authorization: Bearer $JANTARIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mureka-v9",
"input": {
"prompt": "Lo-fi hip hop, warm vinyl crackle, mellow piano",
"lyrics": "[Verse]\nCity lights are fading slow\n[Chorus]\nStay a little longer"
}
}'lyrics is required: mark verses and the chorus with [Verse] and [Chorus], and write line breaks as \n in JSON. prompt sets the style. Check the model's input_schema for limits. Price: $0.124 per song.
Errors
| Status | error.type | When |
|---|---|---|
| 401 | authentication_error | Bad or missing key |
| 402 | insufficient_balance | Available balance below estimated_cost |
| 403 | permission_error | Key spend limit reached |
| 404 | not_found | Model id does not exist |
| 422 | invalid_request | input does not match the schema |
| 429 | rate_limit | Over 60 requests per minute per key (default) |
| 429 | rate_limit_error | 25 failed generations on the account in 24 hours, wait time in the Retry-After header |
A task that fails after it started is not an HTTP error. It shows up as status: "failed" with an error object, see Tasks. Full list: Errors.









