Switching from Lighthouse

If your reports, spreadsheets or code read rates from Lighthouse's v3 API, you can point them at Beacon. Three endpoints answer with the same filters, field names and rates CSV columns, so in most cases a request changes in two places: the base URL and the token. The hotelId values in the answers are Beacon's own, so anything you've keyed on Lighthouse's ids needs matching up again.

In short

Beacon can answer the same requests that your Lighthouse reports, spreadsheets and code already make. You change the web address and the key, and they carry on working.

The one new job is starting runs yourself, because Beacon captures rates when you ask for them, not on a schedule. If nobody at your company reads rates from Lighthouse's API, you can skip this page.

Want more than Lighthouse gives?

These endpoints keep working. Beacon's own /v1 API returns more on every rate, in an easy-to-read shape that suits code and LLMs. See Moving from Lighthouse to /v1.

What stays the same

  • The token goes in the same X-Oi-Authorization header, the one Lighthouse clients already send.
  • The paths are the same under a new base URL: /hotels, /rates and /roomtyperates.
  • The filters have the same names: subscriptionId, ota, los, bar, persons, mealType, roomType, fromDate, shopLength, compsetIds and format, with the same defaults for ota, los, bar, persons and compsetIds.
  • Rates come back with the same fields in the same order, and the rates CSV has exactly the same header line.
  • When there's no rate for a date, message says why with Lighthouse's codes, such as rates.soldout. Beacon doesn't send every Lighthouse code. A night that needs a longer minimum stay gives rates.restrictionlosN, Lighthouse's code, with the minimum number of nights as N.
  • roomType uses Lighthouse's own values on these endpoints. How Beacon reads them is in Room types.

Lighthouse terms on Beacon

In Lighthouse On Beacon
Subscription, subscriptionId A run reference, which you choose when you start a run
The subscription's own property The first property in the run
Competitors The other properties in the run
Competitor set One set, 1 "All", holding every competitor. As a rates filter, compsetIds=1 covers every property in the run, your own included.
Hotel ID, hotelId Beacon's own property ID, which is different from the Lighthouse one
Lighthouse's capture schedule Your own scheduled job that starts runs
API token A Beacon read key, in the same X-Oi-Authorization header

You decide when rates are captured

This is the main difference. Lighthouse checks rates for each subscription on its own schedule. Rate Capture captures rates when you start a run. Each run checks every check-in date for the next 365 days.

A Lighthouse subscription is a Beacon run reference. When you start a run with POST /v1/runs, you give it a reference. /rates?subscriptionId=client-42 then reads the newest completed run with that reference. Start a new run with the same reference and it takes over as soon as it completes.

So switching means adding one scheduled job that starts runs, as often as you want fresh rates. Each run costs one credit per property.

A company can have 5 runs queued or running at once, unless Beacon has raised your company's limit. Starting another answers 429 too_many_active_runs. If a job has more subscriptions than that, it should start the next run when one finishes.

A subscription reads only runs whose rows are still stored. If you delete a run's results, or your company's retention period removes them, the subscription reads the newest earlier run with the same reference whose rows still exist. If there isn't one, it answers general.missing. So if you delete every run's results once you've loaded them, the subscription has nothing to read.

Switch in three steps

  1. Create your keys

    In Beacon Pro, open Company, then the API tab. Create a Read and write key for the job that starts runs, and a Read key for the reports and tools that read rates. Choose All company runs for the read key so it can see runs started by the other key. See API keys.

  2. Start a run for each subscription

    Put your own property first and its competitors after it. The first property becomes "the hotel", and the rest become its competitors in competitor set 1.

    curl -X POST https://api.pro.getbeacon.cloud/v1/runs \
      -H "Authorization: Bearer $BEACON_WRITE_KEY" \
      -H "Idempotency-Key: 4211-2026-10-05" \
      -H "Content-Type: application/json" \
      -d '{
        "reference": "4211",
        "name": "Harbour View and 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"},
          {"url": "https://www.booking.com/hotel/gb/marina-lodge.html", "name": "Marina Lodge"}
        ]
      }'
    Keep your subscription IDs

    Use your existing Lighthouse subscription ID as the reference, as above. Your stored subscriptionId values then keep working, and /hotels returns subscription_id as a number, as it did before.

    A run usually takes minutes rather than seconds. Until the first run for a reference completes, /rates for that subscription answers with an empty list and general.missing. After that, the last completed run keeps answering while a new one is going.

  3. Point your client at Beacon

    Change the base URL and the token. In most cases everything else in the request stays as it is. Values Beacon doesn't support, such as a compsetIds list without 1, a roomType outside Lighthouse's list, a los over 45 or a currency other than the subscription's, answer 400. The differences that remain are in Differences from Lighthouse.

    import os, requests
    
    BASE = "https://api.mylighthouse.com/v3"BASE = "https://api.pro.getbeacon.cloud/lighthouse/v3"TOKEN = os.environ["LIGHTHOUSE_TOKEN"]TOKEN = os.environ["BEACON_READ_KEY"]
    rates = requests.get(
        f"{BASE}/rates",
        headers={"X-Oi-Authorization": TOKEN},
        params={"subscriptionId": 4211, "los": 1, "persons": 2},
    ).json()["rates"]

    In Excel or Power BI, create a parameter called BeaconReadKey that holds your read key, and refer to it as above.

