taskId, and the request runs in the background — like a tracking number rather than waiting at the counter. Submit tasks one at a time or in batches of up to 500.
Step 1: make an API request
payload takes the same parameters as the provider’s sync request. Include a webhook.url and we notify you when the job is done; omit it and you poll for the result.
Identifying your requests with Generate unique keys using UUIDs, timestamps, or a combination of user ID + timestamp to ensure no duplicates.Key retention: a key stays bound to its task until the task record is deleted, whether it ended
idempotencyKeyYou can optionally include an idempotencyKey in your request. This is a unique string you create that allows you to easily identify and reference your requests in your own system.The idempotencyKey must be a unique string across your entire account. If you submit a request with an idempotency key that has already been used, the API returns 409:COMPLETED or FAILED. To retry a failed task before then, submit it with a new key.Serverless crash recovery: GET /v1/async/task/{taskId} finds a task by its taskId only, not by its idempotencyKey. Store the taskId as soon as you receive the submit response. If your serverless function (Vercel, Lambda) crashes before it saves the taskId, a resubmit with the same idempotencyKey returns 409 and does not create a second task, so you are not charged twice. Set a webhook.url when you submit: the webhook payload carries task.id and task.idempotencyKey, so you still receive the result.Understanding task states
QUEUED: received and waiting its turn, in priority order. A task waiting to retry a failed attempt is back inQUEUED.PROCESSING: a worker is running the task.COMPLETED: cloro got a result, returned inresponse. That result can be the provider’s own error or refusal text (details).FAILED: no attempt returned a result, or the scheduler rejected the task when it reached it (invalid payload or insufficient credits).responsecarries the error.
QUEUED. Poll the task status endpoint to follow the transitions; a webhook fires only once the task is COMPLETED or FAILED.Finished tasks are deleted about 24 hours after you create them, and HTML URLs in a result expire after 24 hours on their own clock.Step 2: receive the results
Two ways to retrieve a result. Either way, a finished task carriestask.latencyMs — the milliseconds from first pickup to the final outcome, excluding the initial queue wait. It is null while the task is QUEUED or PROCESSING, and stays null on a task that failed before processing ever started. On a retried task it covers every attempt, including the backoff between them, so it can be far longer than one provider call.
Option A: webhooks (recommended)
If you provided awebhook.url in your request, we will send an HTTP POST to that URL containing the full result as soon as the task reaches a terminal state.
See Webhooks for the payload shape, retry behavior, signature verification, and troubleshooting.
Option B: polling
Without a webhook URL,GET the task status endpoint with your taskId. Once the task completes, the response carries the full result.
Understanding limits
Task submission limits
Your concurrency limit never blocks submission. Besides the per-second rate limit every endpoint has, two limits can:- Credit limit: your balance must cover the task’s
creditsToCharge— see what happens when credits run out. - Queue limit: your organization can hold up to 100,000
QUEUEDtasks, counting tasks waiting to retry but notPROCESSINGones. Past that, a submit returns429 QUEUE_LIMIT_EXCEEDED, and a batch that would overflow the queue is rejected whole. Contact support if you need it raised.
Task processing limits
The scheduler picks up queued tasks in priority order, and this is where your plan’s concurrency limit applies: if your plan allows 10 concurrent requests, up to 10 of your tasks run in parallel. Sync requests draw on the same slots, so sync traffic in flight leaves fewer for your tasks.Request prioritization
Setpriority from 1 to 10 when you submit. Higher numbers run first, and tasks with the same priority run in the order they were received. Omitting it leaves the task at 1, so existing integrations are unaffected. Monitor how your queue is distributed across levels via the async status endpoint.
For practical concurrency patterns and examples, see our
concurrency documentation.
Common questions
How do I cancel pending async tasks?
Individual queued tasks cannot be canceled bytaskId — once a task enters the QUEUED state, it stays there until it is either processed (COMPLETED / FAILED) or removed by a queue-wide clear.
To wipe every pending task in one call, use the DELETE /v1/async/queue endpoint. It removes every QUEUED task for your organization (tasks already PROCESSING keep running) and returns the number cleared:
cURL
How long do async tasks stay in the queue?
Tasks stay in the queue until they are processed, resulting in eitherCOMPLETED or FAILED status. There is no fixed time-to-result. How quickly a task completes depends on:
- Queue depth: how many of your tasks are ahead of it in priority order.
- Concurrency limit: your plan’s limit caps how many of your tasks process in parallel, so a deep queue drains at most that many tasks at a time. See Rate & concurrency limits.
- Provider processing time: once a task is picked up, the time to the final outcome is reported in
task.latencyMs. Queue wait is excluded from that number.
How do I track the credits consumed by each task?
Both the polling response and the webhook payload include acredits object:
creditsToCharge— the estimated cost, computed at submission; nothing is reservedcreditsCharged—nulluntil the task finishes, then the amount actually billed (0for aFAILEDtask)
creditsCharged from the terminal state to attribute cost per job. How billing works covers what is charged.
What happens to async tasks when my credits run out?
Credits are checked twice, and the balance is never reserved in advance:- At submission.
POST /v1/async/taskrejects with403 INSUFFICIENT_CREDITSwhen your balance does not cover that task’s cost.POST /v1/async/task/batchinstead returns200with a per-taskINSUFFICIENT_CREDITSerror for each task that doesn’t fit, so a partially affordable batch is partially accepted. - At scheduling. Tasks already sitting in
QUEUEDare re-checked when the scheduler picks them up. A task that no longer fits the balance moves toFAILEDwithcreditsCharged: 0and anINSUFFICIENT_CREDITSerror — it is not held, retried, or resumed when you top up. Re-submit those tasks after topping up.
GET /v1/credits reflects completed charges only — it does not net out creditsToCharge for work still queued or processing. To decide whether a submission will fit, subtract your own outstanding creditsToCharge from remaining rather than trusting remaining alone.
A task came back COMPLETED but the result looks like an upstream error. What happened?
COMPLETED means cloro got a result from the provider, and that result can be the provider’s own error or refusal text; it is charged like any other result. Errors cloro recognizes are retried instead, and end in FAILED only if every attempt fails. See empty responses from AI providers to detect and retry the rest.
Does async cost more credits than sync?
No — async is 2 credits cheaper per request. The base cost and feature add-ons are identical whether you use synchronous or async delivery, but sync monitor requests (/v1/monitor/*) carry a +2 credit surcharge that async and batch requests (/v1/async/*) do not. The creditsCharged field in the terminal task state shows the actual amount billed for each task.
The async endpoint feels slow today. What should I check?
CallGET /v1/async/status to see your account’s queue depth and concurrency usage. If the queue is deep and concurrency is saturated, throughput is plan-bound — see concurrency for how to raise it.