Skip to main content
ModuleX list endpoints return one page at a time. There is no single pagination style across the API — different resources use different schemes, and the response field you read to fetch the next page depends on which scheme the endpoint uses. This page documents all three styles, the exact query parameters and response fields for each list endpoint, and the SDK helpers that page automatically so you do not have to loop by hand. Every list call is authenticated the same way as any other request: 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_more avoids 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_at timestamp, so inserting a new row while you page does not shift or duplicate items the way a moving offset can. 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, 1100. 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).
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

Increment page until you reach total_pages.

Auto-paginating with the SDKs

Both SDKs expose a generator that walks every page for you, fixing page_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.
The id field on each run record is what you pass to GET /workflow-runs/{run_pk}. It is not the same as the per-execution run_id used by GET /workflows/listen/{run_id} and POST /workflows/cancel/{run_id}. Passing a run_id to the by-id endpoint returns 404. ModuleX uses “run id” in three distinct senses — see Workflows & runs.

Request parameters

integer
default:"50"
Page size, 1100. 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 provides executions.iter_runs, which walks every page using has_more and yields typed WorkflowRunListItem records. Its internal page size defaults to 50.
There is no JavaScript equivalent of iter_runs. The JS SDK’s only auto-paginator is workflows.listAll. For run history in JavaScript, loop client.workflowRuns.list({ offset }) on has_more as shown above. This gap is tracked in the SDK parity matrix.

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, 1100. 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.
cursor must be a valid ISO 8601 timestamp. A malformed value returns 400 with {"detail": "cursor must be an ISO timestamp"}. Always source the cursor from a prior response’s next_cursor rather than constructing it yourself.

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, passing next_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.
The cursor lists (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

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).
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.
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).
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.
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.
The JS SDK converts your request parameters from camelCase to snake_case (pageSizepage_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: 401AuthenticationError, 403PermissionError, 404NotFoundError, 422ValidationError, and 429RateLimitError (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.