UniRouteUniRoute

Media billing, recovery, and errors

Understand USD quotes and charges, shared key limits, unknown task states, and API errors.

Quotes, wallet funds, and key limits

First call POST /v1/media/quote. Check currency: "USD", policy_version: "usd-wallet-v1", and the exact string quoted_usd, then pass that amount as max_cost_usd. The quote is for the entire task. A catalog unit price only explains the rate; videos may be billed per second.

The account's USD wallet pays the actual charge. The key's spending limits provide additional budget controls. LLM, image, video, and music requests share the same key's total and time-window limits. Unsettled tasks also reserve available budget. Even if the key has no total limit, actual usage still has to be paid from the account wallet.

A task's billing includes quoted_usd, held_usd, charged_usd, released_usd, and state. A hold is not an actual charge, and a release is not a new top-up. Reconcile the final cost using charged_usd and the actual console bill.

StateNext step
heldBudget is reserved. Continue querying the same task.
settlingSettlement is in progress. Continue querying; do not create a replacement task.
chargedCost settlement is complete; the final charge may be zero. Download once generation has also succeeded.
releasedThe hold was released. Check generation status and billing, then stop this example.
released_unknownThe wallet hold may be released, but the upstream result and final cost remain unknown. Keep the task ID and account for the outstanding budget obligation; query or contact support.

Generation success and settlement charged are separate conditions. Generation success alone does not establish that the full workflow is complete. Charges confirmed later may appear after an unknown state.

A successfully settled zero-cost task still uses billing.state: "charged", with charged_usd equal to zero, held_usd equal to zero, and the entire reserved amount recorded in released_usd. Only a trusted completed result and verified zero-cost settlement acknowledgment establish this outcome. Missing or unknown usage is not evidence of a zero charge. The existing success-and-settlement checks remain applicable; no separate download state is required.

Query actual spending

GET /v1/usage returns raw JSON without the media APIs' data wrapper. Only relevant fields are shown below; the response may include additional fields.

Example for a key with a total limit:

{
  "mode": "quota_limited",
  "quota": {
    "limit": 1,
    "used": 0.1396,
    "remaining": 0.8604
  }
}

Example for a key without a total limit:

{
  "mode": "unrestricted",
  "usage": {
    "total": {
      "cost": 0.1,
      "actual_cost": 0.15
    }
  }
}

For actual spending, read quota.used when present; for a key without a total limit, read usage.total.actual_cost. A key with only a time-window limit may also be quota_limited without quota; use actual_cost in that case. usage.total.cost is a reference amount before rate adjustments, not the actual charge. The legacy balance field is not the account's USD wallet balance. Check wallet funds in the console.

Save usage before and after the run, then compare the change with the sum of each task's charged_usd. Other concurrent LLM or media calls on the same key are included in the usage change. Isolate the run or reconcile by task ID; do not assume the entire change belongs to these tasks.

Idempotency and recovery after interruption

Save the request body and Idempotency-Key before creating a task. Save the task ID immediately after receiving HTTP 202. Replaying with the same key, the same idempotency key, and the identical request body should return the same ID. Changing the prompt, option, or max_cost_usd creates a different request body.

If you know the ID, resume with GET /v1/media/tasks/{task_id}; do not create again. If the POST result is unknown and you have no ID, keep the original request and idempotency key. Query this key's history with GET /v1/media/tasks?limit=20 and paginate with next_cursor, or contact support to confirm acceptance. Do not automatically retry an unknown POST, delete the state file, or guess by recreating with a new idempotency key. The standalone client persists sent before the network request and saves the ID immediately after receiving it. After an unknown result, restarting stops before any API call.

Rate limits and errors

Each key can make up to 120 media requests per minute, including catalog, quote, task query, and history requests. Creation POSTs are limited to 10 per minute, including idempotent replays. The maximum number of unfinished tasks is 10 per key and 30 per user. The client creates tasks sequentially, spacing ordinary requests by at least 0.7 seconds, creation and replay requests by at least 7 seconds, and polling by 5 seconds.

HTTPCommon cause and action
400Invalid request fields, option, or cost ceiling. Check the current quote and exact USD string.
401The key is invalid, expired, or revoked. Check the original key without exposing it in logs.
403The key type or restrictions do not meet requirements, or content moderation rejected the request. Check the unified key and input.
404The task does not exist or belongs to another key. Use the key that created it.
402 / 403Insufficient wallet funds or key allowance. Check actual console funds and key limits.
409Idempotency body conflict or concurrency limit. Keep the original task; do not blindly recreate it.
429Too many requests. Follow Retry-After and reduce frequency. Do not automatically resend a creation request whose result is unknown.
503The service or settlement capability is unavailable. Save state; continue querying known IDs, and confirm an unknown POST's result first.

Media errors return an HTTP status and {code,message,reason,data:null}. Keep the status code, task ID, and original idempotency key so support can investigate. Do not publish your key or private upstream details. Public downloads are completely separate requests: do not send a Bearer token or allow a downgrade to HTTP.