Skip to main content

Lists and paging

Reelwire has two kinds of list, and they behave differently on purpose.

Logs never stop growing: posts, jobs, renders, ingest events, approvals, invoices. These are paged, searched and sorted by the server, over every row the workspace has.

Configuration does not grow without bound: brands, channels, streams, post lists, the media library. These come back whole, and the client searches and sorts them itself. A library is hundreds of files, not millions, and paging it would buy nothing and cost a round trip.

The log parameters

ParameterWhat it does
pageWhich page, from 1. Anything else is treated as 1.
perPageHow many to a page. 1 to 200; outside that, the route's own default.
limitThe older name for perPage, still honoured.
qWhat was typed, matched case-insensitively across the columns the row prints. At most 200 characters.
sortA column name the route offers. Anything else falls back to the route's default order.
ascending"true" or "false". Absent is the column's own default, which is newest first for a time.
since, untilThe window, as absolute instants.
curl "http://localhost:4000/v1/jobs?page=2&perPage=25&q=bund&sort=state&ascending=true" \
-H "Authorization: Bearer rw_your_key_here"

Every log answers with the page and the counts around it:

{
"jobs": [],
"total": 7,
"all": 7,
"page": 1,
"perPage": 1,
"facets": { "state": ["COMPLETED"] }
}
FieldWhat it is
totalRows matching the filters. Divide by perPage for the last page.
allRows the workspace has, ignoring the filters, so you can say "7 of 412".
page, perPageWhat you actually got, after clamping.
facetsThe values each filter can usefully take.

The default perPage differs per route, because the screens differ: 25 for /v1/jobs, 40 for /v1/posts, 30 for /v1/render-jobs, 50 for /v1/ingest-events, 200 for /v1/approvals. Send perPage rather than relying on any of them.

Facets are read from the rows

facets is computed from what the workspace actually holds, not from a fixed list. A filter value that appears there is one that can return something, and a state nothing is in does not appear. Some lists also carry labels, which maps ids in the facets to names, so a filter dropdown does not need a second request to say "Channel 4 de" instead of a ULID.

An unparseable filter widens rather than refuses

since and until that cannot be read are ignored, not rejected. This is a filter on a list, and the honest failure is to show more, not to show an error. The same goes for a sort column a route does not offer, and for a filter repeated in the query string: a key given twice arrives as an array, a filter is only ever one value, so it is treated as no filter at all.

A search longer than 200 characters is the one exception, and is a 400.

Windows mean different things

On /v1/jobs, the window is when something happened to the job: it arrived, it rendered, or one of its posts changed. A job that arrived yesterday and published ten minutes ago is inside the last hour, because the rows sort by Updated and anything else would be a lie.

On /v1/posts and the other logs, it is when the row was created.

/v1/calendar is not a log and takes from and to instead, both required, both absolute instants, with a window of at most 62 days.

Paging safely while things are arriving

A log is ordered newest first, so a row arriving between two requests shifts everything down a place and page 2 can repeat a row page 1 already showed. Two ways round it:

  • Pin the window. Pass until set to the moment you started, and page through a set that cannot grow underneath you.
  • Page from the oldest end, with ascending=true, where you are reading everything rather than the most recent.

Every row carries an id, so deduplicating on the way through works whatever you do.

The lists that are not paged

/v1/brands, /v1/channels, /v1/streams, /v1/queues, /v1/media, /v1/media/renders, /v1/feeds and /v1/templates return everything. page and perPage on them do nothing.

/v1/deliveries is in between: it is a log, but it takes limit alone, defaulting to 40 and capped at 200, with no page.