Skip to main content
This guide covers the basics of making requests to the cloro API, including synchronous and asynchronous requests.

Synchronous Requests

cloro API monitoring endpoints follow a mostly consistent request and response structure, with the Google Search endpoint being the main exception.

Request structure

Most monitoring endpoints follow a consistent request structure:
{
  "prompt": "Your query here (1-10,000 characters)",
  "country": "US", // Optional, defaults to "US"
  "include": {
    // Optional, varies by endpoint
    "markdown": false,
    "html": false
  }
}

Common parameters

Most monitoring endpoints share these core parameters:
ParameterTypeRequiredDescription
promptstringYesThe query to send to the AI provider (1-10,000 characters)
countrystringNoISO 3166-1 alpha-2 country code for localized results. Defaults to "US"
includeobjectNoOptional flags for additional response formats
Google Search endpoint exceptionThe Google Search endpoint uses query instead of prompt as the required parameter, since it’s designed for search queries rather than AI prompts. See the Google Search endpoint documentation for details.

Country codes

The API supports country-specific monitoring. Common examples are US (United States) and GB (United Kingdom). For a complete list, see the Countries endpoint.

Response structure

All successful responses follow this base structure:
{
  "success": true,
  "result": {
    "text": "AI response text",
    "sources": [...],
    "html": "https://storage.cloro.dev/results/c45a5081-808d-4ed3-9c86-e4baf16c8ab8/page-1.html",  // Only included when "include.html": true, expires after 24 hours
    // Additional fields vary by endpoint
  }
}

Common response fields

FieldTypeDescription
successbooleanAlways true for successful responses
result.textstringThe AI provider’s response text
result.sourcesarrayArray of sources referenced in the response
result.htmlstringA URL to the full HTML of the response. (included when include.html is true, expires after 24 hours)

Sources array structure

Each source in the sources array follows this format:
{
  "position": 1,
  "url": "https://example.com/article",
  "label": "Example Site",
  "description": "Description of the source content"
}

Optional response formats

Most endpoints support additional response formats through the include parameter:

HTML format

  • Parameter: include.html
  • Type: boolean
  • Default: false
  • Description: Request a URL to the full HTML of the response. The URL expires after 24 hours.
  • Cost: Varies by endpoint (usually +0 credits)

Markdown format

  • Parameter: include.markdown
  • Type: boolean
  • Default: false
  • Description: Include response formatted in Markdown
  • Cost: Varies by endpoint (usually +0 credits)

Endpoint-specific formats

Different endpoints offer unique formats. See individual endpoint documentation for specific options and features available for each monitoring endpoint. See Pricing for endpoint costs and credit information.

Request examples

curl -X POST "https://api.cloro.dev/v1/monitor/chatgpt" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type": application/json" \
  -d '{
    "prompt": "What do you know about Acme Corp?",
    "country": "US",
    "include": {
      "markdown": true,
      "html": true
    }
  }'
import requests

response = requests.post(
    "https://api.cloro.dev/v1/monitor/chatgpt",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json"
    },
    json={
        "prompt": "What do you know about Acme Corp?",
        "country": "US",
        "include": {"markdown": True, "html": True}
    }
)

result = response.json()
print(result['result']['text'])
print(result['result']['sources'])
if 'html' in result['result']:
    print(result['result']['html'])
if 'markdown' in result['result']:
    print(result['result']['markdown'])
const response = await fetch("https://api.cloro.dev/v1/monitor/chatgpt", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    prompt: "What do you know about Acme Corp?",
    country: "US",
    include: { markdown: true, html: true },
  }),
});

const data = await response.json();
console.log(data.result.text);
console.log(data.result.sources);
if (data.result.html) {
  console.log(data.result.html);
}
if (data.result.markdown) {
  console.log(data.result.markdown);
}
Remember: These examples use prompt, which is correct for most endpoints. For the Google Search endpoint, replace "prompt" with "query" in your requests.

Asynchronous Requests

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.

When to use async requests

This approach is ideal 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.
  • 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 entire process involves two simple 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",
    "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",
    "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 queued in FIFO (first-in, first-out) order.
  • 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 due to various reasons like 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.
Option A: Webhooks (Recommended)
A webhook is an automated message sent from our servers to yours when your task is complete. If you provided a webhook.url in your request, we will send an HTTP POST request to that URL containing the full result.
{
  "task": {
    "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9",
    "taskType": "CHATGPT",
    "status": "COMPLETED",
    "createdAt": "2025-11-10T15:00:00.000Z",
    "idempotencyKey": "your-custom-identifier-123"
  },
  "credits": {
    "creditsToCharge": 10,
    "creditsCharged": 10
  },
  "response": {
    "model": "gpt-5-mini",
    "text": "The weather in New York is currently sunny...",
    "html": "https://storage.cloro.dev/results/c45a5081-808d-4ed3-9c86-e4baf16c8ab8/page-1.html",  // Expires after 24 hours
    "sources": [],
    "shoppingCards": [],
    "entities": [],
    "markdown": "The weather in New York is currently sunny...",
    "searchQueries": ["weather in New York"]
  }
}
Responding to a webhook To let us know you’ve received the webhook, your server must respond with a 2xx status code (e.g., 200 OK). If we don’t receive a successful response, we’ll try again. We will attempt to deliver a webhook up to 5 times with an exponential backoff strategy. If an attempt fails, the next one will be scheduled for:
  • Attempt 2: ~2 minutes later
  • Attempt 3: ~4 minutes later
  • Attempt 4: ~8 minutes later
  • Attempt 5: ~16 minutes later
// Example: A simple server endpoint to receive the webhook
app.post('/webhook-handler', (req, res) => {
  // It's best to process the result asynchronously
  console.log(req.body);

// Immediately send a 200 OK to acknowledge receipt
res.status(200).send();
});

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 has a two-step process for handling limits to provide both flexibility and fairness.

1. 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.

2. 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 in the order they were received (FIFO).
For practical concurrency patterns and examples, see our concurrency documentation.