Listing & pagination
Every list endpoint in the Pagr API — templates, template versions, and documents — shares one contract: the same query parameters in, the same envelope out. Learn it once and it applies everywhere.
Query parameters
Section titled “Query parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
skip |
integer | 0 |
Records to skip. Offset-based, not cursor-based. |
take |
integer | 25 |
Page size. Clamped server-side to 1–200. |
sortBy |
string | (per-endpoint default) | Field to sort on, using the wire’s camelCase name. An unrecognised value silently falls back to the endpoint’s default sort. |
sortDirection |
asc | desc |
asc |
Sort direction. |
search |
string | — | Free-text; contains-matches across the endpoint’s text fields. |
filters[i].field |
string | — | Field to filter on. |
filters[i].op |
string | eq |
Comparison operator. Supported operators vary by field — see Filterable fields. |
filters[i].value |
string | — | Value to compare against. |
Filters use an indexed query form and combine with AND:
?filters[0].field=environment&filters[0].op=eq&filters[0].value=production&filters[1].field=renderedAt&filters[1].op=gte&filters[1].value=2026-01-01T00:00:00ZResponse envelope
Section titled “Response envelope”Every list response is wrapped the same way:
{ "items": [ /* … */ ], "total": 42, // full count matching your filters, independent of skip/take "skip": 0, // the paging the server actually applied "take": 25}total is the count across all pages, so use it to know when to stop paging.
skip and take echo what the server applied after clamping — if you asked for
take=5000 you’ll get take=200 back.
Filterable fields
Section titled “Filterable fields”Operator sets used below:
| Set | Operators |
|---|---|
| Id | eq |
| Text | eq, contains |
| Ordered (numbers, dates) | eq, gt, gte, lt, lte |
| Enum | eq, neq |
Templates
Section titled “Templates”GET /v1/templates and GET /v1/projects/{projectId}/templates.
| Field | Type | Operators |
|---|---|---|
name |
string | Text |
project.guid |
UUID | Id |
createdAt |
ISO 8601 datetime | Ordered |
updatedAt |
ISO 8601 datetime | Ordered |
Sortable: name, createdAt, updatedAt (default). search matches name.
Template versions
Section titled “Template versions”GET /v1/templates/{templateId}/versions.
| Field | Type | Operators |
|---|---|---|
versionNumber |
number | Ordered |
publishedAt |
ISO 8601 datetime | Ordered |
createdAt |
ISO 8601 datetime | Ordered |
updatedAt |
ISO 8601 datetime | Ordered |
Sortable: versionNumber (default), publishedAt, createdAt, updatedAt.
Documents
Section titled “Documents”GET /v1/documents.
| Field | Type | Operators |
|---|---|---|
documentName |
string | Text |
template.guid |
UUID | Id |
versionNumber |
number | Ordered |
fileSizeBytes |
number | Ordered |
pageCount |
number | Ordered |
renderedAt |
ISO 8601 datetime | Ordered |
createdAt |
ISO 8601 datetime | Ordered |
updatedAt |
ISO 8601 datetime | Ordered |
environment |
test | production |
Enum |
language |
string | Enum |
Sortable: documentName, versionNumber, fileSizeBytes, pageCount,
renderedAt (default), renderDuration, environment, createdAt, updatedAt.
search matches documentName.
Example
Section titled “Example”Page through every production document rendered this year, newest first.
curl -G "https://pagr-prd-api-public.azurewebsites.net/v1/documents" \ -H "Authorization: Bearer pagr_prod_xxxxxxxx" \ --data-urlencode "skip=0" \ --data-urlencode "take=100" \ --data-urlencode "sortBy=renderedAt" \ --data-urlencode "sortDirection=desc" \ --data-urlencode "filters[0].field=environment" \ --data-urlencode "filters[0].op=eq" \ --data-urlencode "filters[0].value=production" \ --data-urlencode "filters[1].field=renderedAt" \ --data-urlencode "filters[1].op=gte" \ --data-urlencode "filters[1].value=2026-01-01T00:00:00Z"Increment skip by take until skip + items.length >= total.
import asynciofrom pagr import PagrApiClient
async def main(): async with PagrApiClient("pagr_prod_xxxxxxxx") as client: skip = 0 while True: page = await client.get_documents( skip=skip, take=100, sort_by="renderedAt", sort_direction="desc", filters=[ {"field": "environment", "value": "production"}, {"field": "renderedAt", "op": "gte", "value": "2026-01-01T00:00:00Z"}, ], ) for doc in page.items: print(doc.document_name, doc.rendered_at)
if not page.has_more: break skip += len(page.items)
asyncio.run(main())import { PagrApiClient } from 'pagr';
const client = new PagrApiClient('pagr_prod_xxxxxxxx');
let skip = 0;for (;;) { const page = await client.getDocuments({ skip, take: 100, sortBy: 'renderedAt', sortDirection: 'desc', filters: [ { field: 'environment', value: 'production' }, { field: 'renderedAt', op: 'gte', value: '2026-01-01T00:00:00Z' }, ], });
for (const doc of page) console.log(doc.documentName, doc.renderedAt);
if (!page.hasMore) break; skip += page.items.length;}import org.example.*;import org.example.models.*;
try (PagrApiClient client = new PagrApiClient("pagr_prod_xxxxxxxx")) { int skip = 0; while (true) { PagedResult<RenderDocument> page = client.getDocuments(ListOptions.builder() .skip(skip) .take(100) .sortBy("renderedAt") .sortDirection(SortDirection.DESCENDING) .filter(new Filter("environment", "production")) .filter(new Filter("renderedAt", FilterOp.GTE, "2026-01-01T00:00:00Z")) .build());
for (RenderDocument doc : page.getItems()) { System.out.println(doc.getDocumentName() + " " + doc.getRenderedAt()); }
if (!page.hasMore()) break; skip += page.getItems().size(); }}using Pagr.Sdk;
using var client = new PagrApiClient("pagr_prod_xxxxxxxx");
var skip = 0;while (true){ var page = await client.GetDocumentsAsync(new ListOptions { Skip = skip, Take = 100, SortBy = "renderedAt", SortDirection = SortDirection.Descending, Filters = [ new Filter("environment", "production"), new Filter("renderedAt", FilterOp.Gte, "2026-01-01T00:00:00Z"), ], });
foreach (var doc in page) Console.WriteLine($"{doc.DocumentName} {doc.RenderedAt}");
if (!page.HasMore) break; skip += page.Items.Count;}require "pagr"
client = Pagr::Client.new("pagr_prod_xxxxxxxx")
skip = 0loop do page = client.documents( skip: skip, take: 100, sort_by: "renderedAt", sort_direction: "desc", filters: [ { field: "environment", value: "production" }, { field: "renderedAt", op: :gte, value: "2026-01-01T00:00:00Z" }, ], )
page.each { |doc| puts "#{doc.document_name} #{doc.rendered_at}" }
break unless page.more? skip += page.items.sizeend#include "pagr/PagrApiClient.hpp"
pagr::PagrApiClient client("pagr_prod_xxxxxxxx");
int skip = 0;while (true) { pagr::ListOptions options; options.skip = skip; options.take = 100; options.sort_by = "renderedAt"; options.sort_direction = pagr::SortDirection::Descending; options.filters.emplace_back("environment", "production"); options.filters.emplace_back("renderedAt", pagr::FilterOp::Gte, "2026-01-01T00:00:00Z");
const auto page = client.get_documents(options); for (const auto& doc : page.items) { std::cout << doc.document_name << "\n"; }
if (!page.has_more()) break; skip += static_cast<int>(page.items.size());}Paging helpers in the SDKs
Section titled “Paging helpers in the SDKs”Every SDK’s paged result exposes the same three things, so the loop above looks the same in each language:
| SDK | Items | Total | More pages? |
|---|---|---|---|
| Python | .items (also iterable / indexable) |
.total |
.has_more |
| TypeScript | .items (also iterable) |
.total |
.hasMore |
| Java | .getItems() (also Iterable) |
.getTotal() |
.hasMore() |
| C# | .Items (also IReadOnlyList<T>) |
.Total |
.HasMore |
| Ruby | .items (also Enumerable) |
.total |
.more? |
| C++ | .items |
.total |
.has_more() |
The “more pages?” flag is computed client-side as skip + items.length < total —
no extra request needed. Note Ruby names it more? rather than has_more?,
following Ruby’s predicate convention.
Other considerations
Section titled “Other considerations”- Offset paging drifts. New renders arrive while you page. If exactness
matters, filter on a fixed upper bound (
renderedAtltea timestamp you captured before starting) so the result set can’t grow underneath you. takeis clamped, not rejected. Asking for 5000 gives you 200 and no warning. Readtakeoff the response if you depend on the page size.- Datetime values are ISO 8601 strings on the wire. The Python SDK accepts a
datetimeand the C# SDK hasFilter.Gte(field, DateTimeOffset)helpers that format for you; elsewhere, format to ISO 8601 yourself. - UUID filter fields are the nested wire names —
project.guidandtemplate.guid, notprojectId/templateId. The names differ from the fields in the response body on purpose; use the table above. - List endpoints are reads, so the SDKs retry them on transient server failures. Paging loops are safe to leave running. See Handle errors and retries.
Related articles
Section titled “Related articles”- Templates & versions — the template list endpoints.
- Documents — the document list endpoint.
- Browse and download documents — paging and downloading, step by step.
- Handle errors and retries — what the SDKs retry, and what they don’t.
