One reference per client or market
A reference is a label you choose, such as a client or market name. Give every run the reference of the client it's for. Each reference then holds that client's history: you can list its runs, compare one week with the last, count the credits it used and read it from Lighthouse tools.
Give every run a label for the client or market it belongs to, and each client builds up its own history. You can then list a client's runs, compare this week with last week and count the credits each client used. It suits consultants who look after several clients.
At a glance
| Who it's for | Revenue consultants and teams who look after rates for several clients or markets. |
| What you end up with | Every run filed under its client, a history of snapshots for each one, and a credit count per client. |
| What it costs | One credit per property for each run you start. Listing, comparing and counting are free. |
| What you need | A key with the Read and write scope to start runs, and optionally a Read key for reporting tools. |
Steps
-
Choose a reference for each client or market
A reference is your own label, sent with every run you start with
POST /v1/runs. Pick one per client, such asclient-42, or one per market, such asmarket-york. If a client has several markets, combine them:client-42-york.- It can be 1 to 128 characters long.
-
It can't contain a comma. A reference with one answers
422 validation_failed, and the list filter below uses commas to separate references. - It doesn't have to be unique. Sending it again starts a new report and run under the same reference, which is how a history builds up.
Keep each reference the same from run to run. A new spelling starts a new history.
Only runs started with
POST /v1/runshave a reference. Runs started in the Beacon Pro app, or withPOST /v1/reports/{id}/runs, havenull. -
Decide which key sees which runs
A key sees either the runs it started itself (Its own runs) or every run in your company (All company runs). A key can't be limited to certain references. Choose one of these set-ups:
- One key per tool. Each tool or script starts its own runs with its own key, set to Its own runs, and sees only those.
- One key for reporting. One system starts every run, and a Read key with All company runs feeds your spreadsheets and dashboards. It also sees runs started in the app.
A run a key can't see answers
404and never appears in its lists. See API keys. -
Start a run with the client's reference
Start each client's run as in the quick start, with the client's reference. Run the same reference again next week, or whenever you want a new snapshot. Each run is a new report with the same list, and older runs stay as they were.
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": "Client 42, York", "currency": "GBP", "nights": 1, "properties": [ {"url": "https://www.booking.com/hotel/gb/harbour-view.html", "name": "Harbour View Hotel"}, {"url": "https://www.booking.com/hotel/gb/quayside-inn.html", "name": "Quayside Inn"} ] }'Your company can have 5 runs queued or running at once, unless Beacon has raised your company's limit. With more clients than that, start the next run when one finishes. A start over the limit answers
429 too_many_active_runsand charges nothing. -
List a client's runs
GET /v1/runswith areferencereturns every run with that reference that your key can see, newest first. Each run carries its status, dates and cost.curl "https://api.pro.getbeacon.cloud/v1/runs?reference=client-42&status=completed" \ -H "Authorization: Bearer $BEACON_KEY"
import os, requests API = "https://api.pro.getbeacon.cloud/v1" HEADERS = {"Authorization": f"Bearer {os.environ['BEACON_KEY']}"} def list_runs(filters): """Every run that matches the filters, newest first, across all pages.""" params = {**filters, "limit": 200} runs = [] while True: r = requests.get(f"{API}/runs", params=params, headers=HEADERS) r.raise_for_status() page = r.json() runs += page["runs"] if page["nextCursor"] is None: return runs params["cursor"] = page["nextCursor"] for run in list_runs({"reference": "client-42", "status": "completed"}): print(run["id"], run["startedAt"], run["creditsCharged"])
// Node 18 or later, saved as a .mjs file so await works at the top level const API = "https://api.pro.getbeacon.cloud/v1"; const HEADERS = { Authorization: `Bearer ${process.env.BEACON_KEY}` }; // Every run that matches the filters, newest first, across all pages async function listRuns(filters) { const params = new URLSearchParams({ ...filters, limit: "200" }); const runs = []; while (true) { const res = await fetch(`${API}/runs?${params}`, { headers: HEADERS }); if (!res.ok) throw new Error(await res.text()); const page = await res.json(); runs.push(...page.runs); if (page.nextCursor === null) return runs; params.set("cursor", page.nextCursor); } } for (const run of await listRuns({ reference: "client-42", status: "completed" })) { console.log(run.id, run.startedAt, run.creditsCharged); }
{ "runs": [ {"id": 1204, "reference": "client-42", "status": "completed", "creditsCharged": 2, "dateFrom": "2026-10-05", "startedAt": "2026-10-05T08:00:12Z", ...}, {"id": 1188, "reference": "client-42", "status": "completed", "creditsCharged": 2, "dateFrom": "2026-09-28", "startedAt": "2026-09-28T08:00:09Z", ...} ], "nextCursor": null }You can combine these filters. A run must match all of them:
Filter What it keeps referenceRuns with this reference. Send up to 100, separated by commas, such as client-42,client-43. More than 100 answers422 invalid_reference.statusqueued,runningorcompleted.fromRuns started on or after this date, written YYYY-MM-DD.toRuns started on or before this date. limitRuns per page, 1 to 200. The default is 50. When there are more runs than fit on a page,
nextCursorholds a value to send back ascursorfor the next page. It isnullon the last page. The Python and JavaScript examples follow it for you. -
Compare two runs
Each run is a snapshot, so comparing two runs of the same reference shows how prices moved between them. The quickest way is to read rates for both, which gives one price per property and check-in date, and match them on
hotelId(Beacon's stable id for the property) andcheckIn. The example needs at least two completed runs with the reference.# Uses API and HEADERS from the example above def rates_by_day(run_id): """One rate per property and check-in date, keyed by (hotelId, checkIn).""" r = requests.get(f"{API}/runs/{run_id}/rates", params={"persons": 2}, headers=HEADERS) r.raise_for_status() return {(rate["hotelId"], rate["checkIn"]): rate for rate in r.json()["rates"]} # The two newest completed runs of this client r = requests.get(f"{API}/runs", headers=HEADERS, params={"reference": "client-42", "status": "completed", "limit": 2}) r.raise_for_status() latest, previous = r.json()["runs"] now = rates_by_day(latest["id"]) before = rates_by_day(previous["id"]) for key, rate in now.items(): old = before.get(key) if old is None or rate["price"] is None or old["price"] is None: continue # no match, or no rate on one side: message says why if rate["price"] != old["price"]: print(rate["hotelName"], rate["checkIn"], old["price"], "to", rate["price"])
By default, rates start from today's date (UTC), so both runs are compared over the same check-in dates ahead. Use the same filters, such as
personsorbar, for both runs. For every room rather than one price a day, download both runs' results and compare them in your own tools instead.If the runs capture a stay of more than one night, add
loswith that number of nights, or every date answersgeneral.missing. -
Count the credits each client used
Every run carries
creditsCharged. To see what each client used in a month, list the month's runs and add them up by reference:from collections import Counter # Uses list_runs() from the example above credits = Counter() for run in list_runs({"from": "2026-09-01", "to": "2026-09-30"}): credits[run["reference"] or "(no reference)"] += run["creditsCharged"] for reference, total in credits.most_common(): print(f"{reference}: {total} credits")
client-42: 8 credits client-43: 6 credits (no reference): 3 credits
The total covers the runs your key can see. Use a key with All company runs to count every run in your company, including runs started in the app, which appear as
(no reference).On an account with unlimited credits, runs aren't charged, so these totals may be 0.
-
Read a client from Lighthouse tools
Each reference is also a Lighthouse subscription ID. Tools written for Lighthouse, and spreadsheets that use the Lighthouse-compatible API, read the newest completed run of a client with
subscriptionId=client-42, and pick up the next run as soon as it completes./lighthouse/v3/hotelslists one entry for each reference the key can see. The example below sends the key inX-Oi-Authorization, the header Lighthouse clients already send.curl "https://api.pro.getbeacon.cloud/lighthouse/v3/rates?subscriptionId=client-42&format=csv" \ -H "X-Oi-Authorization: $BEACON_READ_KEY"
See Rates in Excel or Power BI to read a client's rates in a spreadsheet, and Switching from Lighthouse for the rest.
What to watch for
-
Deleted rows. A run whose rows were deleted still appears in the list, with
resultsPurgedAtset, but its results and rates answer410 results_purged. ItscreditsChargedstill counts. - Runs still going. Rates need a completed run, so filter the list with
status=completedwhen you compare. - Different property lists. If a client's list of properties changed between runs, a property that is in only one of them can't be compared. The example skips it.
- Stay length and currency belong to each run. Compare runs that used the same ones.
-
Upper and lower case. References are matched without regard to case or accents, so
Client-42andclient-42find the same runs. The run keeps the reference as you sent it. - Credits.
creditsChargedis what the run cost when it was accepted. It doesn't change later. - Dates. The
fromandtofilters onGET /v1/runsare UTC dates.