A Starter key asks GET /v1/feeds for a week of history. It gets two days: status 200, a full first page, a working cursor, and after a few more requests a next_cursor of null. That is the tier doing exactly what the tier says it does, and it is worth twenty minutes of your time to understand before you write the backfill rather than after.
History on this API is tiered. Your since says the earliest publication time you want. Your plan says the earliest publication time you can reach. The effective lower bound is whichever of those two is later, so a request that overshoots your plan comes back with the part of the range your plan covers.
Starter reaches back 48 hours. Pro reaches 30 days, Quant a year, Enterprise the whole archive with no floor at all. Free carries no REST history, so on Free the question never comes up.
We decided early that overshooting a plan boundary should be served rather than refused, and I'd make that call again. Backfill jobs overshoot constantly and on purpose. You point them at your own start date, not at your plan's, and you would rather get back what exists than get a 403 you have to write a special case around. Asking for a year on a 48-hour plan is a normal thing for a correct client to do.
Enterprise doesn't fit on that axis, which is the point of Enterprise.
Page size moves with the window, and that's the part the lookback numbers alone don't tell you. limit is capped at 50 on Starter, 200 on Pro, 500 on Quant, 1,000 on Enterprise. The tier that reaches furthest back also covers the most ground per request. Send limit=1000 on a Starter key and you'll be served 50 rows. The walk still completes correctly, it just takes twenty times the round trips, and round trips are the thing your quota counts.
Walk it until the cursor is null
Pagination is cursor-based. You ask for a page, you get data and next_cursor, and you pass that cursor back on the next request until it comes back null.
Three rules make that loop correct, and all three of them are things people get wrong on the first attempt.
The cursor is opaque. It's a position in an ordering, and the ordering is publication time descending with a tiebreak on id, because a wire burst can put a dozen items on the same minute and a sort on time alone would shuffle them between pages. Treat the string as a token you hand back unmodified. Store it if you want to resume tomorrow, log it if it helps you debug, and don't parse it, because anything you read out of it is an implementation detail you'd be building on.
The cursor carries a position, not your query. Change symbol or a tag dimension between pages and the server will keep walking from a position defined by a different result set. You'll get a page that looks entirely coherent and means nothing. Fix your filters before the first request and keep them identical for every request in the walk.
And next_cursor: null is the only end condition. Not a short page, not an empty page, not a page smaller than the limit you asked for. Because limit is capped by tier, the page size you asked for and the page size you got may already differ, so any termination logic built on counting rows is reading a number that means something else.
A rolling window rolls
The floor is measured from the moment the request is served, which is what a rolling window is. Over a walk of a few minutes that's invisible. Over a walk that runs for an hour against the oldest edge of your plan's window, the oldest items are aging out from under you while you page toward them.
So don't backfill from the edge. Set until to a fixed timestamp, start comfortably inside your window rather than at the boundary of it, and record the oldest published_at you actually received so tomorrow's incremental run has a real anchor instead of an assumed one. If your job needs a frame it can reproduce next week, the plan you want is the one whose window is wider than your reconciliation horizon, not the one that barely covers it.
Headers, and one shared counter
Rate-limit state comes back on the response:
On an uncapped tier both values are the literal string unlimited. Not a large number, not an absent header. Anything doing int(resp.headers["X-RateLimit-Limit"]) works fine against the Starter key you tested with and throws the first time a Pro customer runs it. Read them as strings and check for that value. They ride on 200 and 429; a 400 or a 401 carries no rate-limit headers at all, so don't make your client depend on them being there.
The counter behind those headers is shared. REST requests and WebSocket deliveries draw from the same daily allowance, and on Starter that allowance is 5,000 units a day for both together. Paging 48 hours at 50 rows a page while a live socket is running takes from one bucket, and the bucket resets at 00:00 UTC rather than on a rolling window of its own. Pro, Quant and Enterprise are uncapped here, so this is a Starter shape specifically, and it's the reason the page-size cap matters more than it looks.
One related note on cost. GET /v1/feeds/{id} is the endpoint with the full detail on it: raw content, the reasoning text behind the scores, per-symbol analysis. It counts as its own unit. Hydrating every row of a 500-item page into full detail is 501 requests, not one. Use the list endpoint to decide what you care about and the detail endpoint on the handful you actually do.
Reconciling a socket against history
The common integration is a live socket plus REST for the gaps, and the gaps are real: deploys, network drops, a client restart at 09:31.
Subscribe with a snapshot so a reconnect replays recent items, dedupe on id, and treat anything longer than the snapshot as a job for /v1/feeds with a since set to the last publication time you successfully handled. Replayed items are metered like live ones, so a client that reconnects in a tight loop and asks for a snapshot every time will spend a Starter allowance on history it already has.
That reconciliation is the honest way to size a plan. Not by how many rows a page holds, but by how long your system can be down before history stops being able to tell you what you missed. On Starter that answer is two days. Work out yours before the outage, because that's not a number you want to discover during one.