Supported endpoints

Base URL: https://api.pro.getbeacon.cloud/lighthouse/v3. The three that read need only a read key, and count towards the key's limit of 600 reads a minute, unless Beacon has raised it. Starting a live shop needs a write key, see Live shop.

  • GET/hotelsOne entry for each subscription the key can read, with its competitors and one competitor set, 1 "All". Like Lighthouse, it returns every subscription when you leave out page. With page (1 or more) and per_page (default and most 100) it returns that page, with meta. A page past the last one answers 400.
  • GET/ratesThe lowest rate per property and arrival date for one subscription.
  • GET/roomtyperatesThe lowest rate per property, arrival date and room type, room types in alphabetical order.
  • POST/liveupdateStarts a live shop of a subscription: a fresh run, charged like POST /v1/runs.
  • GET/liveupdate/{id}How far a live shop has got.

otas in /hotels lists the sites of the run's properties.

Live shop

A live shop asks for fresh rates for one subscription. It is the same thing as starting a run, so you can also use POST /v1/runs directly, see Moving from Lighthouse to /v1. If your Lighthouse client expects something different, contact us.

  1. Start it. POST /lighthouse/v3/liveupdate with a write key and the subscription, as JSON ({"subscriptionId": "client-42"}) or in the query string. If both have it, the body wins. Beacon copies the report of the newest run with that reference that your key can see, whatever its status, and starts the copy. The answer is 202.
  2. Poll it. GET /lighthouse/v3/liveupdate/{id} with liveupdateId from the start answer, or follow statusUrl. A read key is enough. status is queued, running or completed.
  3. Read the rates. Once status is completed, GET /rates?subscriptionId=client-42 serves the new run, because the newest completed run of a reference is the one a subscription reads.
{
  "liveupdateId": 1310,
  "subscriptionId": "client-42",
  "status": "queued",
  "progress": {
    "total": 3650,
    "done": 0,
    "soldOut": 0,
    "failed": 0,
    "past": 0,
    "restricted": 0,
    "remaining": 3650
  },
  "requestedAt": "2026-10-15T09:30:00Z",
  "completedAt": null,
  "creditsCharged": 10,
  "statusUrl": "/lighthouse/v3/liveupdate/1310"
}

progress counts check-in dates the way a run does. requestedAt is when the run started, and completedAt is null until it has finished. Errors have Lighthouse's shape, message and errors.

  • Credits. A live shop is a normal run: one credit per property, taken when it starts, exactly as for POST /v1/runs. Nothing is refunded. Without enough credits the answer is 402 insufficient_credits.
  • Limits. It counts towards the company's runs in progress. Over the limit the answer is 429 too_many_active_runs.
  • Retries. Send an Idempotency-Key header, as on /v1. A repeat with the same key and subscription gives the first answer and charges nothing again.
  • Other refusals. 404 not_found for a subscription or live shop your key can't see, 422 when subscriptionId is missing, 403 scope_read_only for a read key on the start, and 405 for another method.

Filters for rates and room type rates

