Retrieve a task
Get one task's status and result: response, errors, the task object and failure reasons.
https://api.nanoapi.ai/v1/tasks/{task_id}Get one task's status and result. A task is generated in the background; with a webhook you don't need to poll, and the webhook body is exactly this endpoint's response. Every response uses the { code, message, data } envelope, see Response format.
curl https://api.nanoapi.ai/v1/tasks/tk_01jb8z5q4w3e2r1t0y9u8i7o6p \
-H "Authorization: Bearer $NANOAPI_API_KEY"Response
On success you get 200 with the full task object as data:
{
"code": 200,
"message": "success",
"data": {
"object": "task",
"id": "tk_01jb8z5q4w3e2r1t0y9u8i7o6p",
"model": "genjutsu-1.0",
"status": "running",
"input": {
"mode": "motion_control",
"image_urls": ["https://example.com/person.png"],
"video_url": "https://example.com/dance.mp4",
"prompt": "",
"resolution": "720p"
},
"num_outputs": 1,
"outputs": [],
"billing": {
"currency": "USD",
"frozen": "4.095",
"charged": "0.00",
"refunded": "0.00",
"billed_seconds": null
},
"error": null,
"webhook_url": "https://example.com/hooks/nanoapi",
"created_at": "2026-10-22T08:30:00Z",
"started_at": "2026-10-22T08:31:05Z",
"finished_at": null,
"elapsed_seconds": null
}
}This task is still generating: outputs is empty, finished_at and elapsed_seconds are null and nothing has been charged yet. A succeeded task is shown under The task object, a failed one under Failed tasks.
data.status | Meaning | Next |
|---|---|---|
queued / running | Queued / generating. The response has Retry-After: 15, the suggested polling interval. | Check again in 15 seconds or more. |
succeeded | Done: data.outputs has the results and data.billing.charged is what was charged. | Download the result, see Outputs. |
failed | The task failed; this request didn't. The frozen amount has been returned in full. | Handle it by data.error.type, see Failed tasks. |
Errors
A failed request returns 4xx / 5xx. Only this one read failed; it says nothing about the task, which keeps queuing or generating as before. For example, a task that doesn't exist:
{
"code": 404,
"message": "Task not found.",
"error": {
"type": "not_found",
"param": null,
"details": null
},
"request_id": "rq_01jb8z9x2c4v6b8n0m1q3w5e7r"
}error.type | Status | When | What to do |
|---|---|---|---|
invalid_request | 400 | The task ID is longer than 64 characters. | Check the task ID. |
invalid_api_key | 401 | The key is missing or malformed, doesn't exist, was deleted or has been reset. | Use a valid key. |
account_suspended | 403 | Your account has been suspended. | Contact support@nanoapi.ai. |
not_found | 404 | The task doesn't exist or isn't yours. | Check the task ID. A task can only be read by the account that created it (with any of its keys). |
rate_limited | 429 | Too many reads: all read endpoints together (retrieving and listing tasks, the balance, products) allow 60 requests per 10 seconds per account, shared by all your keys (the website playground counts too). | Retry after Retry-After seconds. Poll one task at most every 15 seconds, check several with the ids of List tasks in one request, or use webhooks instead. |
internal_error | 500 | Something went wrong on our side. | Reads have no side effects, so you can simply retry. |
The task object
It's the data of a retrieved task, each item in a task list's data.items, and the data of a webhook (creating a task returns a short form). This is what data contains:
{
"object": "task",
"id": "tk_01jb8z5q4w3e2r1t0y9u8i7o6p",
"model": "genjutsu-1.0",
"status": "succeeded",
"input": {
"mode": "motion_control",
"image_urls": ["https://example.com/person.png"],
"video_url": "https://example.com/dance.mp4",
"prompt": "",
"resolution": "720p"
},
"num_outputs": 1,
"outputs": [
{
"index": 0,
"type": "video",
"url": "https://file.example.com/outputs/tk_01jb8z5q4w3e2r1t0y9u8i7o6p/0.mp4",
"preview_url": "https://file.example.com/outputs/tk_01jb8z5q4w3e2r1t0y9u8i7o6p/0_compressed.mp4"
}
],
"billing": {
"currency": "USD",
"frozen": "4.095",
"charged": "4.095",
"refunded": "0.00",
"billed_seconds": 15
},
"error": null,
"webhook_url": "https://example.com/hooks/nanoapi",
"created_at": "2026-10-22T08:30:00Z",
"started_at": "2026-10-22T08:31:05Z",
"finished_at": "2026-10-22T08:47:12Z",
"elapsed_seconds": 1032
}| Field | Description |
|---|---|
id | Task ID, such as tk_01jb8z5q4w3e2r1t0y9u8i7o6p. See IDs. |
model | The model version, such as genjutsu-1.0. |
status | queued, running, succeeded or failed. See Status. |
input | The model parameters from the create request. |
num_outputs | The number of results requested. |
outputs | The results, empty until the task succeeds. See Outputs. |
billing | Amounts in US dollars. See Billing fields. |
error | Why the task failed ({ type, message }), otherwise null. See Failed tasks. |
webhook_url | From the create request, or null. |
created_at / started_at / finished_at | ISO 8601 times; started_at and finished_at are null until they happen. |
elapsed_seconds | Duration in seconds, from creation to the end, queueing included; null until the task finishes. |
Status
queued ──starts──→ running ──┬─→ succeeded (charged)
│ └─→ failed (frozen amount returned)
└──not started within 30 minutes──→ failed (queue_timeout)succeeded and failed are final and never change. Tasks can't be cancelled.
Outputs
| Field | Description |
|---|---|
index | Position, from 0. |
type | video for Genjutsu. |
url | The result file, as a direct download link. |
preview_url | A silent, compressed copy 600 pixels wide, for previews; null if it couldn't be made. |
Anyone with the link can open the file, so don't share links you want to keep private. Result files currently aren't deleted, but copy anything you need to keep to your own storage.
Billing fields
| Field | Description |
|---|---|
frozen | Frozen from your balance when the task was created. |
charged | Charged when the task succeeded; "0.00" until it finishes and for failed tasks. |
refunded | Returned to your available balance; "0.00" until it finishes. |
billed_seconds | Seconds billed (a number, up to 3 decimals) once the task succeeds, otherwise null. |
Once the task has finished, charged + refunded = frozen. See Billing.
Failed tasks
A failed task was created and its cost frozen, but no result was generated. A failed task:
- has
status: "failed". That's final: it never changes, and you can't retry the task itself. - has an
errorwith the reason:typefor your program,messagean English explanation for people. A task failure isn't a failed request, so there's no HTTP status here. - has an empty
outputsarray. - got its money back in full:
billing.chargedis"0.00"andbilling.refundedequalsbilling.frozen. The frozen amount is already back in your available balance; you don't need to do anything.
A failed task looks like this in the data of a retrieved task, in a task list's data.items and in the data of a webhook. Here are three tasks that failed at different stages; they differ in error, started_at and when they finished:
Generation started, then a reference image couldn't be downloaded (invalid_media). started_at is set.
{
"object": "task",
"id": "tk_01jb8z5q4w3e2r1t0y9u8i7o6p",
"model": "genjutsu-1.0",
"status": "failed",
"input": {
"mode": "motion_control",
"image_urls": ["https://example.com/person.png"],
"video_url": "https://example.com/dance.mp4",
"prompt": "",
"resolution": "720p"
},
"num_outputs": 1,
"outputs": [],
"billing": {
"currency": "USD",
"frozen": "4.095",
"charged": "0.00",
"refunded": "4.095",
"billed_seconds": null
},
"error": {
"type": "invalid_media",
"message": "A reference image could not be downloaded. Make sure every link in image_urls is publicly downloadable."
},
"webhook_url": "https://example.com/hooks/nanoapi",
"created_at": "2026-10-22T08:30:00Z",
"started_at": "2026-10-22T08:31:05Z",
"finished_at": "2026-10-22T08:31:40Z",
"elapsed_seconds": 100
}Compared with a succeeded task, these fields differ:
| Field | Failed task | Description |
|---|---|---|
status | "failed" | Final, never changes. |
error | { type, message } | type is the reason; branch on it. message is an English explanation. Every reason and what to do: Failure reasons. |
outputs | [] | No result. |
billing.charged | "0.00" | Nothing was charged. |
billing.refunded | Equal to billing.frozen | The frozen amount went back to your available balance. |
billing.billed_seconds | null | Only set on succeeded tasks. |
started_at | A time or null | null means it failed before generation started: rejected on submission, or a queue timeout. |
finished_at / elapsed_seconds | Set | When it failed / seconds from creation to failure. |
A failed task can't be retried. To generate again, create a task with the same parameters: you get a new task ID and the cost is frozen again.
Failure reasons
error.type | Cause | What to do | Resubmitting unchanged |
|---|---|---|---|
content_policy | The content breaks the usage policy: the reference images, source video or prompt contain inappropriate (NSFW) content, or a protected likeness or copyrighted content (such as a photo of a public figure). | Use different media or a different prompt. | Fails again |
invalid_media | The media can't be used: a reference image or the source video can't be downloaded, an image is corrupt or in an unsupported format, or the video length isn't allowed. message says which. | Replace the media message points to. | Fails again |
invalid_request | The generation service didn't accept these parameters. message may name the parameter. | Adjust the parameters as message says. | Fails again |
generation_failed | Generation failed or timed out. | Create it again later; if it keeps failing, change the media or prompt. | May succeed |
queue_timeout | Not started within 30 minutes of being queued, usually at peak times. | Create it again later. | May succeed |
internal_error | An internal error on our side. | Create it again later. | May succeed |
New failure reasons may be added: treat any type you don't recognize as an ordinary failure instead of raising an error.
When a task can fail
| Stage | Possible error.type | started_at | Where you see it |
|---|---|---|---|
| Rejected on submission: the create endpoint hands the task to the generation service, which turns it down | content_policy, invalid_media, invalid_request, internal_error | null | The create endpoint returns 200 with a task that is already failed; the same task when you read it and in the webhook |
| Queue timeout: not started within 30 minutes of being queued | queue_timeout | null | Reads, webhook |
| During generation | content_policy, invalid_media, generation_failed, internal_error | Set | Reads, webhook |
Messages
message is an English explanation for developers: log it, use it to debug, or translate it for your own users. These are all the messages you can currently get:
error.type | message | Meaning |
|---|---|---|
content_policy | The input was rejected by the content policy. | The content didn't pass moderation. |
invalid_media | A reference image could not be downloaded. Make sure every link in image_urls is publicly downloadable. | A reference image can't be downloaded. |
invalid_media | A reference image could not be read. It may be corrupted or in an unsupported format. | A reference image is corrupt or in an unsupported format. |
invalid_media | The source video could not be downloaded. Make sure video_url is publicly downloadable. | The source video can't be downloaded. |
invalid_media | The source video must be 4-30 seconds long. | The source video length isn't allowed. |
invalid_media | A required input file is missing or could not be used. | A required file is missing. |
invalid_media | An input file could not be used. Check image_urls and video_url. | Another media problem. |
invalid_request | The value of <parameter> was rejected. | One parameter's value was rejected, for example The value of resolution was rejected. |
invalid_request | The task parameters were rejected. | The parameters were rejected; which one is unknown. |
generation_failed | The generation failed. | Generation failed. |
generation_failed | The generation timed out. | Generation timed out. |
generation_failed | The task did not finish in time. | Generation never finished. |
queue_timeout | The task did not start within 30 minutes. | Queue timeout. |
internal_error | An internal error occurred. Please try again later. | Internal error. |
The wording may change and new messages may be added: branch on type only.