Documentation / Guides

Batch requests

Fetch up to 50 known records in one call with the ids parameter, instead of one request per record.

If you already know which records you want, ask for them together. Every list endpoint that supports it takes an ids parameter:

curl "https://api.greyhoundapi.com/v1/races?ids=2765720978-1237493697-2214883445" \
  -H "X-API-Key: $GAPI_KEY"

One request, one response, three races. The alternative — a loop calling /v1/races/{race_id} once per race — costs you a round trip each time and burns a request from your quota for every one.

We found this from the other side. One of our own collectors was fetching results a race at a time; moving it to a batched upstream call turned a full day of work into about fifteen minutes, for byte-identical data. The saving is almost entirely latency, and it is larger than people expect.

What it costs

Each id costs one call. Fifty ids in one request is charged as fifty, exactly as if you had fetched them one at a time.

So batching does not buy you a cheaper allowance — it buys you time. Fifty round trips become one, which on a typical connection is the difference between several seconds and a few hundred milliseconds, and your code gets simpler.

We are explicit about this because the alternative is worse for everyone. If a batch counted as one request, a key limited to 180 a minute could pull 9,000 records a minute by wrapping them fifty at a time, and the limit would stop describing anything. A rate limit should measure how much data you take, not how many connections you opened.

The cost is charged before the request is served, so a batch larger than your remaining allowance is refused with 429 rather than partially filled.

The rules

  • Dash separated, e.g. ?ids=A-B-C. Commas work too if that suits your code better.
  • Up to 50 ids per request. Past that you are paginating, not batching — see below.
  • Filters still compose. ?ids=A-B-C&status=complete narrows the batch; it does not replace it.
  • Duplicates collapse. Sending the same id three times returns it once.
  • Each id costs one call. A batch of fifty is charged as fifty, exactly the same as fetching them one at a time.
  • Public ids only, the same ones every response returns. Raw internal integers are refused here as they are everywhere.

A bad id fails the whole request

If any id in the batch has no record, you get 400 invalid_id naming it, and no data at all:

{
  "error": {
    "code": "invalid_id",
    "message": "No record for id 9999999999.",
    "ids": ["9999999999"]
  }
}

This is deliberate, and it is the opposite of what several APIs do. Quietly returning the 49 that worked gives you a response that looks complete and is not. Nothing in the payload tells you one is missing, so the gap gets written into whatever you build and surfaces weeks later as a hole in a report. We would rather be loudly wrong now than quietly wrong later.

If you are working from ids that may not exist — a stale list, or something a user typed — check them in smaller batches, or handle the ids array in the error and retry without them.

When not to use it

ids is for a known set. If you are walking everything — a whole meeting, a date range, a track’s history — use the cursor instead. It is built for that, it has no cap, and it will not break when the set grows past 50.

The distinction in one line: you already have the ids → ids. You are discovering them → cursor.

Where it works

EndpointBatches
/v1/races Search races
/v1/results Search results
/v1/dogs/search Search dogs
/v1/tracks List tracks