Authorization: Bearer mx_live_… plus X-Organization-ID. See Authentication for the full model. All list endpoints in this reference are read-only GET requests, which means the SDKs will safely retry them on transient failures (429/500/502/503) — see SDK errors & retries.
The three pagination styles
Responses are snake_case on the wire for both SDKs. The JavaScript SDK converts your request parameters from camelCase to snake_case (so you pass
pageSize and it sends page_size), but it does not convert responses back — you read response.total_pages, response.has_more, and response.next_cursor exactly as the API returns them. The Python SDK is snake_case in both directions.There is no generic, top-level
{ items, total, … } envelope shared by every endpoint. Each list nests its rows under a resource-specific key (workflows, runs, or items) and exposes only the “more?” signal that fits its style. The JavaScript SDK ships a PaginatedList<T> interface whose fields are all optional precisely because it is a superset across these styles — concrete list responses use their own narrower types, not PaginatedList directly.Choosing a style as a caller
- Offset / page lets you jump to an arbitrary page and shows a total count and page count — good for “page 3 of 12” UIs, at the cost of an extra
COUNT(*)on the server. - Offset / limit with
has_moreavoids the count query and is cheaper for deep history; you cannot show a total, only “there is another page.” - Cursor is the most stable under concurrent writes: rows are keyed by their
updated_attimestamp, so inserting a new row while you page does not shift or duplicate items the way a movingoffsetcan. Cursor lists are user-scoped and always newest-first.
Offset / page — GET /workflows
GET /workflows lists the workflows in your organization. It is the only list that exposes a total count and page count, and it does so only when you supply page_size. With no pagination parameters, it returns all matching workflows in a single response plus total — there is no implicit default page size.
This is also the only endpoint with an SDK auto-paginator in both clients (JavaScript workflows.listAll, Python workflows.list_all).
Request parameters
integer
1-based page number. Must be
>= 1. Optional. When omitted but page_size is sent, the server defaults to page 1.integer
Items per page,
1–100. Optional. Pagination metadata (page, page_size, total_pages) is included in the response only when this parameter is present. Omit both page and page_size to receive every matching workflow at once. In the JavaScript SDK this parameter is pageSize (camelCase) and is sent as page_size.string
Filter by lifecycle status (for example
active, draft, published). Optional.string
Filter by workflow category. Optional.
string
Filter by sharing visibility — one of
private, organization, public, or system. Optional. New workflows default to organization, and private is no longer creator-only (it behaves like organization).string
Free-text search over workflow name/description. Optional.
Response
WorkflowSummary[]
The page of workflow summaries. Rows are nested under
workflows, not a generic items key.integer
The total number of workflows matching the filters across all pages. Always present, regardless of whether pagination parameters were sent.
integer
The current 1-based page. Present only when
page_size was supplied in the request.integer
The page size echoed back. Present only when
page_size was supplied.integer
ceil(total / page_size). Present only when page_size was supplied. This is the field auto-paginators read to know when to stop.Paging by hand
Incrementpage until you reach total_pages.
Auto-paginating with the SDKs
Both SDKs expose a generator that walks every page for you, fixingpage_size at 100 internally and yielding one workflow at a time. The JavaScript method is workflows.listAll; the Python method is workflows.list_all. Pass the same filters as list, minus page/page_size (the helper controls those).
There is no cURL equivalent for
listAll / list_all. Auto-pagination is an SDK convenience that loops the underlying GET /workflows calls; with raw HTTP you page manually as shown above.Offset / limit — GET /workflow-runs
GET /workflow-runs returns durable run history for the organization, newest first. It is preview-optimized (it omits the heavy input/output snapshot columns) and deliberately does not return a total count — paging uses a has_more boolean instead, so the server never runs a COUNT(*) over potentially huge history. Advance by adding limit to offset until has_more is false.
Request parameters
integer
default:"50"
Page size,
1–100. Optional, defaults to 50.integer
default:"0"
Number of rows to skip from the start of the (newest-first) result set. Must be
>= 0. Optional, defaults to 0.string
Filter to a single workflow’s run history (UUID). Optional. In the JavaScript SDK this is
workflowId.string
Filter by durable run status (for example
succeeded, failed, running). Optional.string
Filter by how the run was triggered (for example
api, schedule, chat). Optional. In the JavaScript SDK this is triggerType.Response
WorkflowRunListItem[]
The page of run-history rows, newest first. Rows are nested under
runs.boolean
true when another page exists at offset + limit. The server determines this without a count query (it fetches one row beyond the page). Stop when this is false.integer
The page size echoed back.
integer
The offset echoed back.
Paging by hand
Run history is grouped differently in the two SDKs. The Python SDK folds it into
client.executions (list_runs for one page, iter_runs to auto-paginate). The JavaScript SDK exposes a separate client.workflowRuns resource (list / get) and has no auto-paginator for runs — loop manually. Both call the same GET /workflow-runs routes.Auto-paginating runs (Python only)
The Python SDK providesexecutions.iter_runs, which walks every page using has_more and yields typed WorkflowRunListItem records. Its internal page size defaults to 50.
Cursor — GET /composer/chats and GET /assistant/chats
The AI Composer and Assistant chat lists are user-scoped (they return the calling user’s own chats) and use cursor pagination keyed on each row’s updated_at timestamp, newest first. You pass the previous page’s last updated_at back as the cursor to fetch the next page; the server returns next_cursor, which is null once there are no more pages. Both endpoints have an identical contract.
Because the cursor is an updated_at timestamp rather than a moving offset, inserting or updating chats while you page does not skip or duplicate rows the way offset can.
Request parameters
integer
default:"20"
Page size,
1–100. Optional, defaults to 20.string
An ISO 8601 timestamp — the
updated_at of the last item on the previous page. Omit it to fetch the first (newest) page. Optional.Response
ChatSummary[]
The page of chat summaries, newest first. Rows are nested under
items (this is the one list family that uses the generic items key). The Composer list adds a workflow_id field on each row; the Assistant list does not.string | null
The cursor for the next page — the
updated_at of the last item — or null when there are no more pages. The server returns a non-null next_cursor only when the page came back full (exactly limit rows), so a short final page yields null.Paging by hand
Loop, passingnext_cursor back as cursor, until next_cursor is null. Neither SDK ships an auto-paginator for these lists, so you loop manually in every language.
Swap
assistant for composer to page Composer chats — client.composer.list(...) takes the same limit and cursor and returns the same { items, next_cursor } shape (plus a workflow_id per row).SDK auto-paginator reference
Only three SDK methods auto-paginate. Everything else is a single page you loop yourself.In Python,
list_all and iter_runs return an AsyncPage[T] — a lazy async-iterable that validates each row into the typed Pydantic model as you iterate. You consume it with async for; it fetches pages on demand, not all at once. In JavaScript, listAll is a plain AsyncGenerator you consume with for await.composer.list, assistant.list) and the JavaScript runs list (workflowRuns.list) return a single page; you loop them by hand using the snippets above. There is no plan to wrap the cursor lists in an auto-paginator at present. The full route-to-method mapping, including these pagination gaps, is in the SDK ⇄ API parity matrix.
Edge cases and gotchas
Why doesn't GET /workflows return total_pages?
Why doesn't GET /workflows return total_pages?
It does — but only when you send
page_size. With no pagination parameters, GET /workflows returns every matching workflow plus total, and omits page, page_size, and total_pages entirely. If your code reads response.total_pages unconditionally, send page_size or fall back to Math.ceil(total / pageSize) (which is exactly what the JS listAll helper does).GET /workflow-runs has no total — how do I show a count?
GET /workflow-runs has no total — how do I show a count?
You can’t get an exact count from this endpoint; it intentionally trades the count query for cheaper deep paging. Use
has_more to drive a “Load more” affordance instead of “page N of M”. If you need an authoritative total, that signal is not exposed here.My cursor request returns 400.
My cursor request returns 400.
cursor must be a valid ISO 8601 timestamp. The response is {"detail": "cursor must be an ISO timestamp"}. Always reuse a prior response’s next_cursor verbatim; do not hand-build or truncate it. When sending it over raw HTTP, URL-encode it (the + and : characters in a timestamp must be percent-encoded).Items shift or duplicate while I page through history.
Items shift or duplicate while I page through history.
This is inherent to
offset-based paging (GET /workflow-runs) when rows are inserted during iteration — a newer run pushes everything down by one, so a fixed offset can re-show or skip a row. Cursor pagination (GET /composer/chats, GET /assistant/chats) avoids this because the cursor pins to an updated_at timestamp rather than a positional offset. If stable iteration over runs matters, filter to a closed set (for example a single workflow_id plus a terminal status) so new runs don’t enter the window.Do list calls hit the billing gate?
Do list calls hit the billing gate?
No. The flat
DenialEnvelope (402/403/429) is returned only by the billing-gated surfaces — run, composer, assistant, and managed knowledge. The list endpoints documented here are plain reads; on error they return the standard FastAPI {"detail": "<message>"} shape, or a header-based 429 if you exceed the request rate limit. See Errors & status codes and Rate limiting.JavaScript: why do I read response.total_pages and not response.totalPages?
JavaScript: why do I read response.total_pages and not response.totalPages?
The JS SDK converts your request parameters from camelCase to snake_case (
pageSize → page_size) but leaves responses in their original snake_case. So you pass pageSize/workflowId/triggerType in, and read total, total_pages, has_more, and next_cursor out. The Python SDK is snake_case in both directions.Errors on list endpoints
List endpoints use the standard FastAPI error envelope{"detail": "<message>"}.
The SDKs map these to typed errors:
401 → AuthenticationError, 403 → PermissionError, 404 → NotFoundError, 422 → ValidationError, and 429 → RateLimitError (which exposes retryAfter/retry_after, limit, remaining, and reset from the headers). The read-only list calls are auto-retried on 429/500/502/503 with backoff that honors Retry-After. See SDK errors & retries for the complete error class hierarchy and retry policy.
Next steps
SDKs overview
The JavaScript and Python clients, unified by operation, with install and configuration.
SDK ⇄ API parity matrix
Every REST route mapped to its JS and Python methods, with pagination and other gaps called out.
Errors & status codes
The three error-envelope shapes and which surface emits each.
Rate limiting
Per-key and per-user limits, the
429 response, and the rate-limit headers.