Making requests

The rules every request follows: where to send it, what to send with it, how lists come back in pages, and how times, dates and prices are written.

In short

Every request goes to one of two base addresses, carries your API key, and sends and receives JSON. This page also covers lists that come back in pieces, sending a write safely a second time, caching, and how dates, times and prices are written. Developers need all of it. If you work in Excel or Power BI, the Excel recipe covers what you need.

Base URLs

API Base URL Use it for
Rate Capture API https://api.pro.getbeacon.cloud/v1 All of Rate Capture: credits, reports, runs, rows and rates. You can read and write.
Lighthouse-compatible API https://api.pro.getbeacon.cloud/lighthouse/v3 Reading rates with a client already written for Lighthouse's v3 API. Read only. See Switching from Lighthouse.

Paths in these docs start from the domain, such as GET /v1/runs or GET /lighthouse/v3/rates, so the full address of the first is https://api.pro.getbeacon.cloud/v1/runs.

HTTPS and JSON

  • HTTPS only.
  • Parameters go in the query string for GET, and in a JSON body for POST. Send the body with Content-Type: application/json. A body that isn't valid JSON, or isn't a JSON object, answers 422 invalid_json.
  • Every answer is JSON, with Content-Type: application/json; charset=utf-8, unless you ask for another format: ndjson for a whole run, or CSV from the Lighthouse-compatible API.
  • Answers aren't cached. They carry Cache-Control: no-store, including the ndjson and CSV formats you ask for. The one exception is a 200 from GET /v1/runs/{id}/rates, which carries private, max-age=60. See caching rates.
curl -X POST https://api.pro.getbeacon.cloud/v1/runs \
  -H "Authorization: Bearer $BEACON_KEY" \
  -H "Idempotency-Key: client-42-2026-10-05" \
  -H "Content-Type: application/json" \
  -d '{"reference": "client-42", "name": "Harbour competitors", "currency": "GBP", "nights": 1,
       "properties": [{"url": "https://www.booking.com/hotel/gb/harbour-view.html", "name": "Harbour View Hotel"}]}'

Paths that don't exist

An address under /v1 that doesn't exist answers 404 not_found in the usual error shape, never an HTML page. So your code can read even this answer as JSON.

{"error": {"code": "not_found", "message": "No such API path."}}

Under /lighthouse/v3, a Lighthouse endpoint that Beacon doesn't provide answers 501 not_supported instead, so a Lighthouse client finds out at once. Any other address under /lighthouse answers 404 endpoint_not_found. See the error codes.

Authentication

Every request needs an API key, sent as Authorization: Bearer followed by the key. Lighthouse clients already send their key in a header called X-Oi-Authorization, and Beacon accepts that too. Three kinds of answer need no key: the Rate Capture API's OpenAPI file at /v1/openapi.json, unknown paths, and the Lighthouse 501 not_supported answers.

How to create a key, and what each one may do, is in API keys.

Lists and pages

The Rate Capture API's three lists come back a page at a time. Ask for a page size with limit. Each answer has a nextCursor: pass it back unchanged as cursor to get the next page. On the last page it is null.

List limit Order
GET /v1/reports 1 to 200, default 50 Newest first
GET /v1/runs 1 to 200, default 50 Newest first
GET /v1/runs/{id}/results 1 to 2000, default 500 Check-in date, then property, room and price
  • A cursor is a bookmark, not something to read. Don't build or change one. A cursor from another list, or another run, answers 422 invalid_cursor.
  • A limit that isn't a whole number in range answers 422 invalid_limit.
curl -H "Authorization: Bearer $BEACON_KEY" \
  "https://api.pro.getbeacon.cloud/v1/runs/1204/results?limit=500"

# then, with the nextCursor from that answer
curl -H "Authorization: Bearer $BEACON_KEY" \
  "https://api.pro.getbeacon.cloud/v1/runs/1204/results?limit=500&cursor=$NEXT_CURSOR"

A whole run in one request

