Jobs
Poll Nodaro jobs for status and results, filter your job list, rate and favorite results, read image sizes, check 100 jobs in one call and cancel jobs.
A job is one unit of generation in Nodaro: one image, one video render, one speech clip. A node run returns a job id, a workflow run creates one job for each AI node, and the job endpoints report each job's status, progress, result and credits. You poll a job until it reaches completed, failed or cancelled, then read its result from output_data.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v1/jobs/:id/status | The lean status for polling: status, progress, result and error. |
GET | /v1/jobs/:id | The full job, including what was sent (input_data) and its credits. |
GET | /v1/jobs | Your jobs, newest first, in pages. |
GET | /v1/jobs/status?ids=… | The status of up to 100 jobs, ids in the query. |
POST | /v1/jobs/batch-status | The status of up to 100 jobs, ids in the body. |
PUT | /v1/jobs/:id/rating | Rate one of your jobs good or bad, or clear the rating. |
PATCH | /v1/jobs/:id | Name one of your jobs, or clear its name. |
POST | /v1/jobs/from-upload | Record an image edited in your own app as a job. |
POST | /v1/gallery/favorite | Favorite a job, or remove the favorite. |
GET | /v1/gallery/favorites | Your favorited job ids. |
POST | /v1/jobs/:id/cancel | Cancel a job and release its reserved credits. |
DELETE | /v1/jobs/:id | Send a job to the Trash. ?forever=true deletes a job already in the Trash for good. |
POST | /v1/jobs/:id/restore | Bring a deleted job back from the Trash. |
GET | /v1/trash | Your Trash: removed session items and deleted jobs. |
GET | /v1/component/execute/:jobId/wait-limit | How long the server waits for a component run. |
POST | /v1/generate-video-pro/:jobId/stop | Stop a Generate Video Pro run and keep its finished segments. |
POST | /v1/generate-video-pro/continue | Continue a Generate Video Pro run as a new job. |
POST | /v1/credits/video-pro-estimate | Price a Generate Video Pro run without starting it. |
An OAuth token needs the jobs:read scope to read jobs. Personal API tokens need no scope. To have Nodaro tell your server when a job ends instead of polling, register a webhook endpoint.
Read a job
curl -s https://app.nodaro.ai/v1/jobs/0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10/status \
-H "Authorization: Bearer $NODARO_API_KEY"{
"data": {
"id": "0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10",
"status": "completed",
"progress": 100,
"output_data": { "imageUrl": "https://…/0f1a9c2e.png" },
"error_message": null,
"error_hint": null,
"credit_status": "committed"
}
}const { data } = await client.jobs.getStatus(jobId)
if (data.status === 'completed') console.log(data.output_data)
// The full record, with input_data and credits:
const { data: job } = await client.jobs.get(jobId)nodaro jobs get 0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10 --jsonGET /v1/jobs/:id/status is made for poll loops: it skips input_data and the cost fields, so it is lighter than GET /v1/jobs/:id. Both answer { "data": … }, and both answer 404 for a job that does not exist or is not yours. Job fields use snake_case, as the server sends them.
| Field | Meaning |
|---|---|
id | The job id. |
status | Where the job is. See Job statuses. |
progress | 0 to 100. |
output_data | The result. Media jobs carry imageUrl, videoUrl or audioUrl, often with thumbnailUrl. Other jobs carry their own fields, such as text or json. |
error_message | Why the job failed, in words. |
error_hint | A structured reason for five kinds of failure. See Why a job failed. |
error_detail | What the provider itself said when the job failed, or null. See The provider's own words. |
credit_status | reserved, committed, refunded or null. See Credits of a job. |
recovering | true while the platform repairs a job whose worker stopped after the model had delivered. |
input_data | Full job only. What was sent, after corrections: for example the final prompt, your own userPrompt, and the direction ids. |
credits | Full job and list. The credits the job held when it was accepted. This amount never changes. |
credits_charged | Full job and list. What the job cost once its hold settled, the steps it ran included, or null. See Credits of a job. |
credits_held, credits_released | Full job and list. The run's hold with its steps' holds, and what went back from it once every hold settled. See Credits of a job. |
job_type, source, source_detail | Full job only. The kind of job, and where it came from: source is internal, mcp, app, cli, sdk, extension, web or api, and source_detail names the client, such as sdk/1.10.0. |
session_id | Full job and list. The generation session the job was filed in, or null. |
favorited, rating | Full job and list. Your favorite (true or false) and your rating ("good", "bad" or null). See Favorites and ratings. |
title | Full job and list. Your own name for the job, or null until you name it. See Favorites and ratings. |
created_at, started_at | Full job only. Timestamps. |
completed_at | When the job finished, on the server's clock, or null until it has. The status route returns it too. |
input_data and output_data are public views: fields that exist only for the server are removed for every caller.
An Apply EDL job's output_data says what the render was cut from. quality is "proxy" for a Preview and "final" for a final, and clipKey names the plan clip when the request sent one. planBasis is returned when the request sent it, and renderBasis is a stamp of the render's own settings and sources, written by the server. A Speaker View job returns the same three stamps as sent. An older Apply EDL job reads back with the quality it was ordered at.
Image size on image jobs
Every job whose output_data has an imageUrl also carries the image's size in pixels: generations, edits, image-to-image runs, collages, overlays, extracted frames and more. A client can lay out a grid before the images load, or show and sort by size.
| Field | Type | Meaning |
|---|---|---|
width, height | integer | The size of imageUrl in pixels, as the file is stored: what you download, a free-tier watermark included. EXIF orientation is applied, so it is the size a browser shows. |
images | { url, width, height }[] | Beside imageUrls, on a job that made several images: one entry per image, in imageUrls order. An entry whose size could not be read has only its url. imageUrls itself stays an array of strings. |
{
"imageUrl": "https://cdn.nodaro.ai/images/<jobId>.png",
"imageUrls": ["https://cdn.nodaro.ai/images/<jobId>.png", "https://cdn.nodaro.ai/images/<jobId>-v1.png"],
"width": 1024,
"height": 1536,
"images": [
{ "url": "https://cdn.nodaro.ai/images/<jobId>.png", "width": 1024, "height": 1536 },
{ "url": "https://cdn.nodaro.ai/images/<jobId>-v1.png", "width": 1024, "height": 1536 }
]
}Treat all three fields as optional. Jobs completed before sizes were recorded carry none of them, and a size the server could not read is left out, never guessed. The fields are on every job read: GET /v1/jobs, GET /v1/jobs/:id and GET /v1/jobs/:id/status. The MCP job tools get_job, wait_for_job and get_asset carry width and height beside outputUrl.
Job statuses
| Status | Final | Meaning |
|---|---|---|
pending, queued, processing | No | The job is waiting to run or running. |
pending_review | No | The result exists, and the deployment holds it for a human review. Keep polling. |
completed | Yes | The result is in output_data. |
failed | Yes | error_message and error_hint say why. |
cancelled | Yes | You or the platform cancelled the job. |
Recovering jobs. When a worker stops after the model has already delivered, the job stays processing with recovering: true while the platform repairs it. It then completes or is refunded on its own. For slow models this can take tens of minutes, which is longer than the SDK's default wait: a JobTimeoutError from runAndWait does not cancel the job, so fetch it again later.
Jobs held for review. On a deployment that registers a review policy, a job can enter pending_review. The work is done, the credits stay reserved for the whole hold, and a person decides whether the result is released. The job then ends completed (approved), failed with a policy-block hint (rejected) or cancelled. A held job can be cancelled like any job in flight. Do not run the request again, because a duplicate would be held too. When the deployment sets a review deadline, a job nobody reviews in time is rejected: it ends failed, the reservation is refunded and the held result is deleted. A held job is never approved automatically. The SDK's runAndWait throws JobHeldError on the first poll that sees pending_review, and the CLI's --watch exits with code 3.
Poll well
- Poll every 2 to 5 seconds. A job changes state in seconds, not milliseconds.
- Status reads are free of the token's rate limit. Only workflow runs and workflow lists count against a personal API token's per-minute limit. See Rate limits.
- Track many jobs with one call. Use the batch endpoints instead of one request per job.
- Let the client wait for you. The SDK's
client.nodes.runAndWaitpolls every 2 seconds for up to 15 minutes, and the CLI's--watchpolls until the job ends.
Why a job failed
The error table in Errors covers requests that never created a job. A job that fails later carries error_message, and for five kinds of failure also a structured error_hint.
A model's safety filter blocked the request:
{ "kind": "safety-block", "class": "safety", "retried": true, "suggestedProvider": "nano-banana-pro" }classiscopyright,likenessorsafety. Acopyrightorlikenessblock is final: the same request never passes.- A
safetyfilter is not always consistent on some models. For GPT Image 2, GPT Image 2.5 Flare and GPT Image 2.5 Sunburst, Nodaro retries the same request once, at no extra cost.retriedsays whether that retry already ran. suggestedProviderappears only when the model has a recommended fallback. It is a real model id: send the same prompt and references to it.subject: "input_video"appears when the provider refused the job's input video itself, not the prompt, a reference image or the result. Send a different clip.subject: "input_image", always withclass: "copyright", appears when the provider refused one of the job's input images as possibly copyrighted. Send a different picture: rephrasing the prompt will not help.subject: "prompt"appears, on any class, when the provider's own words named the prompt. Changing the prompt is the fix.subject: "audio"appears, on any class, when the provider's own words blamed the generated audio, not the picture.- Treat a
subjectyou do not know as absent.
A deployment's policy rejected the job:
{ "kind": "policy-block", "policyId": "brand-safety", "reason": "This image was not approved for release.", "hookPoint": "result" }reasonis text written for your user by the deployment's policy. Show it as it is.hookPointisrequestwhen the job was refused before it ran, andresultwhen its result was rejected afterwards, including by a reviewer.- The platform does not retry a policy rejection and offers no other model.
When the job ran out of credits partway, because a step that reserves its own credits was refused, for example the video that a Nodaro Motion or trend run starts:
{ "kind": "credits", "code": "insufficient_credits" }codeis the refusal's own code, in lowercase snake case:insufficient_credits, when the balance could not cover the step, or a plan or workspace limit such asfree_video_limit,instance_cap_reached,budget_exceeded,member_cap_exceededoruser_allowance_exceeded. Treat an unknown code likeinsufficient_credits.- The job's credits are refunded. Ask the person to top up, or to raise the limit, and run it again. The same request fails the same way until then.
The provider refused the job's input video without naming a content-policy class. Its message is about the file, not a policy:
{ "kind": "input-refused", "subject": "input_video" }Treat it like any other failure. No other model is suggested, but you know which input to change: send a different clip.
The provider refused the request as it was sent, naming a fault of the request, such as a setting, a file's format or size, or the prompt's length:
{ "kind": "request-refused", "subject": "input_video" }subject: "input_video" is there only when the provider's words name the input video as the fault, for example its length or size, or no character found in it. A different clip is then what can change. The same request is refused the same way, so do not run it again unchanged. Change the input images, the video, the prompt or the settings. It is not a content-policy block. On a job made of steps, such as a Nodaro Motion or trend run, it is the refusal of one step.
Every other failure has no error_hint. When a model refuses the request itself, because the settings or the input media are invalid for that model, error_message says so: change the settings or the media before you run it again. Sometimes the model refuses the request with no reason, only its general "Generation failed. Please try again later." message. Then error_message says that the model gave no reason, and that the cause may be temporary or in the input media, such as its size or format. Run that job again once, and change the input media if it fails again. An error on the model's side stays worth retrying, whatever its wording. error_hint appears on every job payload that carries error_message: the full job, the status routes, the list, and both batch routes.
The provider's own words
Beside error_message, the platform's own sentence about the failure, a failed job carries error_detail: what the provider itself said, stored when the job failed. Links in it are cut to their host, and secrets are removed. Otherwise the words are the provider's own, for example task failed: [400] Request blocked: The uploaded image may contain protected or restricted IP content.
nullwhen the provider said nothing, for example on a refusal before anything was sent, a timeout, or a queue wait that ran out. An older server leaves the field out.- Show it next to
error_message. Branch onerror_hint, never on these words. - Where it appears: the same job payloads as
error_message, and the job inside ajob.failedwebhook event. A failed node of a workflow run carries it aserrorDetailinnodeStates. See Executions.
The words go only to a caller who may read them, because they can quote a prompt:
- A trend run withholds the words with its text, because they can quote its hidden input.
- The jobs of a workflow run carry the words only when their owner can open the workflow that the run executed. That is the workflow's creator, or someone it is shared with. You own the jobs of a run you start from someone else's share link, but they carry
error_detail: null, on every job payload and in thejob.failedevent. - A published app's runner never receives the words.
- The hidden run of a use-case node, such as Tighten Episode, executes no saved workflow. When a node of a workflow run started it, it is linked to that run and judged by that run's workflow, on its node states and its jobs alike. A hidden run inside another follows the links to the nearest run of a workflow. A hidden run started on its own is its owner's.
- A job of your own that belongs to no run, such as one node run on its own, carries its words to you. So does a job that is not linked to the run that started it. That is a job of a few node types, a job of a use-case node's hidden run made before these links were recorded, or a job of a run whose workflow was deleted. A linked hidden run whose run is gone keeps its words from everyone but administrators.
- A deployment can keep the words for its administrators. With
jobs.errorDetail: "admin"in its surface profile, every caller who is not an administrator getserror_detail: null. That is what a failure with no provider words looks like, so a client needs no change. See Editions and surface profiles.
Credits of a job
credit_status follows the job's credit reservation:
| Value | Meaning |
|---|---|
reserved | Credits are held while the job runs. |
committed | The job delivered and was charged. |
refunded | The reservation was released, for example after a safety block. |
null | The job has no credit record to report. |
It appears on GET /v1/jobs, GET /v1/jobs/:id, GET /v1/jobs/:id/status and GET /v1/jobs/status, never on POST /v1/jobs/batch-status. A generation that ends in a safety block or a policy block is always refunded. The rare exception is a job whose credits were already settled before a policy rejected its result. See Credits.
A job's credits is the amount the job itself held when it was accepted, and it never changes. GET /v1/jobs and GET /v1/jobs/:id also carry credits_charged: what the job cost once its hold settled, read from the same credit record.
credits_chargedcan be lower thancredits. Some jobs hold more than they use, for example to cover a possible re-render or a length measured after the run. The rest goes back to your balance.- When the job's own hold was refunded,
credits_chargedis0plus any steps that settled. For example, a refunded Nodaro Motion run whose reference and mask steps were charged readscredit_status: "refunded"withcredits_charged: 165. credits_chargedisnullwhile the job's own hold is still open (credit_status: "reserved"), even when some of its steps have already settled. It is alsonullwhen nothing in the run was reserved.
To total what jobs cost, add up credits_charged, not credits. For example, a job that held 888 credits and settled at 444 reads credits: 888, credits_charged: 444 and credit_status: "committed".
Jobs that run other jobs
credits_charged is the charge of the job as a run, its steps included. Some jobs run other jobs for you. For example, a Nodaro Motion run prepares its references, makes a mask and renders the video, each as a step of its own. A step is never listed on its own, and it is charged once it settles.
- A step whose hold is still open adds nothing yet, and a refunded step adds
0. credits_heldis the run's hold: the job's owncreditsplus its steps' holds. For a job with no steps, it is the same ascredits.credits_releasedis what went back from the hold (credits_heldminuscredits_charged), once every hold in the run has settled. It isnulluntil then.creditsandcredit_statusstay the job's own.- If the steps cannot be read for a moment,
credits_charged,credits_heldandcredits_releasedreadnull. That means unknown, never a lower figure.
For example, a Nodaro Motion preview whose own hold was 44, with two reference steps at 66, a mask at 33 and a video held at 3,564 and settled at 854, reads credits: 44, credits_charged: 1063, credits_held: 3773 and credits_released: 2710.
A workflow run counts a node the same way. The node's credits include the settled steps of its job, a failed node's too, and so does the run's totalCreditsUsed. A step that settles after its node finished is counted when the run ends.
List your jobs
GET /v1/jobs returns your jobs, newest first, as { data: Job[], next }. A child job, one that another job started as a step of its own work, is not listed. Output keys whose name starts with __ are for the server only, and no job read returns them.
| Query | Meaning |
|---|---|
limit | Page size, up to 100. |
cursor | The next value of the previous page. |
status | One or more job statuses: pending, queued, processing, pending_review, completed, failed or cancelled. An unknown status answers 400 validation_error. |
type | One or more routes that created the job, such as llm-structured or video-analysis, up to 20. |
origin | The client app that sent it, exactly, such as studio. |
source | One or more surfaces that created the job: mcp, cli, sdk, web, extension, app, api or internal, the job's source field. source=mcp lists what your MCP clients, such as Claude or ChatGPT, made. |
favorited | true keeps the jobs you favorited, and false the ones you did not. |
rating | good, bad, rated (any rating) or unrated. |
projectId | The jobs a session of this project holds now, each listed once. none keeps the jobs a session with no project holds. Cannot be combined with inSession=false. |
inSession | false keeps the jobs no session holds now, which the app shows as General. true keeps the jobs at least one session holds. |
withSessions | true adds sessions: [{ id, title, projectId }] to each job: the sessions it is in. |
batchId | The jobs of one Generate Image batch. See Several images in one call. |
attachToCharacterId | The jobs of one character. See Characters. |
status, type and source take a comma-separated list (status=failed,cancelled) or a repeated parameter (type=a&type=b). Every filter matches exactly, and the filters combine. A page may hold fewer rows than limit, even none, and still carry a next: keep paging while next is present, never by counting rows.
const { data: runs, next } = await client.jobs.list({ type: 'llm-structured', origin: 'my-app' })Favorites and ratings
You can keep two marks on your generations. Both are readable on every job and usable as list filters:
- A favorite is a bookmark on a job: one of your own, in any status, or anyone's public completed job in the gallery.
- A rating is your verdict on one of your own jobs:
goodorbad.
You can also give one of your jobs a title, a name of your own, such as "Wedding toast, final" on a picture, a clip, a track or a speech take.
| Method | Path | Body or query | What it does |
|---|---|---|---|
PUT | /v1/jobs/:id/rating | { "rating": "good" | "bad" | null } | Rate one of your jobs, or clear its rating with null. rating is required, so clearing is explicit. Returns { data: { jobId, rating } }. |
PATCH | /v1/jobs/:id | { "title": string | null } | Name one of your jobs, or clear its name with null or "". Returns { data: { jobId, title } }. |
POST | /v1/gallery/favorite | { "jobId": "..." } | Favorite a job, or remove the favorite when it already is one. Returns { favorited }, the state after the toggle. |
GET | /v1/gallery/favorites | None | Your favorited job ids, newest first: { data: string[] }. |
GET | /v1/gallery | type, userId, favoritesOnly=true, limit (1 to 50, default 20), cursor | The public gallery, newest completed first. With your own userId and your token, favoritesOnly=true narrows the list to your favorites. Each card carries title, the owner's name for the job, or null. |
- Every job carries
favoritedandratingonGET /v1/jobsandGET /v1/jobs/:id. When the server cannot read them for a moment, the jobs still answer and the two fields are left out. Treat a missing field as unknown, never asfalse: a toggle on an unknown favorite could remove it. - One rating per job, its owner's. Anyone else's job answers
404 not_found. A rating other thangood,badornullanswers400 validation_error. - Another person's private job answers
404 not_foundon a favorite, and nothing is stored. Removing a favorite always works. - Scopes: an OAuth app token needs
assets:writefor the two writes, andassets:readorjobs:readto list your favorites. The job reads stayjobs:read. - A deployment that hides the gallery answers
404on every/v1/galleryroute. - A deployment that does not allow public work answers every
GET /v1/galleryview with an empty page,{ data: [], nextCursor: null, totalCount: 0 }, except your own: your ownuserIdwith your token still lists your work. There,POST /v1/gallery/favoriteadds only your own jobs, and anyone else's job answers404 not_found. Removing a favorite always works. The gallery's report route,POST /v1/gallery/report, answers404 not_foundfor every item. - A title is one line of plain text, 1 to 120 characters, in any language, trimmed. Anything else answers
400 validation_error. Only the job's owner can name it, and anyone else's job answers404 not_found. Naming is free and works on any job type and status, the Trash included. Nothing else about the job changes. An OAuth app token needsassets:write. - The title reads back as
titlewherever the job is returned:GET /v1/jobs/:id,GET /v1/jobs, a session's items,GET /v1/trash, the gallery's cards (public, beside the prompt) and the library's rows. On a library row,titleis the name of the job that made the file, not itsfilename.
# Rate a job good, favorite it, then list your good favorites that completed
curl -s -X PUT "https://app.nodaro.ai/v1/jobs/$JOB/rating" \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"rating": "good"}'
curl -s -X POST "https://app.nodaro.ai/v1/gallery/favorite" \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"jobId\": \"$JOB\"}"
curl -s "https://app.nodaro.ai/v1/jobs?rating=good&favorited=true&status=completed" \
-H "Authorization: Bearer $NODARO_API_KEY" | jq '.data[] | {id, favorited, rating}'Record an edited image
An image edited in your own app, for example cropped, annotated or filtered, is an upload, not a job. On its own, it cannot be rated, favorited, filed in a session or passed to a tool that takes a job id. Record it as a job:
- Store the edited image with
POST /v1/upload, and keep itsassetId. See Uploads. - Optionally, store the editor's design state with
POST /v1/upload-json, so the edit can be opened again. It takes any JSON body up to 25 MB and returns{ url }. - Call
POST /v1/jobs/from-uploadwith{ assetId, sourceJobId?, sourceImageUrl?, sourceAssetId?, editStateUrl?, sessionId? }.
The route records the upload as an image-edit job that is completed at once, and returns { data: Job }. It answers 201 when it records the image, and 200 when this assetId was recorded before, with the same job.
- Free and private. Recording the job charges nothing, because the file's storage was counted when you uploaded it. The job is never public: it lists in your own job history, gallery and library views, never in the public gallery.
- What it holds.
output_data.imageUrlis the upload, withthumbnailUrl,widthandheight.input_datacarriestype: "image-edit", theassetIdand the source:sourceJobIdorsourceAssetId, plussourceImageUrl, the source's image. For a job that made several images, send the one you edited assourceImageUrl. An edit of a character, object, creature or location image keeps that link, so it lists with that asset.GET /v1/jobs?type=image-editlists these jobs. - One upload, one job. The same
assetIdagain answers the same job, so a retry or a double click never makes two. A retry also finishes a job that an earlier attempt left unfinished, so a2xxanswer always carries a completed job. The job keeps the source, design state and session of the request that created it. A retry's own values are not applied. To show the job in a second session, add it there withPOST /v1/sessions/:id/items. - Refusals.
404 not_foundwhen the asset, the source or the session is not yours.400 validation_errorwhen the asset is not an image or is not a file you stored. The same error comes when the source job has no finished image,sourceImageUrlis not one of its images, both sources are sent, oreditStateUrlis not a URL thatPOST /v1/upload-jsonreturned.409 conflictwhen you already usedimage-edit:<assetId>as theIdempotency-Keyof another request.422 job_blockedwhen a job policy of the deployment refuses the image, on every retry. - Deleting. Deleting the job leaves the uploaded file in your uploads. Deleting the upload from your library keeps the file while this job still shows it.
An OAuth app token needs assets:write.
# Record an edited image next to the job it was made from, in a session
curl -s -X POST "https://app.nodaro.ai/v1/jobs/from-upload" \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"assetId\": \"$ASSET\", \"sourceJobId\": \"$JOB\", \"sessionId\": \"$SESSION\"}" | jq '.data | {id, status, output_data}'Poll many jobs at once
Two endpoints return the status of up to 100 jobs in one round trip:
curl -s "https://app.nodaro.ai/v1/jobs/status?ids=$JOB_A,$JOB_B,$JOB_C" \
-H "Authorization: Bearer $NODARO_API_KEY"{
"jobs": [
{ "id": "0f1a9c2e-…", "status": "completed", "output_data": { "imageUrl": "https://…/a.png" }, "error_message": null, "error_hint": null, "credit_status": "committed" },
{ "id": "7d3e5f60-…", "status": "processing", "output_data": null, "error_message": null, "error_hint": null, "credit_status": "reserved" }
]
}curl -s -X POST https://app.nodaro.ai/v1/jobs/batch-status \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"jobIds\": [\"$JOB_A\", \"$JOB_B\", \"$JOB_C\"]}"The answer is { "data": [ … ] }, with id, status, output_data, error_message and error_hint for each job, and no credit_status.
Ids that do not exist, or that belong to someone else, are left out without an error. Compare the answer with your own list of ids.
Cancel or delete a job
POST /v1/jobs/:id/cancel cancels a job and releases the credits it reserved. It answers { "success": true, "cancelled": 1 }. A job held in pending_review can be cancelled too. Cancelling a job also cancels its child jobs that are still running. A finished child job is left as it is, and one already sent to the model finishes, like any job in flight.
Cancelling a metered LLM job. A language-model job is charged for the tokens its call uses, so cancelling it depends on whether the call has started:
POST /v1/jobs/:id/cancelon a job whose call has started answers{ "success": true, "cancelled": 0, "inFlight": true }. The job finishes and is charged only the credits it used.- The canvas Stop,
POST /v1/jobs/cancel-alland a workflow cancel (POST /v1/workflow-executions/:id/cancel) cancel the job. It is charged what the provider already billed for it, and the rest of its hold is refunded. - A job stopped before its call starts is refunded, whichever way it was cancelled. The exception is what an earlier attempt of the same job already used, for example a retry after a billed attempt. That usage is charged.
DELETE /v1/jobs/:id sends a job to the Trash and answers { "success": true }. Only the job's owner may delete it. A job that is still running keeps running, so cancel it first when its work should stop. Nothing is refunded when a job is deleted. OAuth apps need the assets:write scope to delete or restore a job.
curl -s -X POST https://app.nodaro.ai/v1/jobs/$JOB_ID/cancel \
-H "Authorization: Bearer $NODARO_API_KEY"const { cancelled } = await client.jobs.cancel(jobId)
await client.jobs.delete(jobId)nodaro jobs cancel 0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10To stop a whole workflow run, cancel its execution instead: see Executions.
The Trash
Removing an item from a session and deleting a job both send it to the Trash.
- A removed item stays until you restore it with
POST /v1/sessions/:id/items/:jobId/restore, or delete it for good withDELETE /v1/sessions/:id/items/:jobId?forever=true. A removal never deletes the job. An item that is in no live session shows in General. - A deleted job disappears from every read: job lists and reads by id, status polls, the gallery, session feeds and previews, MCP tools and the CLI. It stays restorable for 30 days.
POST /v1/jobs/:id/restoreputs it back in every session it was in. - After 30 days a deleted job is deleted for good, with its private media. A copy saved to the Library stays.
- Delete for good now.
DELETE /v1/jobs/:id?forever=truedeletes a job that is already in the Trash for good. On a live job it answers409 not_in_trash. - Nothing is refunded when a job is deleted, or when it is deleted for good.
GET /v1/trash lists the Trash, newest first, one page at a time (limit 1 to 100, default 50, and cursor). Each entry is either { kind: "removed", removedAt, session: { id, title, projectId }, job } or { kind: "deleted", deletedAt, purgeAt, job }. A removed item whose job you also deleted is listed once, as the deleted job.
| Query | Meaning |
|---|---|
projectId | A project id: that project's sessions' removed items, and the deleted jobs a session of it held. none: the sessions with no project, and General. |
tool | What a session of that creation tool holds or held. A job no session ever held has no tool, so tool leaves it out. |
type | The job's input_data.type, comma-separated. Filter by type alone, with your tool's job types, to include deleted jobs that were never in a session. |
Deleting a session also deletes its removed items.
Component runs
POST /v1/component/execute runs a saved Component in the background and answers 202 with { jobId }. Poll that job like any other. On a deployment that does not allow public work, it runs only your own components, or a component inside a full run of a workflow that holds it. Any other call answers 404 not_found "Component not found". With an OAuth app token, the route needs the workflows:execute scope, and the wait-limit read below needs jobs:read. The server gives a component run 90 minutes, plus the time budget of any long render inside it, such as the final render of an Apply EDL node.
A client with its own time limit can ask how long the server will wait:
curl -s https://app.nodaro.ai/v1/component/execute/$JOB_ID/wait-limit \
-H "Authorization: Bearer $NODARO_API_KEY"{ "data": { "budgetExcessMs": 0, "waitLimitMs": 5400000, "pendingBudgetedNodes": true } }waitLimitMsis the server's wait for this run: 90 minutes plusbudgetExcessMs.budgetExcessMsstays0until the run starts a long render.pendingBudgetedNodesistruewhile a long render has not started yet, and while the run itself has not started. So a0excess does not yet mean there is nothing long inside.
Both values can change while the run goes on, so ask again when waitLimitMs is reached. The route answers only the run's owner, with 404 for anyone else and for jobs that are not component runs. The editor itself uses this rule: it waits 30 minutes for a run with nothing long inside, waitLimitMs once a long render has started, and at least 90 minutes while pendingBudgetedNodes is true, then asks again.
Generate Video Pro runs
Generate Video Pro makes long videos one segment at a time and saves its progress between segments, so a run can be stopped and continued later. It runs on Nodaro Cloud, and a self-hosted install runs it through its connection to Nodaro Cloud.
Stop a run
POST /v1/generate-video-pro/:jobId/stop stops a processing run gracefully:
- The segment being generated is abandoned, and it is still billed, because the model keeps rendering it.
- The remaining segments are skipped.
- Every finished segment is stitched into the job's final video.
- The unused part of the reservation is refunded.
The answer is { "jobId": "…", "stopping": true }. Keep polling the job: it ends completed, with output_data.pro.stopped set to true and stoppedAtSegment. A job that is still pending is cancelled with a full refund instead.
Continue a run
POST /v1/generate-video-pro/continue starts a new job from a finished one. It reuses the parent's plan and every delivered segment before fromSegment, and generates again from there:
{ "fromJobId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "fromSegment": 4 }fromSegmentcounts from 1. By default it is the first segment that was not delivered.- The parent must be finished: stopped, failed with at least one delivered segment, or completed. An explicit
fromSegmenton a completed run generates its ending again. - You pay only for the segments generated again, plus the flat Video Pro fee.
- The route honors an
Idempotency-Keyheader, so a retried request does not start a second job.
The answer is { jobId, continuedFromJobId, fromSegment, segmentCount }. Poll the new jobId.
curl -s -X POST https://app.nodaro.ai/v1/generate-video-pro/$JOB_ID/stop \
-H "Authorization: Bearer $NODARO_API_KEY"
curl -s -X POST https://app.nodaro.ai/v1/generate-video-pro/continue \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4c1f0a7e-2b3d-4e5f-8a9b-0c1d2e3f4a5b" \
-d "{\"fromJobId\": \"$JOB_ID\", \"fromSegment\": 4}"await client.videoPro.stop(jobId)
const { data } = await client.jobs.getStatus(jobId) // completes with a partial video
const { jobId: childId } = await client.videoPro.continueRun(jobId, { fromSegment: 4 })nodaro video-pro stop 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
nodaro video-pro continue 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d --from-segment 4 --watchBoth routes answer 404 for a job that is not yours and 400 for a job that is not a Video Pro run.
Estimate a run
POST /v1/credits/video-pro-estimate prices a Video Pro run without creating a job or reserving credits:
{ "provider": "gemini-omni-flash", "resolution": "720p", "duration": 12, "renderMethod": "keyframes", "segmentMode": "short" }In one example configuration the answer is { "data": { "credits": 760, "upperBound": true } }. Read the live response for current prices.
Prop
Type
- With
segmentModeshortorlong, the run first assigns whole actions to spans of the source, andupperBound: truemeans the figure is the reservation limit before planning. The final charge follows the actual plan. - A
planOnlyestimate covers the planning fee and returnsupperBound: false. ItssourceSegmentDurationsandplanCheckpointcan be sent back assourceSegmentDurationsandseedPlan, with the same mode and settings.
Read Generate Video Pro for how segmentation works and what each setting does.
Frequently asked questions
What is a job in the Nodaro API?
A job is one unit of generation, such as one image, one video render or one speech clip. POST /v1/<node-type> returns a job id, and a workflow run creates one job for each AI node it runs.
How often should I poll a Nodaro job?
Every 2 to 5 seconds. A job changes state in seconds, not milliseconds. To track many jobs, use GET /v1/jobs/status or POST /v1/jobs/batch-status, which return up to 100 jobs in one call.
What does the pending_review status mean?
The deployment holds the result for a human review before releasing it. The job is still in flight and its credits stay reserved. Keep polling; it ends completed, failed or cancelled.
How do I know if a failed job was refunded?
Read credit_status on GET /v1/jobs/:id or GET /v1/jobs/:id/status. It is reserved, committed or refunded. A generation blocked by a safety filter or a deployment policy is always refunded.
How do I know what a job actually cost?
Read credits_charged on GET /v1/jobs or GET /v1/jobs/:id. It is what the job cost once its hold settled, including the steps it ran, and it can be lower than the hold. It is null while the job's own hold is still open.
Can I rate or favorite a job through the API?
Yes. PUT /v1/jobs/:id/rating sets good, bad or null on one of your jobs, and POST /v1/gallery/favorite toggles a favorite. GET /v1/jobs filters on both with rating= and favorited=.
Related
Nodes
Executions
Errors
Credits
Generate Video Pro
Last updated on
Nodes
Run any Nodaro node with POST /v1/<node-type>, discover nodes, models and picker values, and steer prompts with references and cinematic direction ids.
Executions
Follow a Nodaro workflow run node by node, read each node's result, list a workflow's past runs, and cancel a run now or after its running nodes finish.