SuperCoolDocs Get an API key

Guides

Getting results

Follow a SuperCool message to its result. Long-poll GET /v1/messages/{id}, read events or use webhooks, and handle every status, reason and job.

View as Markdown

POST /v1/messages returns within about 30 seconds. If the agent finished in that time, you already have the result. If it started longer work, the message is processing and you follow it until it's final.

There are three ways to follow a message. Use whichever fits your stack:

How Best for
Long-poll GET /v1/messages/{id}?wait=30 in a loop Scripts and simple servers
Events GET /v1/messages/{id}/events?cursor=… Showing progress step by step
Webhooks SuperCool POSTs to your URL Servers that shouldn't poll

They all read the same record, so they never disagree.

#Long-poll the message

bash
curl "https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d?wait=30" \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY"

wait holds the request for up to that many seconds (0 to 45) and returns as soon as something changes: the status, the agent's reply arriving, or any job's status. It also returns right away when the message is already final. Without wait, you get the current state immediately.

python
import os
import requests

API = "https://api.supercool.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"}

def wait_for(message_id):
    while True:
        r = requests.get(f"{API}/messages/{message_id}", headers=HEADERS, params={"wait": 30}, timeout=60)
        r.raise_for_status()
        msg = r.json()
        if msg["final"] or msg.get("resume") == "add_credits":
            return msg

Set your HTTP client's timeout above wait (60 seconds for wait=30 is a good default). Long-polls count toward a limit of 1,500 waits per hour per account, which a loop like this stays well under.

#The message object

Field What it tells you
id The message id, msg_….
status Where the message stands overall. See Statuses.
reason Why, for blocked, failed, partial and expired. See Reasons.
final true when nothing about the message will change any more. Stop polling.
reply What the agent said, once it has answered.
question For needs_input, the agent's question.
jobs Each piece of work the message started, with its own status and files. See Jobs.
files Every file the message produced, from the reply and from all jobs.
notes Problems with attachments you sent, like a URL that couldn't be fetched.
credits_used Credits this message's jobs used so far.
resume, resume_by Set when queued work is waiting for credits. See Waiting for credits.
retry_after_seconds Set when the agent was busy. See agent_busy.
running Set when work couldn't start because 5 jobs were already running: the running jobs, each with job_id, message_id and work_id.
created_at, updated_at Timestamps (UTC, ISO 8601).

The full schema is in the reference.

#Statuses

A message's status sums up all of its work. It is not just "has the agent replied": the agent often replies in seconds ("On it") while the video it started takes minutes.

Status Meaning Final? What to do
processing The agent is still thinking, or work is running. No Keep polling, or wait for a webhook.
completed Everything finished. Also used for a plain reply that started no work. Yes Read reply and download files.
needs_input The agent asked a question and nothing is running. Yes Answer with a new message.
blocked Nothing could run, usually because of credits. Usually See reason. If resume is set, add credits and the work runs by itself.
partial Some work finished, some didn't. Usually Use the finished files; check each job's reason.
failed Nothing finished. Usually Check reason. Retry or rephrase.
expired Work outlived its watch without reporting back. Yes Recover it, or read the work.

Rely on final, not on the status alone. A blocked or partial message whose queued work is waiting for credits is not final yet: it can still move to processing and then completed.

#How the status is worked out

The status comes from structured signals: the agent's actions and each job's outcome, never from the wording of the reply. The rules apply in this order, and the first match wins:

  1. The agent is still planning or starting work: processing. A quick job finishing while the agent sets up the next one can't end the message early.
  2. The agent's turn failed, or the agent was busy with another message: failed, even if some jobs finished.
  3. Any job is running: processing.
  4. The agent asked a question and nothing is running: needs_input.
  5. No jobs, just a reply: completed.
  6. Every job completed: completed.
  7. Every job blocked: blocked.
  8. Every job expired: expired.
  9. At least one job completed and at least one didn't: partial.
  10. Otherwise: failed.

So if a message asked for two things and the second was refused for lack of credits, it ends partial, with one completed job and one blocked job.

#Reasons

reason says why a message or job didn't simply complete. For a message, it's the reason of the first job that explains the status.

Reason On Meaning What to do
out_of_credits job, message No credits to start the work, or it ran out of credits mid-run. Add credits. If resume is set, the queued work resumes by itself. Otherwise send a new message (like "continue").
hosting_fees job, message Hosting fees for your sites are due, so new work can't start. Settle them in the app, then send the message again.
too_many_running job, message The agent tried to start new work while 5 jobs were already running on your account. The message lists them in running. Wait for one to finish, then send the message again. See At capacity.
queue_expired job, message Queued work waited more than 24 hours for credits and was dropped. Send the message again.
watch_expired job, message Running work didn't report back within 7 days. Recover it or read the work.
result_unknown job, message A recovered job's chat is idle and no result came back for it. Open the work to check, or send a follow-up.
execution_failed job, message The work ran and failed. Read the job's chat (link), then retry or rephrase.
stopped job, message The work was stopped (for example, from the app). Send a new message if you still want it.
agent_busy message Your agent was busy with another message, so nothing ran. Send the same request again with the same Idempotency-Key after retry_after_seconds (15).
turn_failed message The agent's turn failed before it finished. Other values here, like interrupted or abandoned, give more detail. Send the message again. Anything that already started is still listed in jobs.

