SDKs
Pagination
Auto-paginators vs manual cursor walks. Memory considerations for long lists. Side-by-side TypeScript, Python, and Go.
Last updated
TL;DR. Every list endpoint exposes an iterator that walks every page transparently
and a page() (Python) / ListPage (Go) escape hatch for manual cursor control.
Iterators are memory-stable — at most one page is in memory at a time. Default limit
is 10; max is 100.
Matter list endpoints use cursor pagination — limit, starting_after, ending_before.
The response envelope is:
{
"object": "list",
"data": [ /* up to `limit` resources */ ],
"has_more": true,
"url": "/v1/entities",
"next_cursor": "ent_4Kj2m8pQ"
}next_cursor is the ID of the last resource on the page. Pass it as starting_after
on the next request. Walking backward uses ending_before. There is no offset-based
pagination — cursor pagination is the only mode.
Auto-paginators
The default. The SDK walks every page and yields one resource at a time. Memory stays flat regardless of list size.
for await (const entity of matter.entities.list()) {
if (entity.status === "active") {
console.log(entity.id, entity.legal_name);
}
}Filter at the API layer to avoid pulling pages you don't need:
for await (const entity of matter.entities.list({
jurisdiction: "US-DE",
status: "active",
})) {
// ...
}Stop early by returning or breaking:
for await (const e of matter.entities.list()) {
if (e.created < cutoff) break; // SDK stops requesting further pages
}for entity in matter.entities.list():
if entity.status == EntityStatus.ACTIVE:
print(entity.id, entity.legal_name)Filter at the API layer:
for entity in matter.entities.list(jurisdiction="US-DE", status="active"):
...Async client:
async for entity in matter.entities.list(): # AsyncMatter only
...Stop early by breaking:
for e in matter.entities.list():
if e.created < cutoff:
break # SDK stops requesting further pagesfor entity, err := range client.Entities.List(ctx, &matter.EntityListParams{}) {
if err != nil {
return err
}
if entity.Status == matter.EntityStatusActive {
fmt.Println(entity.ID, entity.LegalName)
}
}Filter at the API layer:
params := &matter.EntityListParams{
Jurisdiction: "US-DE",
Status: matter.EntityStatusActive,
}
for entity, err := range client.Entities.List(ctx, params) {
// ...
}Stop early by break or by cancelling the context — both halt the underlying
HTTP requests cleanly.
Manual cursor walks
Use this when you need:
- Backpressure between pages — work on page N before fetching page N+1.
- Resumable cursors persisted to disk or a queue.
- Parallel page fans where multiple workers split the cursor space.
let cursor: string | undefined;
do {
const page = await matter.entities.list({
limit: 100,
startingAfter: cursor,
});
await processInBatch(page.data); // backpressure point
await persistCursor(page.next_cursor);
cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);cursor = None
while True:
page = matter.entities.list(limit=100, starting_after=cursor).page()
process_in_batch(page.data) # backpressure point
persist_cursor(page.next_cursor)
if not page.has_more:
break
cursor = page.next_cursorvar cursor string
for {
page, err := client.Entities.ListPage(ctx, &matter.EntityListParams{
Limit: 100,
StartingAfter: cursor,
})
if err != nil {
return err
}
if err := processInBatch(page.Data); err != nil { // backpressure point
return err
}
if err := persistCursor(page.NextCursor); err != nil {
return err
}
if !page.HasMore {
break
}
cursor = page.NextCursor
}Memory considerations
| Mode | Memory ceiling | When to use |
|---|---|---|
| Auto-paginator | One page (limit items) | Default. Streaming reads, small per-item work. |
Manual page() | One page + your batch buffer | Backpressure, resumable cursors. |
Array.from() over auto-paginator | All items | Small lists only. Don't do this on events or audit_entries. |
The fan-out anti-pattern: collecting an entire list into memory before processing.
Matter has list endpoints that return millions of rows in production accounts
(events, audit_entries, share_ledger). The SDK iterator is the only safe way to
walk them.
Page size
limit defaults to 10 and caps at 100. Pick:
- 10 (default) for interactive flows — dashboards, agent queries, "show me the next 10."
- 100 for batch processing — fewer round trips, better throughput.
- Anywhere in between for your specific latency / memory budget.
The cost on the server side is the same — the limit is yours to set.
Backward walks
ending_before walks from a cursor backward. Useful for "show me the 10 most recent
entities" without first finding the most recent ID:
const recent = await matter.entities.list({ limit: 10, endingBefore: "ent_zzz" });recent = matter.entities.list(limit=10, ending_before="ent_zzz").page()recent, _ := client.Entities.ListPage(ctx, &matter.EntityListParams{
Limit: 10,
EndingBefore: "ent_zzz",
})The default sort order is by created descending — newest first. starting_after
walks toward older records.