Cloudstation

Tasks & Events

Track task status, reconcile from the durable event log, apply idempotency keys, and stay within rate limits.

Every accepted send creates a task. This page covers the operational contract that sits underneath every send: task status, the durable event log, idempotency, and rate limits.

Tasks

Every send returns a task_id in its 202 response. Use it to check progress or cancel a run in progress.

Get a task

GET /v1/projects/{project_id}/tasks/{task_id}
{
  "id": "b3f1c9a2-4e5d-4f6a-8b7c-9d0e1f2a3b4c",
  "project_id": "prj_6efcfced-a176-4e4c-a989-9767c16fddf7",
  "conversation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "adw_id": "abc12345",
  "agent_id": "a1b2c3d4-...",
  "status": "completed",
  "created_at": "2026-07-01T12:00:00Z",
  "started_at": "2026-07-01T12:00:01Z",
  "finished_at": "2026-07-01T12:00:14Z"
}
StatusMeaning
queuedAccepted, not yet started.
runningThe agent is actively processing the message.
waiting_inputTask is paused pending additional input.
completedFinished successfully; the reply has been delivered/queued for delivery.
failedFinished with an error; see error (present only when set).
canceledCanceled via the cancel endpoint before finishing.
timed_outDid not finish within the allowed processing time.

List tasks for a conversation

GET /v1/projects/{project_id}/conversations/{conversation_id}/tasks

Returns {"tasks": [...]}, newest first, with task objects in the same shape as above. ?limit= defaults to 50 (max 200).

Cancel a running task

POST /v1/projects/{project_id}/tasks/{task_id}/cancel
  • Success: 200 with the updated task object (status: "canceled").
  • 409 if the task has already finished (completed, failed, canceled, or timed_out).

Event Log and Reconciliation

Every task change (task.created, task.running, task.completed, task.failed, task.canceled, ...) is recorded in a durable, per-project event log, and so is every assistant reply sent to a webhook (message.assistant, written only when the conversation or project has a response webhook). Webhooks are a convenience layer over this log, not the source of truth.

GET /v1/projects/{project_id}/api/events?since_id=&limit=
{
  "events": [
    {
      "id": "01J8X9K3QYRZTS9V0N7M6P5C2A",
      "type": "message.assistant",
      "sequence": 42,
      "payload": { "conversation_id": "telegram-123456789", "message": { "content": "..." } }
    }
  ],
  "has_more": false
}
  • id is a ULID, sortable by creation order.
  • type values include message.assistant, task.created, task.completed, and other lifecycle events.
  • sequence is a per-conversation monotonically increasing counter.

Webhooks are a convenience over this log. After downtime or a missed delivery, catch up by polling /api/events from your last processed event id.

Idempotency

Send an idempotency key

Include an Idempotency-Key header with any caller-generated unique string, or rely on external_message_id in the request body, which also works as an implicit idempotency key when no Idempotency-Key header is sent.

curl -X POST 'https://charlie-back.cloud-station.io/v1/projects/PROJECT_ID/conversations/my-conversation-1/messages' \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'x-client-id: YOUR_CLIENT_ID' \
  -H 'Idempotency-Key: telegram:123456789:42' \
  -d '{
    "message": "Can you help me choose the right plan?"
  }'

Replays return the original response

Replaying the same key returns the original 202 response byte-identically and never runs the agent a second time.

In-flight duplicates get a 409

If the original request for that key is still in flight (processing), a duplicate send returns 409.

Keys expire after 24 hours

After 24 hours, a previously used key is treated as new.

Retrying consumers — Telegram, n8n, Zapier, and any other at-least-once delivery system — must send an idempotency key. Without one, a retried delivery can create a duplicate agent run.

Rate Limits

  • Default 20 sends/minute per conversation.
  • Default 120 sends/minute per project.
  • Over the limit: 429 with a Retry-After header in seconds.

Back off and honor Retry-After rather than retrying immediately.

Next Steps