Conversations and messages

Developer·3 minutes to read

Verified against docs/superpowers/specs/2026-09-15-p4-a-typed-http-api-openapi-webhooks.md §4.3, §4.4, §5.1, §5.2, §6; re-verified against the code at close-out.

What it does

The conversations the key's person is in, and the messages inside one: read a thread, page through it, and read the cards each message carries (F-CONV-1 to F-CONV-12 — the sidebar row, the composer, the append-only transcript, the card catalogue, the five-part result, and the per-message request id).

Authentication

An organisation API key of scope read or wider. There is no write on this page: a message is sent by starting a run (Runs). Conversations are read under the key's person, and the product shows a person only their own: a group conversation they are not in is not_found here, exactly as it is absent there. See Authentication and keys.

Request

GET /conversations

<!-- generated:begin GET /conversations --> <!-- generated:end GET /conversations -->

GET /conversations/{id}

<!-- generated:begin GET /conversations/{id} --> <!-- generated:end GET /conversations/{id} -->

GET /conversations/{id}/messages

<!-- generated:begin GET /conversations/{id}/messages --> <!-- generated:end GET /conversations/{id}/messages -->

GET /conversations takes an optional botId and lists the person's own direct and group conversations. GET /conversations/{id}/messages takes limit and cursor like every list, and also afterSeq: pass the sequence number you last read and the page starts after it, oldest first. That is the shape to poll a thread with.

Message bodies are the app's own redacted projections. This API adds no unredacted reader, and the run ledger — the hash-chained record of model and tool calls (F-RUN-3) — is not exposed here at all.

curl -sS "$BOTSEON_API_ORIGIN/api/v1/conversations?limit=20" \
  -H "authorization: Bearer $BOTSEON_API_KEY" \
  -H "accept: application/json"
const origin = process.env.BOTSEON_API_ORIGIN;
const auth = { authorization: `Bearer ${process.env.BOTSEON_API_KEY}`, accept: 'application/json' };
const list = await fetch(`${origin}/api/v1/conversations?limit=1`, { headers: auth });
if (!list.ok) throw new Error(`conversations: ${list.status}`);
const page = (await list.json()) as { data: Array<{ id: string; title: string | null }> };
const first = page.data[0];
if (first) {
  const messages = await fetch(
    `${origin}/api/v1/conversations/${first.id}/messages?afterSeq=0&limit=50`,
    { headers: auth },
  );
  if (!messages.ok) throw new Error(`messages: ${messages.status}`);
  const thread = (await messages.json()) as { data: Array<{ seq: number; role: string }> };
  for (const message of thread.data) console.log(message.seq, message.role);
}

Response

{
  "data": [
    {
      "id": "6b7c8d9e-0f1a-4b2c-8d3e-4f5a6b7c8d9e",
      "seq": 41,
      "role": "member",
      "kind": "message",
      "body": { "cards": [{ "kind": "message", "text": "Here are the release notes." }] },
      "requestId": "…",
      "authorUserId": "8c1d4e5f-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
      "botId": null,
      "createdAt": "2026-09-18T08:00:00.000Z"
    }
  ],
  "nextCursor": null
}

seq is the message's place in the thread and is what afterSeq takes. requestId is the id the app shows beside a message, and quoting it in a support request is what makes an answer possible.

Errors

StatusCodeWhenWhat to do
401unauthenticatedThe credential is missing, refused, or below read.Check the key.
404not_foundNo such conversation, or the key's person is not in it.List first; a group the person is not a member of never appears.
400invalid_requestlimit, cursor or afterSeq is outside its bounds.limit is 1 to 200; afterSeq is a non-negative integer.
429rate_limitedA bucket is full.Wait for Retry-After.

Rate limits

Every call on this page is counted against two one-minute buckets: 300 requests per key and 1 000 per organisation. A full bucket answers rate_limited with Retry-After: 60; wait that many seconds and retry. Failed authentication is counted separately, against the caller's address, so a working key's allowance is never spent by somebody else's bad credential. See Envelopes, errors, idempotency and paging.

Last verified against build c0f77aa.