Overview
Two paging mechanisms and one silent ceiling, and they do not interchange. Two of the three failure modes look exactly like a series that simply ended. PostgREST is configured withdb-max-rows = 1000, server-wide: every response, every endpoint, every tier. Signing in does not change it. What differs is what each endpoint does when your request exceeds it.
1. GET views — limit, offset, Range
order. Without it, limit/offset walk an undefined sequence and pages can overlap or skip rows — a correctness bug, not a tidiness one.
0-999/* is the tell. Prefer: count=exact turns the * into the real total, at the cost of a count query.
2. RPCs that page — the p_after cursor
Three functions take a cursor, because walking the whole series is the normal thing to want:
1
First page
Send
p_after: "" — the empty string, not null.2
Next page
Copy the key out of the last row you received and send it as
p_after.3
Stop
A page shorter than 1000 rows is the last one.
p_after: null (or omitting it) is whole-result mode: everything, unless that would exceed 1,000 rows, in which case the call refuses with 22023.
fund_nav paging requires p_entity_type. Its cursor is a bare period, unique only within one family, and 385 CNPJs file under both fi and fidc in the same month. Page without it and you get a 22023, not a wrong answer. Whole-result mode does not need it — every row is labelled with its family.
3. RPCs that refuse and do not page
Every other set-returning function raises22023 above one page and offers no cursor, on purpose — a window that wide is a mistake rather than a walk. An option series lives months; a financial statement has tens of rows.
option_history · termo_history · financials · company_financials · income_statements · balance_sheets · cash_flow_statements · anbima_classes · inflation · inflation_items · fidc_cedentes · fidc_sacados · fidc_portfolio · fidc_tranches · fidc_aging · fund_holdings · fund_debentures · fund_documents · fund_restatements · the seven screen_*
The authoritative list is catalog().limits.page.functions.raise_only. Narrow p_from/p_to, the ids, or the metrics. The error says why it refused and how to fix it for that function — in the message, and again as PostgREST’s details and hint. fidc_cedentes, fidc_sacados, fidc_portfolio, fund_holdings and fund_debentures also take p_limit (1..1000) as an explicit request for the newest N rows; until catalog v34 (the FIDC three) and v41 (the holdings pair) they trimmed silently at 500 / 5,000 instead.
4. The silent one — tier ceilings
Three functions clamp: ap_limit above the ceiling is quietly lowered to it. No error, no header, no signal.
The only defence is arithmetic: a result of exactly 500 rows is probably truncated. If the count equals your tier’s ceiling, assume there is more and narrow the window.
panel has a ceiling on a different axis — at most 3 ids anonymously, 50 signed in — but it refuses with 22023 rather than trimming. Universe mode is signed-in only.
Deciding in one pass
Raising the ceilings
Signing in (GitHub) returns a user JWT. Send it asAuthorization: Bearer <jwt> beside apikey, never instead of it — the publishable key is not a JWT.
Signing in raises ceilings and the statement timeout (3s → 8s). It never widens the set of objects you can reach: schema api is the entire public surface for every tier.
Next
Your first screen
Four calls to something real on screen.
Conventions & limits
Headers, error codes and the full limits table.