Filter Default On Beacon
subscriptionId Required The run reference to read.
ota bookingdotcom The site the rates come from: bookingdotcom, travelodge, premierinn or branddotcom, which is the Travelodge and Premier Inn sites. A property on a different site answers general.missing.
los 1 Stay length in nights, 1 to 45. Rates exist for the stay length the run captured, which is its nights.
bar false true keeps only the public flexible rate: free cancellation, and not a members-only or partner offer.
persons 2 Keeps rooms whose maximum occupancy is at least this many guests, 1 to 20. It filters rooms; prices are always for 2 adults.
mealType 0 0 any, 1 breakfast, 2 half board, 3 full board, 4 all inclusive, 5 room only. Each of 1 to 5 keeps only rates of exactly that meal plan, so 5 doesn't include half board, full board or all inclusive. A date with none of that plan answers rates.meal.missing.N.
roomType None parkandfly, dormitory, sharedbathroom, camper, villa, bungalow, suite, apartment, family, premium, budget, single or standard. Any other value answers 400. See Room types.
fromDate Today in the run's time zone First arrival date, YYYY-MM-DD.
shopLength To the end of the run Number of arrival dates, 1 to 365.
compsetIds 1 1, every property in the run, your own included. A comma-separated list that holds 1, such as -1,1, is answered as 1. A list without 1 answers 400.
currency None Must be the subscription's currency, else 400. Beacon doesn't convert: prices always come in the currency the run captured, shown in the currency field.
changeDays None Accepted and ignored. Change history is not returned, and changes is never sent.
format json json or csv.

What comes back

  • Every field, with its match in the Rate Capture API, is in the field dictionary, and there's a sample CSV.
  • hotelId is Beacon's ID for the property, not Lighthouse's ID. It is never null, and it stays the same in every run and report of your company for the same property URL, so you can key your own tables on it.
  • Each rate has the same fields as Lighthouse: hotelId, hotelName, extractDateTime, arrivalDate, los, value, currency, shopCurrency, cancellable, best_flex, the three tax flags, roomName, roomType, maxPersons, mealTypeIncluded and message.
  • mealTypeIncluded (the MealTypeIncluded column in CSV) is 1 Breakfast, 2 Half board, 3 Full board, 4 All-inclusive or 5 Room only. It is 0 when there is no rate. Lighthouse has no lunch category, so some rates are reported as the nearest code. A breakfast and lunch rate, and a lunch and dinner rate, are both reported as 2 (Half board), because each is two meals. A dinner-only or lunch-only rate is reported as 0, Lighthouse's "no meal type included". These codes are for compatibility only. Filtering with mealType=2 still returns only half board rates, and mealType=0 still means any.
  • When a date has no rate, message gives the reason, and the other fields are filled as Lighthouse fills them: value is 0, cancellable and best_flex are false, roomName and roomType are empty text, maxPersons is the persons you asked for and mealTypeIncluded is 0. When there is a rate, message is empty text. Check message to tell the two apart.
  • extractDateTime is never null. A date that was never fetched shows the time the run finished. currency and shopCurrency are the currency of the chosen rate, or the report's currency when there is none.
  • vatIncl and otherTaxesIncl are always null, because whether a price includes those taxes isn't captured. cityTaxIncl is true on Premier Inn rates, whose price includes city tax, and null for every other source.
  • The /hotels CSV has Lighthouse's 10 columns: HotelID,HotelName,HotelStars,CompetitorID,CompetitorName,CompetitorStars,CompsetID,CompsetName,SubscriptionID,BrandCode, with one row for each competitor in the competitor set. A subscription with no competitors has one row, with the competitor and set cells empty.
  • Errors come back as message and Lighthouse's errors list, each entry with status, code, title and detail. The codes are on Errors.
{"rates": [
  {"hotelId": 57, "hotelName": "Harbour View Hotel", "extractDateTime": "2026-10-05T08:00:33",
   "arrivalDate": "2026-10-06", "los": 1, "value": 99.0, "currency": "GBP", "shopCurrency": "GBP",
   "cancellable": true, "best_flex": true, "vatIncl": null, "cityTaxIncl": null, "otherTaxesIncl": null,
   "roomName": "Superior Double Room", "roomType": "premium", "maxPersons": 2, "mealTypeIncluded": 5, "message": ""},
  {"hotelId": 57, "hotelName": "Harbour View Hotel", "extractDateTime": "2026-10-05T08:00:36",
   "arrivalDate": "2026-10-07", "los": 1, "value": 0.0, "currency": "GBP", "shopCurrency": "GBP",
"cancellable": false, "best_flex": false, "vatIncl": null, "cityTaxIncl": null, "otherTaxesIncl": null,
"roomName": "", "roomType": "", "maxPersons": 2, "mealTypeIncluded": 0, "message": "rates.soldout"}
]}

