AnswerLine Start free

Async tasks and batches

Task lifecycle

StatusMeaning
QUEUEDWaiting to start, for the engine's turn. Not charged; can be cleared. A task that has not started within 72 hours of creation fails, uncharged.
PROCESSINGRunning. Transient failures are retried automatically within five minutes of the first start.
COMPLETEDresponse holds the engine's result. Charged.
FAILEDresponse holds the error. Not charged. This includes a task that never started within 72 hours of creation.

task.latencyMs runs from the first start to the final outcome, excluding the time queued.

Every task finishes within bounds: it has 72 hours from creation to start, and five minutes from its first start to finish. 72 hours is enough for a queue of 100,000 tasks to drain at engine rate limits. A task that waits out the first bound — behind an engine that is unavailable or saturated — moves from QUEUED straight to FAILED with 502 EXTERNAL_SERVICE_ERROR and details.cause QUEUE_WAIT_EXCEEDED, is not charged, is not retried, and has a null latencyMs. Resubmit tasks that failed this way. A task that started but did not finish within its five minutes fails with Maximum retries exceeded, also uncharged.

A start that was interrupted before any work began is taken back: the task returns to QUEUED and starts again as if it had not started, so a poller can see PROCESSING followed by QUEUED. The task keeps the deadline it had before that start. Treat only COMPLETED and FAILED as final.

Creating a task

POST /v1/async/task takes the engine's request as payload and answers at once with the task. Invalid bodies get 422. priority orders your own queue: higher runs first, equal priorities in submission order, and other accounts are unaffected.

FieldTypeDescription
taskType * "AIMODE" | "GOOGLE" | "GOOGLE_NEWS" | "GEMINI" | "CHATGPT" | "COPILOT" | "PERPLEXITY" | "GROK" AI provider that should handle this task.
payload * object Request body specific to the provider. Needs at least `prompt` (`query` for Google Search).
priority integer Priority of the task, from 1 to 10; tasks with higher numbers run first. Default: 1.
idempotencyKey string Distinct string that stops the same task from being created twice. No two tasks in your account may share it.
webhook object Settings for the webhook that reports a finished task.
webhook.url * string Endpoint that gets the webhook POST request once the task finishes.

Batches

POST /v1/async/task/batch takes 1 to 500 tasks. Each is validated and admitted on its own, and results answers each by its index in input order. Only an overflowing queue rejects the whole batch (429 QUEUE_LIMIT_EXCEEDED). The SDKs refuse a batch outside that size before sending it.

curl

curl -X POST "https://api.answerline.dev/v1/async/task/batch" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '[{"taskType":"CHATGPT","payload":{"prompt":"iPhone 17 or Galaxy S26?","country":"US"},"idempotencyKey":"run-0-US-2026-09-15"},{"taskType":"CHATGPT","payload":{"prompt":"iPhone 17 or Galaxy S26?","country":"GB"},"priority":5,"idempotencyKey":"run-0-GB-2026-09-15"}]'

TypeScript

const prompts = ["iPhone 17 or Galaxy S26?", "Best wireless earbuds under $150"];
const tasks = prompts.flatMap((prompt, i) =>
  ["US", "GB"].map((country) => ({
    taskType: "CHATGPT" as const,
    payload: { prompt, country },
    idempotencyKey: `run-${i}-${country}-2026-09-15`,
  })),
);

const batch = await client.createBatch(tasks);
for (const item of batch.results) {
  if (!item.success) console.warn(tasks[item.index].idempotencyKey, item.error.code);
}

Python

prompts = ["iPhone 17 or Galaxy S26?", "Best wireless earbuds under $150"]
tasks = [
    {"taskType": "CHATGPT", "payload": {"prompt": p, "country": c}, "idempotencyKey": f"run-{i}-{c}-2026-09-15"}
    for i, p in enumerate(prompts)
    for c in ("US", "GB")
]

batch = client.create_batch(tasks)
failed = [r for r in batch["results"] if not r["success"]]

Response

{
  "success": true,
  "summary": {
    "total": 2,
    "succeeded": 1,
    "failed": 1
  },
  "results": [
    {
      "success": true,
      "index": 0,
      "task": {
        "id": "b27a21e1-7c39-4aa2-a347-23e828c426f9",
        "taskType": "CHATGPT",
        "status": "QUEUED",
        "priority": 1,
        "createdAt": "2026-09-15T09:00:00.000Z",
        "latencyMs": null,
        "idempotencyKey": "run-0-US-2026-09-15"
      },
      "credits": {
        "creditsToCharge": 6.2,
        "creditsCharged": null
      }
    },
    {
      "success": false,
      "index": 1,
      "error": {
        "code": "RESOURCE_ALREADY_EXISTS",
        "message": "…",
        "timestamp": "2026-09-15T09:00:00.000Z"
      }
    }
  ]
}

Item errors are VALIDATION_ERROR, RESOURCE_ALREADY_EXISTS and INSUFFICIENT_CREDITS.

Getting results

GET /v1/async/task/{id} returns task, credits and, once the task has finished, response. waitForTask / wait_for_task poll it with jittered backoff from 500 ms up to 10 seconds between polls (intervalMs / interval) for up to 11 minutes (timeoutMs / timeout). For many tasks, add a webhook instead and let results come to you; see webhooks.

A finished task stays readable for at least four days after it was created. Store what you need from a result rather than treating the API as your archive.

GET /v1/async/tasks lists your tasks newest first, a page of summaries at a time: status filters by QUEUED, PROCESSING, COMPLETED or FAILED, limit sets the page size (50, at most 100), and a full page's nextBefore is the next page's before. The SDKs do not wrap it yet — call it directly.

Idempotency keys

A task that ends FAILED keeps its key for 24 hours after it failed, then releases it: resubmitting the same key before that answers 409, and after it creates a new task under the key. The key of a task that succeeded, or is still queued or running, stays bound while the task is readable.

An idempotencyKey is unique across your account. Creating a task with a key in use answers 409 RESOURCE_CONFLICT, or RESOURCE_ALREADY_EXISTS for a batch item, and creates nothing. Derive keys from what the task means, such as prompt, market and day, never at random per attempt: then retrying a submission after a timeout can never run a task twice. A request whose every task is refused, for example for INSUFFICIENT_CREDITS, creates nothing and claims no key, neither its tasks' idempotencyKey nor an Idempotency-Key header, so the same key can be sent again, with any body, once you have topped up.

Managing the queue

GET /v1/async/status reports queuedTasks, processingTasks, queued counts per priority and concurrency in use. DELETE /v1/async/queue deletes every task still QUEUED, which were never charged, leaves running and finished tasks alone, and returns how many it removed; it is safe to repeat.

curl

curl -X GET "https://api.answerline.dev/v1/async/status" \
  -H "Authorization: Bearer $API_KEY"

curl -X DELETE "https://api.answerline.dev/v1/async/queue" \
  -H "Authorization: Bearer $API_KEY"

TypeScript

const status = await client.asyncStatus();
const { cleared } = await client.clearQueue();

Python

status = client.async_status()
cleared = client.clear_queue()["cleared"]

Sync or async

A synchronous call is one request that waits up to five minutes and costs the synchronous surcharge. Set the HTTP client's read timeout above that. If the wait runs out first, the error's details.taskId names the submitted task — fetch its outcome with GET /v1/async/task/{id} instead of submitting again and paying twice. A task costs the base price, waits in the queue instead of failing, and can be batched. Use synchronous calls for interactive work and tasks for anything scheduled or large.