PineRouterDocs
GuidesModelsAPI referenceApps

Jobs

One endpoint for every media model, with all of its settings, polling or webhooks, and file uploads.

Images, video, music, speech, avatars, lip sync, upscaling and edits all run through /v1/jobs. You send the model's own fields, as listed on each model's page under Models.

Start a job

curl https://pinerouter.ai/v1/jobs \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"model": "z-image", "input": {"prompt": "A red paper boat on a calm lake", "aspect_ratio": "1:1"}}'
{"code": 200, "msg": "success", "data": {"id": "task_abc123", "status": "queued"}}

Get the result

Either poll the job, or give a webhook_url and we call you when it finishes.

Poll

curl https://pinerouter.ai/v1/jobs/task_abc123 -H "Authorization: Bearer $API_KEY"
{"code": 200, "msg": "success", "data": {"id": "task_abc123", "model": "z-image", "status": "succeeded",
  "outputs": ["https://files.example.com/outputs/abc.png"]}}

data.status is queued, running, succeeded or failed. On succeeded, data.outputs lists the links to your files. On failed, data.error says why; a failed job costs nothing. Poll every 5 to 10 seconds; most images finish in under a minute, videos in a few minutes.

import time, requests

BASE = "https://pinerouter.ai"
H = {"Authorization": "Bearer sk-..."}
job = requests.post(f"{BASE}/v1/jobs", headers=H, json={"model": "z-image", "input": {"prompt": "A red paper boat"}}).json()["data"]
while job["status"] not in ("succeeded", "failed"):
    time.sleep(5)
    job = requests.get(f"{BASE}/v1/jobs/{job['id']}", headers=H).json()["data"]
print(job["outputs"] if job["status"] == "succeeded" else job["error"])

Older field names still work: taskId (same as id), state (waiting, queuing, generating, success, fail), resultJson (a JSON string with resultUrls) and failMsg.

Webhook

Add webhook_url (a public https:// address) to the request. When the job succeeds or fails we POST the same job object to it:

curl https://pinerouter.ai/v1/jobs \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"model": "z-image", "input": {"prompt": "A red paper boat"}, "webhook_url": "https://your.app/hooks/jobs"}'
{"type": "job.succeeded", "timestamp": "2026-11-02T10:00:00Z", "data": {"id": "task_abc123", "status": "succeeded", "outputs": ["https://..."]}}
  • Signed per the Standard Webhooks spec: headers webhook-id, webhook-timestamp and webhook-signature (v1,<base64>). The signature is HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<body> with your API key as the secret.
  • Answer with any 2xx within 15 seconds. Otherwise we retry after 1, 5, 15 and 60 minutes, then stop.
  • Delivery is at least once: use webhook-id to ignore repeats.
  • Private and local addresses are refused.
import base64, hashlib, hmac

def verify(body: bytes, headers, api_key: str) -> bool:
    msg = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.".encode() + body
    sig = base64.b64encode(hmac.new(api_key.encode(), msg, hashlib.sha256).digest()).decode()
    return any(hmac.compare_digest(sig, s.split(",", 1)[1]) for s in headers["webhook-signature"].split())

Inputs

  • input takes the fields on the model's page. Leave a field out to use its default.
  • Files go in as https:// links (for example image_url, image_urls, video_url, audio_url). Use any public link, or upload the file first:
curl https://pinerouter.ai/v1/files -H "Authorization: Bearer $API_KEY" -F "file=@photo.png"
{"id": "6f1c...", "object": "file", "url": "https://files.example.com/uploads/6f1c....png", "bytes": 48213, "content_type": "image/png", "expires_at": 1793680000}

Images up to 10 MB, video and audio up to 50 MB. Uploads are deleted after 1 day.

  • Safety filters are always on.

Cost

The model's page shows its prices by setting. A job reserves its expected cost plus 20% when it starts and is charged the real cost when it finishes; a failed job costs nothing. Result links last 7 days.