Budgets & Cursors
Two parameters exist for automated callers: max_credits caps what a single
call is allowed to cost, and cursor makes a repeating job idempotent. Both
are optional and additive. Nothing changes for callers that ignore them.
max_credits - cap the cost of one call
Most endpoints are flat-priced, but a few price per result. On those, the final cost depends on how much data came back, which an agent could not know until after it was charged.
max_credits is the ceiling you state up front.
curl "https://frontrun.vc/v1/trending?limit=100&classify=true&max_credits=200" \
-H "X-API-Key: your_api_key"It fails closed. If the call could cost more than your ceiling, no work is
done, nothing is charged, and you get a 400 telling you the number:
{
"error": "This call could cost up to 1624 credits, which is above your max_credits of 200. Nothing was charged and no work was done.",
"error_code": "max_credits_exceeded",
"max_cost_credits": 1624,
"max_credits": 200,
"max_cost_breakdown_credits": { "trending": 24, "classification": 1600 },
"charged_credits": 0,
"balance_credits": 9836,
"hint": "Classification is 16 credits per entity returned. Lower limit (currently 100), drop classify=true, or raise max_credits."
}The ceiling is checked against the worst case
The check runs against the maximum the call could cost, before any billable work happens. Billing itself is unchanged: you are still charged only for what was actually computed. A call that clears the ceiling usually lands well under it.
That matters most on the sections that are free when empty. include=founders
could cost 100 credits, so it counts 100 toward the ceiling, but it costs 0 if
no founders are found.
Reading the estimate without a failed call
Every endpoint that honors max_credits returns max_cost_credits in its
normal 200 response. That is the same worst-case number, for the parameters
you sent, so you can budget the next call from the last one.
Where it applies
| Endpoint | What scales the cost |
|---|---|
GET /v1/trending | classify=true adds 16 credits per entity returned |
POST /v1/classify | 16 credits per entity found |
GET /v1/search?scope=catalog | include_founders=true adds 100 credits per company that resolves |
POST /v1/founders/batch | 100 credits per company that resolves |
GET /v1/company/:handle | each include= section is priced at its standalone rate |
POST /v1/track (batch) | 4 credits per account new to the platform |
On every other endpoint the cost is a fixed number you can read from
Pricing, so max_credits is not needed and is
ignored.
cursor - resume exactly where the last run stopped
since describes a window. It cannot tell you what you have already
processed, so a job that fails and retries either reprocesses its overlap or
guesses at a gap.
A cursor is an opaque watermark. Read a page, process it, save next_cursor,
and send it back on the next run.
# First run: use since as normal.
curl "https://frontrun.vc/v1/follows/new?since=24h" -H "X-API-Key: $KEY"
# -> { ..., "next_cursor": "frc1_eyJ2IjoxLCJ..." }
# Every run after that: no window to guess at.
curl "https://frontrun.vc/v1/follows/new?cursor=frc1_eyJ2IjoxLCJ..." \
-H "X-API-Key: $KEY"Rules
- Persist
next_cursoronly after you have successfully processed the response. If your job dies mid-batch, retrying with the old cursor returns the same window again. That is the point. - Replaying the same cursor is safe and returns the same window.
- Cursors are opaque. Do not parse, edit, or construct them. A modified token is rejected.
- Cursors are scoped to the endpoint, the API key, and the filters you sent
(
username,sector,event_type, and so on). Change a filter and mint a fresh cursor withsince, otherwise the records your old filter excluded would be skipped forever. Replaying a cursor across a different scope returns a400instead of silently doing the wrong thing. cursorandsinceare mutually exclusive in effect. Whencursoris present it sets the window andsinceis ignored.
next_cursor is withheld while has_more is true
The watermark covers everything the query matched, not just the page you were
handed. If more results are waiting, next_cursor comes back null with a
note: page through with offset first, then take the cursor from the final
page. Advancing mid-pagination would skip the pages you never read.
Granularity
Follow data is stored as one snapshot per tracked account per day, so the watermark is a snapshot boundary. A cursor resumes at the first snapshot after the newest one your last response consumed. Nothing is delivered twice and nothing between the two runs is missed.
One exception worth knowing: accounts still establishing a baseline are
answered from a live check against X rather than from a snapshot. Those results
carry live_refreshed: true and can appear once more in the following window,
once the poller writes them into a snapshot. Dedupe on twitter_user_id if
that matters to you.
Where it applies
| Endpoint | Watermark |
|---|---|
GET /v1/follows/new | newest follow snapshot consumed |
GET /v1/follows/enriched | newest follow snapshot consumed |
GET /v1/feed | newest follow snapshot consumed |
GET /v1/reports | newest report_date returned |
/v1/trending and /v1/convergence deliberately do not take a cursor.
They are ranked aggregations over a window, not record listings: "top 25
trending in the last four hours" is not the continuation of "top 25 in the last
seven days", so a watermark there would quietly degrade the ranking rather than
paginate it. Keep using since for those.