Room types

In the Lighthouse app, each hotel sets the type of each room. Beacon classifies each room from its name instead, so the same room can get a different type from the one Lighthouse gives it. Check the types you filter or group by.

The roomType values are parkandfly, dormitory, sharedbathroom, camper, villa, bungalow, suite, apartment, family, premium, budget, single and standard. Each is Beacon's classification of the room from its name.

A roomType filter that matches no room with a rate on a date gives rates.roomtypes.unavailable for that date.

Why a date has no rate

Beacon narrows each property's rows for a date in this order. The first step that leaves nothing gives the message.

message Why
general.missing The los isn't the run's stay length, the ota isn't the property's site, or the date wasn't in the run. A date in the window you asked for with shopLength that the run doesn't cover also gives this.
rates.soldout The property was sold out that date.
rates.restrictionlosN The property needs a stay of at least N nights from that date, and the run's stay length is shorter. For example rates.restrictionlos4 means a minimum stay of 4 nights. When the site doesn't say how many nights, the message is general.missing.
general.missing The date couldn't be checked, was past, or hasn't been checked yet. Or every row was left out of comparisons (excludedReason).
rates.no_person.N No room takes at least persons guests.
rates.meal.missing.N No room left has exactly the meal plan you asked for with mealType 1 to 5, for example half board (mealType=2). If you asked by a name that has no code of its own, such as dinner, N is that name.
rates.no_bar bar=true and there's no public flexible rate.
rates.roomtypes.unavailable No room of the roomType asked for has a rate on that date.

Other Lighthouse endpoints under the base URL aren't available on Beacon. They answer 501 straight away, even without a key, so your client finds out at once. The message names the endpoint and the ones that are supported.

Differences from Lighthouse

These are the places where Beacon answers differently from Lighthouse. Check them against what your code relies on.

Most of these differences go away on /v1, which also returns more fields. See Moving from Lighthouse to /v1.

