Skip to content

Queue API & webhooks

Every model can be called asynchronously: submit, get a request_id back immediately, then poll, stream or wait for a webhook. The body is the same JSON as the synchronous endpoint for that model, so the model reference applies unchanged. Billing happens on the gateway with your key exactly as for direct calls.

1 · Submit

curl
curl -X POST https://api.tokport.io/queue/v1/gpt-image-2 \
  -H "Authorization: Bearer $TOKPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a neon koi swimming through clouds", "size": "1024x1024", "n": 1}'

# -> {
#   "status": "IN_QUEUE",
#   "request_id": "req_6ec4eb7af6889e6c0bc5b274",
#   "queue_position": 0,
#   "response_url": "https://api.tokport.io/queue/v1/gpt-image-2/requests/req_…",
#   "status_url":   "https://api.tokport.io/queue/v1/gpt-image-2/requests/req_…/status",
#   "cancel_url":   "https://api.tokport.io/queue/v1/gpt-image-2/requests/req_…/cancel"
# }
OptionWhereMeaning
task?task=image-to-imageForce the task kind. Inferred from the body otherwise (an image field means image-to-image / image-to-video).
webhook?webhook=https://… or X-TokPort-WebhookPublic http(s) URL to POST the result to when the request finishes.
priorityX-TokPort-Priority: lowLow-priority requests run only when no normal ones are waiting.
limits12 MB body; 50 pending requests per key (429 beyond). Results are kept for 24 hours.

2 · Status

curl
curl https://api.tokport.io/queue/v1/gpt-image-2/requests/req_…/status?logs=1 \
  -H "Authorization: Bearer $TOKPORT_API_KEY"
# -> { "status": "IN_PROGRESS" | "IN_QUEUE" | "COMPLETED" | "FAILED" | "CANCELLED",
#      "request_id": "req_…", "queue_position": 2, "gateway_request_id": "2026…",
#      "logs": [{"timestamp": "…", "message": "Started"}],
#      "metrics": {"inference_time": 0.82} }      # metrics on COMPLETED, error on FAILED

gateway_request_id is the id in your request history and on the billing row.

3 · Stream status (SSE)

curl
curl -N https://api.tokport.io/queue/v1/gpt-image-2/requests/req_…/status/stream \
  -H "Authorization: Bearer $TOKPORT_API_KEY"
data: {"status":"IN_QUEUE","queue_position":0,…}
data: {"status":"IN_PROGRESS",…}
data: {"status":"COMPLETED","metrics":{"inference_time":0.82},…}   # stream closes
javascript
const res = await fetch(`https://api.tokport.io/queue/v1/gpt-image-2/requests/${id}/status/stream`, {
  headers: { Authorization: `Bearer ${process.env.TOKPORT_API_KEY}` },
});
for await (const chunk of res.body.pipeThrough(new TextDecoderStream())) {
  for (const line of chunk.split("\n")) if (line.startsWith("data: ")) console.log(JSON.parse(line.slice(6)));
}

4 · Result

curl
# waits up to 60 s for completion; 202 with the status if still running
curl "https://api.tokport.io/queue/v1/gpt-image-2/requests/req_…?timeout=60" \
  -H "Authorization: Bearer $TOKPORT_API_KEY"
# image models  -> the OpenAI-shaped response: {"created": …, "data": [{"url": …}], "request_id": "req_…"}
# video models  -> {"request_id": "req_…", "task_id": "task_…", "status": "completed",
#                   "video": {"url": "https://…/content?access=…", "content_type": "video/mp4"}, "artifacts": […]}

FAILED requests answer 422 with {"error": {"message", "code"}} and are not billed; CANCELLED and expired results answer 410. Video URLs carry an access token and can be downloaded without the API key.

5 · Cancel

curl
curl -X PUT https://api.tokport.io/queue/v1/gpt-image-2/requests/req_…/cancel \
  -H "Authorization: Bearer $TOKPORT_API_KEY"
# 202 {"status": "CANCELLATION_REQUESTED"}   while queued (dropped, never billed)
# 400 {"status": "ALREADY_IN_PROGRESS"}      running on the gateway, cannot be stopped
# 400 {"status": "ALREADY_COMPLETED"}

Webhooks

When a request with a webhook finishes, we POST once per attempt, retrying after 15 s, 1 min, 5 min, 30 min and 2 h on anything but a 2xx. The host must resolve to a public address; redirects are not followed.

json — payload
{
  "request_id": "req_6ec4eb7af6889e6c0bc5b274",
  "gateway_request_id": "202610090941004635192198268d9d60bhWr1Mf",
  "status": "OK",                       // or "ERROR" with an "error": {"message", "code"} object
  "payload": { "created": 1791536460, "data": [{ "url": "https://…" }], "request_id": "req_…" }
}
http — headers
Content-Type: application/json
X-TokPort-Webhook-Request-Id: req_6ec4eb7af6889e6c0bc5b274
X-TokPort-Webhook-Timestamp: 1791536461
X-TokPort-Webhook-Signature: <hex HMAC-SHA256 of "<timestamp>.<raw body>" with your key's signing secret>

Each API key has its own signing secret (whsec_…), shown on the API keys page. Verify with the raw request body and reject timestamps older than five minutes:

javascript — verify (Node)
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyTokPortWebhook(rawBody, headers, secret) {
  const ts = headers["x-tokport-webhook-timestamp"];
  const sig = headers["x-tokport-webhook-signature"];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return expected.length === sig.length && timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(sig, "hex"));
}
python — verify
import hmac, hashlib, time

def verify_tokport_webhook(raw_body: bytes, headers: dict, secret: str) -> bool:
    ts = headers["x-tokport-webhook-timestamp"]
    if abs(time.time() - int(ts)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, headers["x-tokport-webhook-signature"])

Sync or queue?

The synchronous /v1 endpoints stay the simplest path for one-off image calls. Use the queue when you want progress and logs, when the client should not hold a connection open for minutes (video), when you need webhooks, or when you want to batch work at low priority. The full contract is in the OpenAPI document.