GET /v1/runs/{id}/results?format=ndjson sends every row of the run in one answer, with no pages. It uses Content-Type: application/x-ndjson: one JSON object on each line. The first line is the run, with "kind": "run" in front, and every line after it is one row. limit is ignored. This is the cheapest way to take a whole run.

curl -H "Authorization: Bearer $BEACON_KEY" \
  "https://api.pro.getbeacon.cloud/v1/runs/1204/results?format=ndjson" > run-1204.ndjson

Pages in the Lighthouse-compatible API

/lighthouse/v3/hotels is paged the Lighthouse way instead. Without page it returns every subscription, with no meta. With page (1 or more) it returns that page of per_page subscriptions (1 to 100, default 100), and the answer's meta gives page, per_page, total_pages and total_results. A value out of range answers 400, and so does a page past the last one.

Sending a write again

Send an Idempotency-Key header on a write, so that sending it again after a timeout never creates or charges twice. It works on POST /v1/reports, POST /v1/reports/{id}/runs, POST /v1/runs, POST /v1/runs/{id}/retry and DELETE /v1/runs/{id}/results.

  • One key for each thing you mean to do once, up to 200 characters, chosen by you. A longer key answers 422 invalid_idempotency_key. Keys belong to your company.
  • Same key, path and body: Beacon answers exactly what it answered the first time, and does nothing again. The same key with another path or body is 422 idempotency_mismatch.
  • Keys are forgotten after 24 hours. After that, the same key starts a new action.
  • If the first request is still being handled, the answer is 409 idempotency_in_progress. Wait the 5 seconds in Retry-After and send it again with the same key. A first request that never finished is given up after 60 seconds, and the key can then be used again.
  • Answers that are kept: every 2xx, 402 insufficient_credits and 409 report_already_run. Sending the same key again returns the same answer, so fix the cause and use a new key.
  • Answers that aren't kept: 429, 422, 500, and a 409 about the run's state (run_not_completed, retry_cooldown or results_purged). Fix the cause, then send the same key again.
{"error": {"code": "idempotency_mismatch", "message": "This Idempotency-Key was already used for a different request (another endpoint or another body)."}}

Caching rates

A 200 from GET /v1/runs/{id}/rates carries three headers: ETag, Cache-Control: private, max-age=60 and Vary: Authorization, X-Oi-Authorization. Your own client may reuse the answer for 60 seconds. A shared cache never may.

  1. Keep the ETag value, exactly as sent, with the rates you stored.
  2. Next time, send it back in an If-None-Match header.
  3. If the run, its report's properties and your filters haven't changed, the answer is 304 Not Modified, with the same three headers and no body. Keep the rates you have.
curl -H "Authorization: Bearer $BEACON_KEY" \
  -H "If-None-Match: $ETAG_FROM_LAST_ANSWER" \
  "https://api.pro.getbeacon.cloud/v1/runs/1204/rates?persons=2"

A retry of failed checks, a change to the report's properties, different filters or a change to Beacon's answer format all give a new tag and a full 200. The tags are compared loosely. A W/ in front or -gzip on the end still matches, and If-None-Match: * matches any tag. Errors are never cached and never answer 304.

Only GET /v1/runs/{id}/rates sends an ETag. The Lighthouse-compatible endpoints don't: they answer Cache-Control: no-store and always send the full answer.

Times, dates, prices and null

  • Times are in UTC, written like 2026-10-05T08:00:12Z. The one exception is extractDateTime in the Lighthouse-compatible API, which follows Lighthouse and has no Z, but is also UTC.
  • Dates are written YYYY-MM-DD.
  • Prices are numbers for the whole stay in the report's currency. In JSON, prices always have a decimal point, such as 99.0. In the Lighthouse-compatible CSV, a whole price has none, such as 99. Whether a price includes tax is not captured.
  • Beacon's ids are whole numbers. roomId and rateId are the site's own ids and are text.
  • A field with no value is null. It is never missing from the answer. The exception is details in an error, which appears only when there is more to say. In CSV, null is an empty cell.