REST API
Server-side7 operationsNordy API
Run ComfyUI workflows and retrieve their results with the Nordy REST API.
- Base URL
- https://api.nordy.ai
- AuthenticationGet an API key
- api-key: $NORDY_API_KEY
On this page
Start here
Get an API key
You need an active Pro subscription. Open Manage API keys to create a key. You can have up to three active keys; each complete key is shown only once.
Send the key in the api-key header. Store it in a server environment variable and keep it out of browser code, mobile apps, and public repositories.
export NORDY_API_KEY="your_api_key"
export NORDY_BASE_URL="https://api.nordy.ai"Job creation limit
Job creation is subject to a daily limit.
Quickstart
These examples use Bash, curl, and jq. Run the steps in the same terminal and directory so they can reuse the environment variables and saved files.
1. Prepare your workflow. Test it in Nordy ComfyUI with a Save node for the outputs you need. Save the Export Workflow (API Format) or Export (API) result as prompt.json; editor-format JSON cannot be used. You can start with an example workflow.
2. Create a Job. Run this command once. It wraps your prompt in the request body and saves the response to job.json.
jq '{prompt: .}' prompt.json > request.json &&
curl --fail-with-body --silent --show-error --max-time 30 \
-X POST "$NORDY_BASE_URL/v1/api/job/comfy-prompt" \
-H "api-key: $NORDY_API_KEY" \
-H "content-type: application/json" \
--data-binary @request.json --output job.json &&
jq '{id, status}' job.jsonA 202 Accepted response means the Job was accepted. Keep its id; completion is checked separately.
3. Check the result. Read the saved Job ID and fetch its current status.
JOB_ID=$(jq -er '.id' job.json) &&
curl --fail-with-body --silent --show-error --max-time 30 \
"$NORDY_BASE_URL/v1/api/job/$JOB_ID" \
-H "api-key: $NORDY_API_KEY" --output job-status.json &&
jq '{status, outputs, error}' job-status.json| Status | Next step |
|---|---|
queued / processing | Wait at least 5 seconds, then repeat step 3 only. |
succeeded | Download the files listed in outputs. |
failed / canceled | Check error.code and error.message. See Failed Jobs. |
For automatic status checks, use the polling script. If creation times out, check Retries and timeouts before submitting again.
4. Download the output
After the Job succeeds, this command saves the first result as output.bin. Use an extension appropriate for the file, and download each URL if the Job has multiple outputs. Do not send the API key to download URLs.
OUTPUT_URL=$(jq -er \
'select(.status == "succeeded") | .outputs[0].url // empty' job-status.json) &&
curl --fail --location --show-error \
--output output.bin --url "$OUTPUT_URL"Outputs expire 7 days after they are created. Save each file before its
expiresAttimestamp. Once all outputs expire, the Job remains available withoutputs: [].
Example workflows
Choose an example, save its JSON as prompt.json, and follow Quickstart to create and check the Job.
| Goal | Example |
|---|---|
| Generate an image from text | DreamShaper |
| Try a workflow without a checkpoint | Load and save an image |
Generate an image with DreamShaper
Check the available checkpoint names first. Use dreamshaper_8.safetensors if listed, or update 4.inputs.ckpt_name to an available compatible checkpoint. An empty object means the node is unavailable.
curl --fail-with-body --silent --show-error \
"$NORDY_BASE_URL/v1/api/comfy/object-info/CheckpointLoaderSimple" \
-H "api-key: $NORDY_API_KEY" --output object-info.json &&
jq -r '.CheckpointLoaderSimple.input.required.ckpt_name[0][]' object-info.jsonThis example needs no input image.
Full codeJSON
{
"3": {
"inputs": {
"seed": 102298450217384,
"steps": 20,
"cfg": 8,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": ["4", 0],
"positive": ["6", 0],
"negative": ["7", 0],
"latent_image": ["5", 0]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"4": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Load Checkpoint"
}
},
"5": {
"inputs": {
"width": 256,
"height": 256,
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "Empty Latent Image"
}
},
"6": {
"inputs": {
"text": "beautiful scenery nature glass bottle landscape, purple galaxy bottle",
"clip": ["4", 1]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Positive)"
}
},
"7": {
"inputs": {
"text": "text, watermark",
"clip": ["4", 1]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Negative)"
}
},
"8": {
"inputs": {
"samples": ["3", 0],
"vae": ["4", 2]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"9": {
"inputs": {
"filename_prefix": "dreamshaper-api-example",
"images": ["8", 0]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
}
}| To change | Edit |
|---|---|
| Positive and negative prompts | 6.inputs.text and 7.inputs.text |
| Seed and sampling settings | 3.inputs |
| Image size and batch size | 5.inputs |
| Checkpoint | 4.inputs.ckpt_name |
Keep node connections unchanged. A listed node or model may still be unavailable when the Job runs.
Load and save an image from a URL
This example uses LoadImage → SaveImage without a checkpoint or custom node. Replace the image URL with your own.
Full codeJSON
{
"11": {
"inputs": {
"image": "https://your-public-host.example/input.png"
},
"class_type": "LoadImage",
"_meta": {
"title": "Load Image"
}
},
"12": {
"inputs": {
"filename_prefix": "nordy-api-example",
"images": [
"11",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
}
}Use an HTTPS URL accessible without login cookies or custom headers, and keep the connection ["11", 0] unchanged. A presigned URL must stay valid until the Job finishes and allow both HEAD and GET; a GET-only signature can be rejected.
The image is decoded and saved again, so encoding, metadata, and transparency may change.
API reference
Select an API to view its parameters, response fields, and examples. Each reference page also links to API TEST with that API selected.
| Operation | Method and path | Success |
|---|---|---|
| Create a Job | POST /v1/api/job/comfy-prompt | 202 |
| List Jobs | GET /v1/api/job/list | 200 |
| Get Job status and results | GET /v1/api/job/{id} | 200 |
| Cancel Jobs | POST /v1/api/job/cancel | 200 |
| Cancel all Jobs | POST /v1/api/job/cancel-all | 200 |
| Get all node definitions | GET /v1/api/comfy/object-info | 200 |
| Get one node definition | GET /v1/api/comfy/object-info/{className} | 200 |
Troubleshooting
See Responses and errors for all HTTP statuses, error codes, Job statuses, and cancellation results.
HTTP request errors
Nordy errors include a code, a message, and a requestId you can share with support.
Full codeJSON
{
"statusCode": 400,
"code": "INVALID_INPUT",
"message": "The request is invalid.",
"requestId": "request-id",
"details": {
"field": "prompt"
}
}| Status and code | What to check |
|---|---|
400 INVALID_JSON / INVALID_INPUT | Validate the JSON, use an API-format prompt, and wrap it in prompt once. |
400 MULTIPLE_AUTH_CREDENTIALS | Send the API key without login cookies. |
401 AUTHENTICATION_REQUIRED | Check the api-key header and whether the key was deleted. |
402 INSUFFICIENT_CREDIT | Check your credit balance. |
403 PRO_SUBSCRIPTION_REQUIRED | Check that your Pro subscription is active. |
403 USER_SUSPENDED | Contact support about your account status. |
404 JOB_NOT_FOUND | Check the Job ID and API key account. Only Jobs created through this API are available. |
413 REQUEST_BODY_TOO_LARGE | Keep the JSON request within 50 MiB. |
415 UNSUPPORTED_MEDIA_TYPE | Send Content-Type: application/json. |
429 DAILY_JOB_LIMIT_EXCEEDED | Try again after the creation limit resets. |
5xx | For lookup requests, wait before retrying. For creation requests, follow the guidance below. |
The API reference lists the responses for each endpoint. Preserve unknown error codes as well as known ones when handling errors.
Failed Jobs
HTTP 200 means the lookup worked; the Job itself may still have failed. Check status and error together.
| Job error | Next step |
|---|---|
INVALID_INPUT | Check node inputs and model names. error.details.node_errors may identify the affected node. |
INSUFFICIENT_CREDIT | Check your credit balance before creating another Job. |
EXECUTION_TIMEOUT / WORKER_UNAVAILABLE / EXECUTION_FAILED | Review the error message. Contact support if the issue persists. |
JOB_CANCELED | The Job is canceled. Other errors use failed. |
Validation errors may include submitted values and model choices. Share only the details needed for support and avoid publishing the complete error object.
Full codeJSON
{
"id": "68abcdef0123456789abcdef",
"user_metadata": {},
"status": "failed",
"createdAt": "2026-08-26T00:00:00.000Z",
"startedAt": "2026-08-26T00:00:02.000Z",
"completedAt": "2026-08-26T00:00:04.000Z",
"outputs": null,
"error": {
"code": "INVALID_INPUT",
"message": "The job input is invalid.",
"details": {
"error": {
"type": "prompt_outputs_failed_validation",
"message": "Prompt outputs failed validation"
},
"node_errors": {
"4": {
"class_type": "CheckpointLoaderSimple",
"errors": [
{
"type": "value_not_in_list",
"message": "Value not in list"
}
]
}
}
}
}
}Retries and timeouts
- Do not automatically repeat a creation request after a timeout or lost response. A Job may already exist. Use the saved ID or check recent Jobs before deciding whether to submit again.
- You can retry a failed GET lookup with the same ID after a delay. For queued or processing Jobs, follow
Retry-After. Resolve authentication and input errors before retrying.
Request limits
See Input limits for prompt and metadata formats, sizes, and node counts.
Advanced usage
Automatic polling
Run this script with the Quickstart environment and job.json. It follows Retry-After when valid, otherwise waits 5 seconds, and saves the latest response to job-status.json.
The script stops after 2 hours; this is a client waiting limit and does not cancel the Job. It prints output URLs on success and an error on failure. Download successful results using step 4.
Full codebash
if ! JOB_ID=$(jq -er '.id' job.json); then
printf 'Could not read the Job ID from job.json.\n' >&2
exit 1
fi
POLL_TIMEOUT_SECONDS=7200
POLL_DEADLINE=$((SECONDS + POLL_TIMEOUT_SECONDS))
while true; do
REMAINING_SECONDS=$((POLL_DEADLINE - SECONDS))
if (( REMAINING_SECONDS <= 0 )); then
printf 'Timed out waiting for Job %s.\n' "$JOB_ID" >&2
exit 1
fi
REQUEST_TIMEOUT_SECONDS=30
if (( REMAINING_SECONDS < REQUEST_TIMEOUT_SECONDS )); then
REQUEST_TIMEOUT_SECONDS=$REMAINING_SECONDS
fi
if ! HTTP_STATUS=$(curl --fail-with-body --silent --show-error \
--max-time "$REQUEST_TIMEOUT_SECONDS" \
"$NORDY_BASE_URL/v1/api/job/$JOB_ID" \
-H "api-key: $NORDY_API_KEY" \
--dump-header job-headers.txt \
--output job-status.json \
--write-out '%{http_code}'); then
if [ -s job-status.json ]; then cat job-status.json >&2; fi
exit 1
fi
case "$HTTP_STATUS" in
2??) ;;
*)
printf 'Unexpected HTTP status: %s\n' "$HTTP_STATUS" >&2
cat job-status.json >&2
exit 1
;;
esac
if (( SECONDS >= POLL_DEADLINE )); then
printf 'Timed out waiting for Job %s.\n' "$JOB_ID" >&2
exit 1
fi
if ! STATUS=$(jq -er '.status' job-status.json); then
printf 'The response did not include a valid Job status.\n' >&2
exit 1
fi
printf 'Job status: %s\n' "$STATUS"
case "$STATUS" in
succeeded|failed|canceled) break ;;
queued|processing) ;;
*)
printf 'Unknown Job status: %s\n' "$STATUS" >&2
exit 1
;;
esac
if (( SECONDS >= POLL_DEADLINE )); then
printf 'Timed out waiting for Job %s.\n' "$JOB_ID" >&2
exit 1
fi
RETRY_AFTER=$(
awk -F ': *' '
tolower($1) == "retry-after" {
gsub("\\r", "", $2)
print $2
exit
}
' job-headers.txt
)
case "$RETRY_AFTER" in
''|*[!0-9]*) RETRY_AFTER=5 ;;
esac
REMAINING_SECONDS=$((POLL_DEADLINE - SECONDS))
if (( RETRY_AFTER > REMAINING_SECONDS )); then
RETRY_AFTER=$REMAINING_SECONDS
fi
sleep "$RETRY_AFTER"
done
if [ "$STATUS" = "succeeded" ]; then
jq -r '.outputs[] | "\(.kind)\t\(.url)"' job-status.json
else
jq '{code: .error.code, message: .error.message}' job-status.json
exit 1
fiList more Jobs
Fetch the first page:
curl --fail-with-body --silent --show-error --max-time 30 --get \
"$NORDY_BASE_URL/v1/api/job/list" \
-H "api-key: $NORDY_API_KEY" \
--data-urlencode 'limit=20' --output jobs.json &&
jq . jobs.jsonThis command passes next_cursor unchanged to fetch the next page and update jobs.json. It sends no request when the cursor is null:
NEXT_CURSOR=$(jq -er '.next_cursor // empty' jobs.json) &&
curl --fail-with-body --silent --show-error --max-time 30 --get \
"$NORDY_BASE_URL/v1/api/job/list" \
-H "api-key: $NORDY_API_KEY" \
--data-urlencode 'limit=20' \
--data-urlencode "cursor=$NEXT_CURSOR" --output jobs.json &&
jq . jobs.jsonCancel pending Jobs
Only Pending Jobs, before execution preparation begins, can be canceled. Choose Cancel Jobs to provide an array of Job IDs, or Cancel all Jobs for your account’s entire pending queue. Each reference includes the request example and response details.
Add your own metadata
Use user_metadata to associate a Job with a record in your application. For example:
{"order_id": "example-order-001"}Metadata is returned with the Job, but does not affect workflow inputs or prevent duplicate creation. See Input limits for supported values.
Terms and abbreviations
| Term | Meaning |
|---|---|
| Workflow | Connected ComfyUI nodes that generate or process content. |
| API-format prompt | JSON that defines the nodes and connections to execute. |
| Node | One workflow step, such as loading a model or saving an image. |
| Job / Job ID | One workflow execution and its lookup ID. |
outputs | Generated files with download URLs and expiration times. One Job can return multiple files. |