Skip to content
API guide

Requests

View as Markdown Updated

A request is one call of one model. It has an id (req_…), a status, its cost in API credits (1000 = 1 USD), and, when it succeeds, the generated files. The same object comes back from every endpoint below, for every model.

Find a model

The catalog of all 1,496 models is public JSON (no key needed):

terminal
curl "https://api.doitong.com/v1/models?category=text-to-video&lab=Google&limit=20"
Query parameter Meaning
q Words that must all appear in the name, id, maker or tags, for example kling 3.
category One category id, for example text-to-video or upscale. GET https://api.doitong.com/v1/categories lists all 21 with their counts.
lab The model's maker, by name or id, for example Google or black-forest-labs.
family A family id from a previous row: every variant of one model (pro, fast, image-to-video, …).
mode exact (the price is computed from the input), measured (from the length or size of the input files), estimate (an estimated price per run) or metered (charged by actual usage, up to a maximum per run).
limit, cursor Page size (1 to 100, default 50) and the next_cursor of the previous page.

Each row has id, name, lab, category, family, summary, tags, output_kind, docs_url and a price object (see Pricing). GET https://api.doitong.com/v1/models/{model_id} adds the full input and output JSON Schema, the canonical parameter names, the price rules and an example input. An unknown category or mode is 400 invalid_parameter.

Run a model

terminal
curl -X POST "https://api.doitong.com/v1/run/{model_id}?wait=20" \
  -H "Authorization: Bearer $DOITONG_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1842-cover" \
  -d '{"prompt": "..."}'
  • The body is the model input, a JSON object. Each model page lists its parameters.
  • The input is checked before anything is charged. An invalid input returns 422 invalid_input with the offending param, and costs nothing.
  • ?wait=N (up to 20 seconds) holds the call until the request finishes or the time is up.
  • ?webhook_url=https://… sends a signed POST to that URL when the request finishes (see Webhooks).
  • ?storage=permanent or ?storage=temporary chooses how long the output files are kept; without it the account setting applies (see Where files live).

The response is 202 Accepted while the request is still working, and 200 OK when it finished within the wait (or when it is a replay, see below). The Location and Doitong-Request-Id headers carry the request URL and id.

Statuses

Request lifecycle
  1. queued Accepted and charged, waiting for a worker
  2. running The model is generating
  3. Final statuses:
    • succeededCharged the quote; outputs[] hold URLs on our storage
    • failedRefunded automatically; error says why
    • canceledStopped before a result; refunded
Status Meaning Final
queued Accepted and charged, waiting for a worker. queue_position may show the place in line. no
running The model is generating. no
succeeded Done. outputs lists the files. yes
failed The model or a limit stopped it. error says why. The API credits are refunded. yes
canceled Canceled before it produced a result. The API credits are refunded. yes

A request that runs longer than its time limit fails with code timeout and is refunded: 15 minutes for images, 60 minutes for video, 30 minutes for audio and everything else.

The request object

JSON
{
  "id": "req_8k2m4q7x1z9c3v5b6n0p2r4t",
  "object": "request",
  "model": "flux/schnell",
  "status": "succeeded",
  "created_at": "2026-10-20T12:00:00Z",
  "started_at": "2026-10-20T12:00:01Z",
  "completed_at": "2026-10-20T12:00:02Z",
  "queue_position": null,
  "cost": { "credits": 3, "usd": "0.003", "charged": 3, "refunded": 0 },
  "input": { "prompt": "..." },
  "output": { "images": [ { "url": "https://content.doitong.com/dev-api/…/0-output.jpeg", "width": 1024, "height": 768 } ] },
  "outputs": [
    { "type": "image", "url": "https://content.doitong.com/dev-api/…/0-output.jpeg", "content_type": "image/jpeg",
      "width": 1024, "height": 768, "file_size": 183920, "stored": true }
  ],
  "error": null,
  "metrics": { "inference_seconds": 1.2 },
  "storage": { "mode": "permanent", "bytes": 183920, "expires_at": null, "expired": false },
  "urls": {
    "self": "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t",
    "cancel": "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/cancel"
  }
}
  • outputs is the same for every model: one entry per generated file with type (image, video, audio, file or 3d), a url on our storage, content_type and, when known, file_size, width, height, duration.
  • output is the model's own result object (its fields are listed on each model page) with the same URLs.
  • storage says how the files are kept: mode (permanent or temporary), bytes stored, expires_at (when a temporary file is deleted, null for permanent ones) and expired (true once the files are gone).
  • cost.credits is the price of the call in API credits (cost.usd in USD), cost.charged what was debited from the API balance, cost.refunded what came back. For a metered model cost.credits is the maximum held at the start (cost.held); once the run succeeds cost.settled says what the usage came to and the rest of the maximum is in cost.refunded (see Charged by usage).
  • When a request fails, error is { "type": …, "code": …, "message": … }.

