Quick start
Start your first run, wait for it to finish and download its rates. Most of the time is spent waiting for the run, which usually takes minutes rather than seconds.
This page takes you from nothing to your first rates: make a key, start a run, wait for it to finish and download the results. It shows curl, Python and JavaScript, so it is written for developers. If you don't code, the Excel and Power BI recipe does the same job without code. A run costs one credit per property, and new accounts get 6 free credits.
You need a Beacon Pro account. Every new account gets 6 free credits, which is enough for a first run of up to 6 properties. Create your free Beacon Pro account if you don't have one.
-
Create an API key
In Beacon Pro, open your initials in the top right, choose Company, then the API tab. Under Create a key, give it a name and choose Read and write as its scope, because starting a run spends credits. Only a company owner or admin can create keys.
The key is shown once, so copy it straight away and keep it somewhere safe, such as an environment variable. Every example below reads it from
BEACON_KEY. The curl examples are written for a macOS or Linux terminal, or WSL on Windows.export BEACON_KEY="bp_k3x9q2m7_..."
import os, time, requests API = "https://api.pro.getbeacon.cloud/v1" HEADERS = {"Authorization": f"Bearer {os.environ['BEACON_KEY']}"}
// 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}` };
More on scopes, visibility and allowed IP addresses in API keys.
-
Check your credits
This call is free and is a quick way to prove the key works. A run costs one credit per property, so make sure the balance covers your list.
curl https://api.pro.getbeacon.cloud/v1/credits \ -H "Authorization: Bearer $BEACON_KEY"
r = requests.get(f"{API}/credits", headers=HEADERS) r.raise_for_status() print(r.json())const credits = await fetch(`${API}/credits`, { headers: HEADERS }); if (!credits.ok) throw new Error(await credits.text()); console.log(await credits.json());
{"companyId": 57, "balance": 6, "unlimited": false} -
Start a run
One call creates a report and starts its run. Send the URL of each property's page on a supported site (see the accepted URL formats), the currency you want prices in, and the stay length in nights. The run checks every check-in date for the next 365 days.
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"}, {"url": "https://www.booking.com/hotel/gb/quayside-inn.html", "name": "Quayside Inn"} ] }'body = { "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"}, {"url": "https://www.booking.com/hotel/gb/quayside-inn.html", "name": "Quayside Inn"}, ], } r = requests.post(f"{API}/runs", json=body, headers={**HEADERS, "Idempotency-Key": "client-42-2026-10-05"}) r.raise_for_status() run = r.json() print(run["id"], run["status"])const body = { 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" }, { url: "https://www.booking.com/hotel/gb/quayside-inn.html", name: "Quayside Inn" }, ], }; const created = await fetch(`${API}/runs`, { method: "POST", headers: { ...HEADERS, "Content-Type": "application/json", "Idempotency-Key": "client-42-2026-10-05" }, body: JSON.stringify(body), }); if (!created.ok) throw new Error(await created.text()); const run = await created.json(); console.log(run.id, run.status);
The answer is
202 Acceptedwith the new run. Keep itsid:{ "id": 1204, "reportId": 812, "reference": "client-42", "status": "queued", "complete": false, "requestsTotal": 730, "remaining": 730, "creditsCharged": 2, "dateFrom": "2026-10-05", "dateTo": "2027-10-04", "timeZone": "Europe/London", ... }-
referenceis your own label for what the run is about, such as a client or a market: 1 to 128 characters, with no commas. It doesn't have to be unique. Using the same one again starts a new report and run. The Lighthouse-compatible API uses the reference as its subscription ID, and reads the newest completed run with that reference. -
namenames the report, from 1 to 100 characters. -
Idempotency-Keymakes the call safe to repeat. If your connection drops and you send it again with the same key, Beacon returns the first answer instead of starting and charging a second run. Keys are forgotten after 24 hours. More in sending a write again. - Limits. Up to your company's limit of properties per run (50 by default), a stay length of 1 to 7 nights, and one of 30 currencies. The reference lists them all.
-
-
Wait for it to finish
A run checks 365 dates for each property, so it usually takes minutes rather than seconds. Ask for the run no more than once a minute, and stop when
completeistrue.curl https://api.pro.getbeacon.cloud/v1/runs/1204 \ -H "Authorization: Bearer $BEACON_KEY"
while True: r = requests.get(f"{API}/runs/{run['id']}", headers=HEADERS) r.raise_for_status() run = r.json() print(run["status"], run["remaining"], "checks left") if run["complete"]: break time.sleep(60)
let current = run; while (!current.complete) { await new Promise((done) => setTimeout(done, 60_000)); const res = await fetch(`${API}/runs/${run.id}`, { headers: HEADERS }); if (!res.ok) throw new Error(await res.text()); current = await res.json(); console.log(current.status, current.remaining, "checks left"); }
The run counts its progress in checks. A check is one property on one date. These counters change as it runs:
remaining: checks still to do. It counts down to 0.done: checks that found rooms.soldOut: checks where the property was sold out.past: checks for dates that had already passed by the time they were reached.restricted: checks where the property needs a longer minimum stay.failed: checks that couldn't be fetched. You can try these again for free with retry.
-
Download the rates
To take a whole run in one request, ask for ndjson, which is one JSON object per line. The first line describes the run. Every line after it is a row: one room and rate on one date, or a placeholder for a date with no rooms.
curl "https://api.pro.getbeacon.cloud/v1/runs/1204/results?format=ndjson" \ -H "Authorization: Bearer $BEACON_KEY" > run-1204.ndjson
import json r = requests.get(f"{API}/runs/{run['id']}/results", params={"format": "ndjson"}, headers=HEADERS, stream=True) r.raise_for_status() lines = (json.loads(line) for line in r.iter_lines() if line) run_info = next(lines) # the first line is the run rows = list(lines) # every row after it print(len(rows), "rows")
const res = await fetch(`${API}/runs/${run.id}/results?format=ndjson`, { headers: HEADERS }); if (!res.ok) throw new Error(await res.text()); const lines = (await res.text()).split("\n").filter(Boolean).map(JSON.parse); const [runInfo, ...rows] = lines; // the first line is the run console.log(rows.length, "rows");
Each row looks like this. A date with no rooms still has a row, with the room fields empty and
availabilitysaying why:sold out,not fetched(the date couldn't be fetched),past,not fetched yet, ormin stay N nights(the property needs a longer stay). So a sold-out day is part of the picture rather than a gap.{ "propertyId": 2291, "property": "Harbour View Hotel", "checkIn": "2026-10-06", "availability": "available", "room": "Double Room", "occupancy": 2, "price": 120.5, "originalPrice": 150.0, "currency": "GBP", "breakfastIncluded": true, "mealType": "breakfast", "freeCancellation": false, "onSale": false, "roomsLeft": 3, "cheapestOfDay": true, ... }Every field is explained in the field dictionary, which also has sample files to build against before you spend credits.
Prefer smaller pieces? Leave out
format=ndjsonand page through the results withcursor. Only need one price per property and day? Use rates instead, which picks it for you with the filters you set. -
Delete Beacon's copy (optional)
Once a run has completed and you've stored its rows, you can tell Beacon to delete them. The run and its counters stay, only the rows go. Use this if your own policy says rates should live in one place. After this, the run's results and rates answer
410 results_purged, and its failed checks can't be tried again. The Lighthouse-compatible API skips a run whose rows are deleted. It reads the newest completed run with the same reference that still has rows and that your key can see, or answersgeneral.missingif there isn't one.curl -X DELETE https://api.pro.getbeacon.cloud/v1/runs/1204/results \ -H "Authorization: Bearer $BEACON_KEY" \ -H "Idempotency-Key: delete-1204"
r = requests.delete(f"{API}/runs/{run['id']}/results", headers={**HEADERS, "Idempotency-Key": f"delete-{run['id']}"}) r.raise_for_status()
const deleted = await fetch(`${API}/runs/${run.id}/results`, { method: "DELETE", headers: { ...HEADERS, "Idempotency-Key": `delete-${run.id}` }, }); if (!deleted.ok) throw new Error(await deleted.text());