Skip to content

Providers — Job protocol

The protocol is pull-based: you call the platform to claim jobs, then report the result. The platform never initiates connections to you.

POST /api/v1/provider/jobs/pull?max_jobs=3
Authorization: Bearer pv_...

max_jobs — how many jobs to claim at once (110000, default 1). Each claimed job is atomically assigned to you: no other provider can claim it.

{
"success": true,
"data": {
"jobs": [
{
"job_id": "9f1c8b1e-7c2a-4a86-9b6e-2c5fa0b7d8e1",
"model": "gemini-2.5-flash-image",
"prompt": "a red panda astronaut floating in a nebula",
"resolution": "1K",
"aspect_ratio": "1:1",
"attachments": [],
"price": 0.08,
"deadline_at": "2026-08-24T12:34:56Z",
"timeout_seconds": 300
}
]
}
}

If there are no queued jobs for your models, the response is {"jobs": []} — poll again later with a reasonable interval (a few seconds).

For image-to-image jobs, attachments contains presigned GET URLs to the input files in our S3. They are valid for ~1 hour — download them immediately after pulling. You need no S3 credentials — the URL is self-contained.

GET <presigned-attachment-url>

Run the generation with your own engine, keys, and proxy fleet. Follow the prompt and model spec. If the client requested an aspect ratio/resolution that you cannot produce, report a failure (see below) rather than returning a malformed image.

You have two options:

Get a presigned PUT URL for your result object from the platform, upload the file, and pass the resulting S3 key back. (The exact endpoint for issuing the presigned PUT is part of your onboarding — the admin provides it together with your credentials.)

If you can host the result yourself (e.g. a public object storage URL), send it directly in the result payload via the url field.

Note Either way, the client downloads the result from the platform, never from you.

POST /api/v1/provider/jobs/9f1c8b1e-7c2a-4a86-9b6e-2c5fa0b7d8e1/result
Authorization: Bearer pv_...
Content-Type: application/json
{
"status": "success",
"result_s3_key": "nb-results/9f1c8b1e-7c2a-4a86-9b6e-2c5fa0b7d8e1.png",
"mime_type": "image/png"
}

or with a direct URL:

{
"status": "success",
"url": "https://cdn.yourco.dev/results/9f1c8b1e...png",
"mime_type": "image/png"
}

Report failures with a stable error_code and a human-readable error_message — they are shown to the client:

{
"status": "failed",
"error_code": "provider_overload",
"error_message": "Gemini quota exhausted on all healthy keys"
}

Recommended error codes:

error_code Meaning
provider_overload Google quota/429/503 — transient, will be retried by the client
empty_response 200 OK but no content (censored/safety block)
provider_error Provider-side failure
invalid_payload Malformed request (bad base64, missing inputs)
timeout Generation exceeded the deadline
  • Submitting a result for an already-terminated job is not an error — the platform returns 200 and ignores the duplicate. Retry safely on network failures.
  • If you do not submit a result before deadline_at, the job is automatically requeued and offered to another provider. A late result after requeue is rejected (404), because the job no longer belongs to you.
  • Claiming jobs you cannot finish (e.g. unsupported model) hurts everyone: if you pull it, complete it or report a failure — don’t let it sit until the deadline.