Read a request

terminal
curl "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t?wait=20" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
  • ?wait=N (up to 20 seconds) long-polls: the call returns as soon as the request is final, or after N seconds with the current state. Looping on this is the simplest way to wait for a result.
  • ?logs=1 adds the model's progress log lines (the last 50) as logs.

You can only read requests of your own account; any other id is 404 request_not_found.

List requests

terminal
curl "https://api.doitong.com/v1/requests?limit=20&status=succeeded&model=flux/schnell" \
  -H "Authorization: Bearer $DOITONG_API_KEY"

Newest first. limit is 1 to 100 (default 20), status and model filter. The response is { "object": "list", "data": [ … ], "has_more": true, "next_cursor": "req_…" }; pass cursor=<next_cursor> for the next page.

Cancel a request

terminal
curl -X POST "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/cancel" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
  • Canceled before the model started: the status becomes canceled, the API credits are refunded, and you get 200.
  • Already generating: the cancel is passed on and you get 202 with "cancel_requested": true. If the model finishes anyway, the request succeeds normally and is charged.
  • Already final: 409 not_cancelable (a request that is already canceled simply returns 200).

Delete the files of a request

terminal
curl -X DELETE "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/files" \
  -H "Authorization: Bearer $DOITONG_API_KEY"

Deletes our copies of the request's output files now and stops their storage charge. The response is the request object with storage.expired: true; its URLs stay in the record but no longer work. Calling it again changes nothing (200). A request that is still queued or running has no files yet: 409 request_active. Needs models:run.

Idempotency: retry without paying twice

Networks fail. To retry a run safely, send an Idempotency-Key header (any string up to 255 characters, unique per operation, for example your own order id):

  • Same key and the same body as before: you get the original request back (200, header Idempotent-Replayed: true), and nothing is charged again.
  • Same key and body after a run that failed before it reached the model (upstream_error or internal_error on submit, insufficient_credits, budget_exceeded, too_many_inflight): nothing was produced and nothing is charged, so the retry runs the model again instead of returning the failure.
  • Same key with a different body: 409 idempotency_mismatch.
  • Keys are scoped to the API key that sent them.

Where files live

Every output file is copied to our storage before the request is marked succeeded, so the URLs in outputs are on our own hosts and you can link to them directly. How long they stay is your choice:

Mode How long Price
permanent (default) Until you delete them 100 API credits ($0.10) per GB per month, charged daily from the API balance
temporary 7 days, then deleted Free
  • The account setting decides for new requests: the "Keep files forever" switch in the dashboard, or PATCH https://api.doitong.com/v1/account/storage with {"mode": "temporary"}. GET https://api.doitong.com/v1/account shows it under storage, with the bytes you keep and the monthly estimate.
  • ?storage=permanent|temporary on a run (the storage argument of the MCP run tool) overrides it for that run.
  • Changing the setting never touches files you already have; delete those with DELETE /requests/{id}/files.
  • When the API balance cannot pay for storage, nothing is charged and you get an email: top up within 30 days, or the kept files become temporary and are deleted 7 days later.

In the rare case that our copy fails, the entry has "stored": false and the URL is the original, short-lived one: download it soon.