Skip to main content
See Making requests for an overview of both request modes. Think of an asynchronous (async) request like sending a package with a tracking number. Instead of waiting at the post office until the package is delivered (a synchronous process), you hand it over, get a taskId (your tracking number), and are free to do other things. You can then either receive a delivery notification (a webhook) or check the tracking status online yourself (polling). Our async API works the same way. You submit a task, we give you a taskId, and we process your request in the background. You can submit tasks one at a time or in batches of up to 500.

When to use async requests

This approach works well when:
  • Your application, especially if it’s running in a serverless environment, has short execution time limits.
  • You want to submit a large number of requests quickly without waiting for each one to complete. The batch endpoint lets you submit up to 500 tasks in a single request.
  • You need to build a more resilient system that isn’t dependent on a single, long-running connection.

How it works: a two-step process

The process involves two steps: making a request and then fetching the result.

Step 1: make an API request

First, you send an API request. If you include a webhook.url, we’ll notify you when the job is done. If you omit it, you will need to poll for the result.
curl -X POST "https://api.cloro.dev/v1/async/task" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "taskType": "CHATGPT",
    "priority": 5,
    "idempotencyKey": "your-custom-identifier-123",
    "webhook": {
      "url": "https://your-app.com/webhook-handler"
    },
    "payload": {
      "prompt": "What is the weather in New York?",
      "country": "US"
    }
  }'
Identifying your requests with 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 will return an error:
{
  "success": false,
  "error": {
    "code": "RESOURCE_ALREADY_EXISTS",
    "message": "Task already exists",
    "details": {
      "field": "idempotencyKey",
      "value": "abc12312233123441234451112322212222223"
    },
    "timestamp": "2025-12-10T11:41:40.243Z"
  }
}
Generate unique keys using UUIDs, timestamps, or a combination of user ID + timestamp to ensure no duplicates.
We’ll acknowledge your request and provide a taskId.
{
  "success": true,
  "task": {
    "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9",
    "taskType": "CHATGPT",
    "status": "QUEUED",
    "priority": 5,
    "createdAt": "2025-11-10T15:00:00.000Z",
    "idempotencyKey": "your-custom-identifier-123"
  },
  "credits": {
    "creditsToCharge": 10,
    "creditsCharged": 0
  }
}
Now, you can store this taskId and wait for the results.
Understanding task statesEvery async task goes through four possible states:
  • QUEUED: The request is received and waiting its turn to be processed. Tasks are processed by priority first, then in FIFO (first-in, first-out) order within the same priority level.
  • PROCESSING: The request is actively being processed by our system. The AI provider is generating your response.
  • COMPLETED: The request finished successfully. The final response is included in the same payload.
  • FAILED: The request failed to complete. This can happen because of rate limits, provider errors, or invalid input.
The initial response always shows status as QUEUED. You can track state transitions by polling the task status endpoint or receiving webhook updates.COMPLETED and FAILED tasks are stored in our system for 24 hours after completion. During this time, you can retrieve the full results using the task ID. After 24 hours, the task record and its associated response data are permanently deleted from our system.HTML URLs included in responses expire after 24 hours from generation, regardless of the task’s retention status.

Step 2: receive the results

You have two options for retrieving the results of your task. If you provided a webhook.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

If you don’t provide a webhook URL, you can periodically check for the result yourself. This is called “polling.” You can make a GET request to our Get task status endpoint using the taskId you received in Step 1. Once the task is complete, the response will contain the full result.
curl -X GET "https://api.cloro.dev/v1/async/task/YOUR_TASK_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

Understanding limits

The asynchronous system handles limits in two steps.

Task submission limits

When you submit a task, we only check two things:
  • Credit Limit: We verify you have enough credits for the task.
  • Queue Limit: Your organization can have a maximum of 100,000 tasks waiting in the queue. If you exceed this, you will receive a 429 Too Many Requests error. Please contact our team if you need this limit increased.

Task processing limits

Once your task is in the queue, our scheduler picks it up for processing. This is where your subscription’s concurrency limit is enforced. For example, if your plan allows 10 concurrent requests, our scheduler will process up to 10 of your tasks in parallel. Tasks are processed by priority first, then in the order they were received within the same priority level.

Request prioritization

You can assign a priority to async tasks using the priority field when creating a task. Priority is an integer from 1 (lowest, default) to 10 (highest).
ParameterTypeRequiredDefaultDescription
priorityintegerNo1Task priority level (1-10). Higher numbers are processed first.
Higher-priority tasks are processed before lower-priority ones within your organization’s queue. Tasks at the same priority level are processed in FIFO (first-in, first-out) order. If you don’t specify a priority, tasks default to 1, the same behavior as before this feature was added. Existing integrations are unaffected. You can monitor how your queue is distributed across priority levels using the async status endpoint.
For practical concurrency patterns and examples, see our concurrency documentation.

Common questions

How do I cancel pending async tasks?

There is no way to cancel queued async tasks. Please be careful when posting tasks in production. Once a task is submitted and enters the QUEUED state, it will remain in the queue until it’s processed (resulting in either COMPLETED or FAILED status). Best practices to avoid unwanted tasks:
  • Test with small batches first
  • Use unique idempotencyKey values to prevent duplicate submissions
  • Implement safeguards in your submission logic
  • Monitor your queue depth via the async status endpoint

What’s the maximum queue depth?

Queue depth is limited to 100,000 tasks per organization. If you exceed this limit, you’ll receive a 429 Too Many Requests error when trying to submit additional tasks. If you need a larger queue for your use case, please contact our team.

How long do async tasks stay in the queue?

Tasks stay in the queue until they are processed, resulting in either COMPLETED or FAILED status. Check your current queue status using the async status endpoint.

How do I track the credits consumed by each task?

Both the polling response and the webhook payload include a credits object:
  • creditsToCharge — the estimated cost shown while the task is QUEUED or PROCESSING
  • creditsCharged — the actual amount billed once the task reaches COMPLETED or FAILED
Use creditsCharged from the terminal state to attribute cost per job. Failed tasks may still incur credits depending on how far processing got. Sync requests canceled by the client are also charged for work already done; async tasks cannot be canceled once queued.

A task came back COMPLETED but the result looks like an upstream error. What happened?

COMPLETED means cloro finished its work and returned what the upstream provider gave us. If the provider returned an error page, a captcha, or a truncated answer, that detail lives inside the response payload. Inspect the response body — FAILED is reserved for cases where cloro could not produce a result at all (network errors, internal exceptions, repeated upstream timeouts).

The async endpoint feels slow today. What should I check?

Call GET /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.