Topic Lighthouse Beacon What to do
Room types Set for each room in the Lighthouse app. Beacon's classification of the room from its name, such as premium, suite or standard. Check the types you filter or group by against your room names. The types are listed in Room types.
Meal codes mealType asks for rates that include that meal. mealTypeIncluded is 0 to 5. mealType matches exactly, so 5 doesn't include half board. Breakfast and lunch, and lunch and dinner, are reported as 2. Dinner-only and lunch-only are reported as 0. mealType also takes names, such as breakfast_lunch. A missing plan asked for by a name with no code of its own gives rates.meal.missing.dinner, not a number. If you read 2 as half board, you will also see breakfast and lunch, and lunch and dinner, rates there. If you read 0 as "no meal plan", you will also see dinner-only and lunch-only rates. To tell them apart, filter by name.
Persons persons sets how many guests the price is for: 1 to 10 on Booking.com, and 2 on other sites. persons only filters rooms by their maximum occupancy, 1 to 20. Every price is for 2 adults. maxPersons is the room's maximum, or the persons you asked for when there is no rate. Don't expect a price to change with persons. Use it to leave out rooms that are too small.
Length of stay Any los from 1 to 45 that the subscription shops. A run captures one stay length, its nights. los 1 to 45 is accepted, but any los other than the run's nights gives general.missing on every date. Start the run with the nights you need, and send the same los. For two stay lengths, start two runs with two references.
Hotel ids Lighthouse's own hotel ids. Beacon's property ids, in hotelId, id, competitors and competitor_ids. Match them to your own records once. An id stays the same for the same property URL in your company.
Subscription ids A number that Lighthouse gives each subscription. A run reference that you choose. subscription_id in /hotels is a number when the reference is all digits, and text otherwise. A reference with a leading zero, such as 007, stays text. A subscriptionId that matches nothing gives general.missing with an empty list, not an error. Use your Lighthouse subscription id as the reference, so your stored ids keep working.
Competitor sets Several sets for a hotel. One set, 1 "All", holding every competitor. compsetIds takes a comma-separated list, such as -1,1, and answers it as set 1 when the list holds 1. A list without 1 answers 400. Expect every competitor of the run whatever other ids you send.
OTA codes Many, such as bookingdotcom, expedia and branddotcom. bookingdotcom (the default), travelodge and premierinn. branddotcom matches the brand sites, which means Travelodge and Premier Inn properties. Any other code gives general.missing. The otas in /hotels list bookingdotcom, travelodge and premierinn, never branddotcom. Send ota=branddotcom for a hotel's own site, or the chain's code.
Tax flags vatIncl, cityTaxIncl and otherTaxesIncl are true or false. vatIncl and otherTaxesIncl are always null. cityTaxIncl is true on Premier Inn rates and null for every other source. Treat the VAT and other-tax status as unknown, and the city tax status as unknown unless the rate is from Premier Inn.
Stars and brand stars and brand_code are filled in /hotels. stars and brand_code come from the property's profile when Beacon knows it. stars is null when the profile has none. brand_code is Premier Inn's only (PI, PID, HUB or ZIP) and is empty for every other property. Use the values when they are set, and keep your own records for properties where they are empty.
A bad key 403 permission_denied. 401, for a key that is not recognised, revoked or expired. 403 is only for a request from an address the key doesn't allow. A request with no key is 401, as in Lighthouse. If your code reacts to 403 for a bad key, react to 401 too.
Endpoints that don't exist 404 endpoint_not_found for an unknown path. 501 not_supported for a Lighthouse endpoint that Beacon doesn't provide, with a message that names it. It needs no key. An address that is not under /lighthouse/v3 is 404 endpoint_not_found. Treat 501 as "not available on Beacon".
Live shop A live shop is started and followed with its own calls. POST /lighthouse/v3/liveupdate and GET /lighthouse/v3/liveupdate/{id}. A live shop starts a normal run, charged like POST /v1/runs, and needs a write key. Keep a write key for the job that starts live shops. If your Lighthouse client expects something different, contact us.
Endpoints that aren't available History, markets, reviews, ranking, parities, raw rates, hotel search and more. Not available, and they answer 501: /historic/rates, /historic/roomtyperates, /markets, /reviews, /ranking, /rankingsummaries, /parities, /demand, /marketinsight, /parityinsight, raw rates and hotel search. There is no OAuth either: you use a key. changeDays is accepted and ignored, and changes is never sent. Keep those calls on Lighthouse, or leave them out.
Rate limits 20 calls a day for each API and subscription, and 120 a minute. 600 reads a minute for each key, unless Beacon has raised it. Over it, the answer is 429 with Retry-After. Every answer to a request with a recognised key has X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, which is the seconds left in the current minute. Nothing, unless you send more than 600 requests a minute. See Limits.
Currency Converts the prices to the currency you ask for. Doesn't convert. A currency that is not the subscription's currency answers 400. Leave out currency, or send the one the run was started with.
Dates outside the run A record for every date asked for. With shopLength sent, every date from fromDate for that many days gets a record for each hotel, and a date the run doesn't cover gives general.missing. Without shopLength, you get only the run's own dates from fromDate. Send shopLength if your code expects one record for each date.
best_flex Judged within each room type on room type rates. The same on /roomtyperates, and on /rates when you send roomType. On /rates without roomType it is judged across all rooms of the property and date. Nothing. It matches Lighthouse.
roomType on room type rates Not a parameter. Accepted on /roomtyperates as an extra. It keeps one room type. Use it if you want one type. Leave it out to match Lighthouse.
Order of room types Not stated. Alphabetical for each hotel and date, such as budget before standard. Don't rely on the order if you read Lighthouse's output by position.
CSV numbers Two decimals, such as 100.10. No padding, such as 98.4 or 99. Read prices as decimals.
CSV row order Grouped by date. By hotel, then by date. Sort the rows if your code expects them by date.
CSV formulas Text is written as it is. A text cell that starts with =, +, - or @ gets a leading ', the formula guard, so a spreadsheet doesn't run it. Numbers are never changed. Strip a leading ' if you load room or hotel names into a database.

Your numbers won't match exactly

Rate Capture captures the rates a guest is shown online at the moment your run checks each date. The same room on the same date can differ from a figure captured at another time or in another way. Compare a few dates side by side when you switch, and expect small differences rather than identical columns.

Going further than the compatible API

The same runs can be read with the Rate Capture API, which goes beyond the lowest rate:

  • Every room and rate plan on every date, with sale badges, rooms left, partner offers that undercut the property's own rate, and whether the price is a members-only preview price. See results.
  • Every snapshot you've taken, listed by reference, so you can compare this week with last week for as long as you keep each run's rows.