Treat unknown reasons like failed: new, more specific reasons may be added.

#Jobs

Each job is one piece of work: one run in one chat. A message that asked for a video and a landing page usually has two jobs.

Field Meaning
job_id exe_… for work that ran or is queued; ref_… for work refused before it was queued.
kind execution (real work) or refusal (refused before anything was queued).
role execution, or follow_up when the message joined work that was already running.
status running, completed, failed, cancelled, blocked or expired.
reason Why, for blocked, failed, cancelled and expired.
final false while running, and for queued work that is waiting for credits.
queued, resume_by Set for queued work waiting for credits.
work_id, link The chat the work runs in, and its URL in the app.
title A short name for the work.
files The files this job made.
started_at, settled_at When it started running and when it finished.

#Waiting for credits

If work was queued but your account had no credits, the job is blocked with reason: "out_of_credits", queued: true and final: false. The message then carries:

json
{
  "status": "blocked",
  "reason": "out_of_credits",
  "final": false,
  "resume": "add_credits",
  "resume_by": "2026-10-01T14:02:11.402000Z"
}

Add credits before resume_by (24 hours after the work was queued) and the work starts by itself (once fewer than 5 jobs are running on your account). The job moves to running and then to its result, and the message follows (blocked → processing → completed). Don't resend the message, or you'll start the work twice. After resume_by, the job becomes expired with queue_expired, and you have to send the message again.

#Events

GET /v1/messages/{id}/events returns the message's update log: what happened, in order. It holds only this message's entries, plus those of any work it joined as a follow-up (so a follow-up sees that work's progress and result). If another message works separately in the same chat, its progress and results don't show up here. Use it to show progress ("Rendering the video…"), or to process each job's result as it lands.

bash
curl "https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/events?wait=30" \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY"
json
{
  "object": "list",
  "data": [
    { "seq": 117, "type": "agent_reply", "message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d", "text": "On it…", "files": [], "delivered_sync": true, "at": "2026-09-30T14:02:12.000000Z" },
    { "seq": 118, "type": "progress", "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "step": "Rendering the video", "status": "started", "file_names": [], "at": "2026-09-30T14:03:02.000000Z" },
    { "seq": 121, "type": "result", "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "title": "Candle shop vertical ad", "text": "Your 15 second ad is ready.", "outcome": "completed", "files": [], "at": "2026-09-30T14:06:40.000000Z" }
  ],
  "has_more": false,
  "done": true,
  "cursor": "eyJjIjoiYXBpOnU..."
}
  • Pass cursor from the previous page to get only new entries. With no cursor, the log starts where the message started.
  • wait (0 to 45) holds the call until there's something new. It always waits at least one second.
  • has_more: true means more entries are ready: call again right away.
  • done: true means the message has nothing more to report.
  • Only one wait per message runs at a time. A second concurrent call gets 409 poll_in_progress.
  • Entries are kept for 7 days. An older cursor gets cursor_expired: true and a fresh cursor. Read the message itself to catch up.

Every entry has seq, type, at and, when known, work_id, message_id and job_id. Progress and result entries carry message_id and job_id whenever the job is known.

Type Meaning Extra fields
agent_reply The agent's reply. delivered_sync: true if it was already in the POST response. text, files
progress A step started, or files landed in the chat. step, status, file_names
result A job finished. outcome is completed, failed or stalled_credits. title, text, outcome, files
state A job's state changed: resumed, stopped, expired or unknown. state, text
turn_failed The agent's turn failed. text, reason
turn_busy The agent was busy and the message didn't run. text, reason

#Webhooks

Rather than polling, register a URL and SuperCool POSTs a signed event when a job changes and when the message reaches a status other than processing. See Webhooks.

#Recover old work

A message or job is expired when its work didn't report back in time (7 days for running work). The work may still have finished. POST /v1/messages/{id}/recover looks for its result again. It never resends the message or starts new work.

bash
curl -X POST https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/recover \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY"
json
{
  "id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d",
  "object": "recovery",
  "work": [{ "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "state": "recovering" }]
}

Each piece of work comes back settled (it already has its outcome), watching (still being watched), recovering (being looked up again: follow the message as usual) or exhausted (recovered too many times). For exhausted work, read the chat with GET /v1/work/{work_id}.

#How long messages are kept

A message, its jobs and its results stay readable for at least 14 days after its last job's deadline. For running work the deadline is 7 days after it started; for queued work, 24 hours after it was queued; a plain reply is kept 14 days. After that, GET /v1/messages/{id} returns 404 not_found.

The work itself lasts as long as the chat exists in your account: read it any time with GET /v1/work/{work_id}, and get fresh file links from GET /v1/files/{file_id}.

Last updated