Docs
Reference
Budgets & Cursors

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

EndpointWhat scales the cost
GET /v1/trendingclassify=true adds 16 credits per entity returned
POST /v1/classify16 credits per entity found
GET /v1/search?scope=cataloginclude_founders=true adds 100 credits per company that resolves
POST /v1/founders/batch100 credits per company that resolves
GET /v1/company/:handleeach 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_cursor only 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 with since, otherwise the records your old filter excluded would be skipped forever. Replaying a cursor across a different scope returns a 400 instead of silently doing the wrong thing.
  • cursor and since are mutually exclusive in effect. When cursor is present it sets the window and since is 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

EndpointWatermark
GET /v1/follows/newnewest follow snapshot consumed
GET /v1/follows/enrichednewest follow snapshot consumed
GET /v1/feednewest follow snapshot consumed
GET /v1/reportsnewest 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.