Async jobs, polling and callbacks
| Mode | How it works |
|---|---|
| Synchronous | Existing /v1/images/generations, /v1/videos/generations and /v1/audio/generations wait for results; requests and responses are unchanged |
| Async polling | Submit with POST /api/v1/jobs/createTask, then query saved results using GET /api/v1/jobs/recordInfo?taskId=... |
| Async callback | Include callBackUrl and callbackSecret when submitting; ModelFold sends a signed success or failure notification. Polling remains available |
Initial support covers image, video and audio models using the unified jobs protocol and having a parameter reference. Chat, Suno and other independent protocols are excluded. Unsupported models return HTTP 400 without charging; use their existing synchronous endpoint. Follow each model's parameter reference, placing native parameters inside input for async requests. Synchronous parameters remain at the top level or in params.
Creation and queries require Authorization: Bearer mf-.... Only the creating API Key can query a job, including when another Key belongs to the same account. JSON bodies are limited to 1 MB; creation and queries share a limit of 120 requests per Key per minute.
Submit a job
curl https://ai.modelfold.online/api/v1/jobs/createTask \
-H "Authorization: Bearer $MODELFOLD_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-20261028-001' \
-d '{"model":"seedream/5-pro-text-to-image","input":{"prompt":"a paper boat in the rain","aspect_ratio":"1:1","quality":"basic"}}'{"code":200,"msg":"success","data":{"taskId":"ecf94083-6e5c-4b3c-b0d1-554d38b3dc20","state":"queued","reused":false}}| Field | Required | Description |
|---|---|---|
model | Yes | ModelFold model ID |
input | Yes | Native parameter object; missing required fields or invalid types return 400. Documented duration/resolution defaults are explicitly applied before submission and quoting |
callBackUrl | No | Public HTTPS destination on port 443, without credentials or fragments. Public IPv4/IPv6 supported; all DNS answers must be public, with private/reserved IPs blocked |
callbackSecret | With callback | Your independently generated 32–256 character signing secret. Stored encrypted and never echoed in responses |
Idempotency-Key header | Recommended | 1–200 characters. Same API Key, idempotency key and request reuse one job without another charge; different parameters return 409. Retained while the job record exists |
Callback fields and output-count overrides such as n, num_images and num_outputs are forbidden inside input. Each request generates the model's default number of outputs. Known conflicting inputs are rejected locally; additional upstream validation may cause an asynchronous failure.
HTTP 200 acknowledges acceptance and a credit reservation, rather than successful generation. After a timeout, retry using the same idempotency key; do not create a replacement request with a new key.
Poll saved results
curl 'https://ai.modelfold.online/api/v1/jobs/recordInfo?taskId=ecf94083-6e5c-4b3c-b0d1-554d38b3dc20' \
-H "Authorization: Bearer $MODELFOLD_API_KEY"{
"code":200,"msg":"success",
"data":{
"taskId":"ecf94083-6e5c-4b3c-b0d1-554d38b3dc20",
"model":"seedream/5-pro-text-to-image","state":"success",
"resultJson":"{\"resultUrls\":[\"https://ai.modelfold.online/uploads/outputs/result.png\"]}",
"failMsg":null,"usage":{"credits":19,"settled":true},
"callback":{"state":"none","attempts":0}
}
}Credits are illustrative; usage.credits is authoritative. Parse the JSON string resultJson to obtain resultUrls, which are absolute download URLs. Structured detection results are available as downloadable JSON files.
| State | Meaning |
|---|---|
queued | Accepted, awaiting upstream submission |
submitting | Submission in progress |
processing | Upstream task ID saved; generation, download or settlement pending |
uncertain | Submission outcome unknown; waiting for an upstream callback. No automatic recreation or refund. Contact support with taskId if this persists; do not resubmit |
success | Results saved and billing settled; read resultJson |
fail | Confirmed failure and reservation refunded; read failMsg. usage.credits is 0 |
Before completion, usage.credits is the reserved amount and settled is false. Afterwards it is the actual ModelFold charge. Poll every 5–10 seconds and stop at success or fail. Queries only read saved data. Async jobs continue reconciliation beyond the synchronous wait timeout; a timeout alone never triggers a refund.
Receive signed callbacks
Add these fields to your submission:
{"callBackUrl":"https://your-service.example/webhooks/modelfold","callbackSecret":"replace-with-your-own-random-secret-at-least-32-characters"}The POST body has the following structure:
{
"eventId":"c783846f-7f2e-4736-9fe3-8524a51836b0",
"event":"task.completed",
"data":{
"taskId":"ecf94083-6e5c-4b3c-b0d1-554d38b3dc20",
"model":"seedream/5-pro-text-to-image","state":"success",
"resultJson":"{\"resultUrls\":[\"https://ai.modelfold.online/uploads/outputs/result.png\"]}",
"failMsg":null,"usage":{"credits":19,"settled":true}
}
}Failures also use task.completed, with state: "fail", null resultJson and a descriptive failMsg. Notifications are queued only after result persistence and billing settlement.
Headers:
X-ModelFold-Event-Id: event ID, matching body.eventId.X-ModelFold-Timestamp: Unix seconds for this delivery attempt.X-ModelFold-Signature:Base64(HMAC-SHA256(callbackSecret, timestamp + "." + eventId + "." + rawRequestBody)).
Verify the original request bytes before JSON parsing, reject timestamps differing by more than five minutes, and deduplicate by event ID. Retries retain the same event ID/body with a fresh timestamp/signature. Use a separate callback secret rather than your API Key.
Node.js verification example, using express.raw to obtain the original bytes:
import crypto from 'node:crypto'
const timestamp = req.get('X-ModelFold-Timestamp') || ''
const eventId = req.get('X-ModelFold-Event-Id') || ''
const received = Buffer.from(req.get('X-ModelFold-Signature') || '', 'base64')
const expected = crypto.createHmac('sha256', process.env.MODELFOLD_CALLBACK_SECRET)
.update(`${timestamp}.${eventId}.`).update(req.body).digest()
const valid = /^\d+$/.test(timestamp) && Math.abs(Date.now()/1000 - Number(timestamp)) <= 300
&& received.length === expected.length && crypto.timingSafeEqual(received, expected)
if (!valid) return res.sendStatus(403)
const event = JSON.parse(req.body.toString('utf8'))
if (event.eventId !== eventId) return res.sendStatus(403)
// Persist and deduplicate by eventId before acknowledging with 2xx; process business logic in the background.Return any HTTP 2xx within ten seconds. Redirects are not followed. Network failures, timeouts and non-2xx responses trigger up to ten total attempts, using exponential backoff starting at 30 seconds and capped at one hour. Retries persist across restarts. Query callback.state (none, pending, delivered, exhausted) and callback.attempts for delivery status. Notification failures never alter task results or billing; polling remains available.
Errors and reliability
Creation/query failures use HTTP status and {"code":400,"msg":"input.prompt is required","data":null}. Authentication retains the existing gateway error structure; rate limits return 429. Common statuses: 400 invalid input, 401 invalid Key, 402 insufficient credits, 404 missing/unowned task, 409 idempotency conflict, 503 unconfigured upstream.
Job creation, credit reservation and ledger insertion share one transaction. Final state, refund/additional charge, results and the customer notification body share another transaction. Upstream callbacks only wake workers; authoritative results are queried upstream. Callback processing and 30-second fallback polling share database mutual exclusion and terminal-state checks, preventing duplicate settlement. Each job uses an independent random upstream callback token; an upstream HMAC signature and timestamp are additionally verified when operations has configured that key. Customers only configure their own callback endpoint.
Existing synchronous APIs and web Playground behavior remain available. See API gateway for synchronous examples.
