Skip to content
Octroi

StayParity: Octroi's first customer, in production.

StayParity tells a hotel when an online travel agency undercuts its direct price. Every comparison it makes rests on a tax line it reads from an Octroi release, through the same contract any customer gets.

Reviewed

Why the tax line decides an alert

StayParity compares a hotel's direct price with what Booking.com, Expedia and the sellers on Google Hotels quote for the same stay. Those prices don't arrive on the same footing. One includes VAT, another adds a city tax per person per night at checkout, and the rates change by season and whenever the law does.

So the tax line is the comparison. Read it wrong and a fair price looks like an undercut, or a real undercut hides inside the tax.

StayParity and Octroi are built by the same team. StayParity still gets no side door, only the contract any customer gets.

How StayParity reads Octroi

StayParity's production deployment reads Octroi's production the way any customer does: a bearer token, the public routes of the delivery contract, and the signature checked against a key it already holds. A daily sync runs this, and an operator can run it on demand:

  1. Ask /tax-data/v1/releases/latest for the newest release and the token's expiry. A release already cached ends the run.
  2. Fetch manifest.json and manifest.json.sig, and verify the signature over the manifest's bytes with the pinned public key.
  3. Parse the manifest. Its release must be the one latest named, and its calculator's major version the one StayParity runs.
  4. Fetch the changes, the licence and every country file the manifest lists. Each file's size and SHA-256 must match the manifest.
  5. Store each file as it's fetched, then record the release, its files and the signed manifest in one step. A release signed before the newest one cached is refused, so a replayed old release never becomes the newest, and so is a rollback.

Any failure deletes what the run stored, so a release is cached whole or not at all. The three newest releases are kept, with any release an applied law still names, and the rest deleted. StayParity's health view raises an alert at once when a release fails a check (its signature, a hash, the manifest, or an older release), after 72 hours without a good sync when Octroi can't be reached, and when the token is within 30 days of expiry.

Pinned, never fetched

Octroi serves its public key at /tax-data/v1/signing-key, but that comes from the same origin as the data, so trusting it would check nothing. StayParity holds the key in its own configuration. If Octroi rotates its key, StayParity's syncs fail as signature_invalid until the new key is set.

From a release to a price

A cached release changes nothing on its own. An operator applies a country from it, and only then does its law reach pricing. Applying reads the country's file back from storage and hashes it again; a file that no longer matches the SHA-256 the sync verified is refused and nothing is written.

Each hotel has a place in that law: a jurisdiction, a property type and, where the law has them, a class. For a stay, StayParity runs resolve and quote from @repo/tax-calculator, the calculator every customer gets, over the applied law:

  • resolve gives the place's jurisdictions and the charges in force on the night, with their rates.
  • quote prices a one-night stay for 2 adults and for 1, with no room price.
  • The VAT is the rate in force on the night at the nearest jurisdiction of the place that states one.
  • What the guest owes on top of the price is every line outside the price, with the VAT on it, in the hotel's currency.

One night in Bucharest

The figure below isn't typed by hand. This page's build reads Romania's law from the current release, runs the charges through the calculator and reads the VAT from the law, so every number is what a customer gets from the same data.

Bucharest · Tourist accommodation establishmentcomplete
VAT at ro, from 1 Aug 202511%
Special tourism promotion taxro.bucuresti.city-tax · 2026-01-01 · outside the priceev_960c32cdRON 10.00 per person per night
Owed outside the price, 2 adultsRON 20.00
Owed outside the price, 1 adultRON 10.00
fig. 1One night in Bucharest on 12 Nov 2026, from the current release at build time: the charges priced by @repo/tax-calculator, the VAT read from the release's law.

For 2 adults, that's RON 20.00 owed on top of the room price. It's the number StayParity reads for the stay, with the VAT, when it puts an OTA's price beside the hotel's.

When the law is unknown

StayParity fails closed. A hotel with no place in the law is refused at admission, so no check runs for it at all. A stay whose law can't be stated exactly gets no law, and a stay with no law is never compared: the check fails before any vendor call, and nothing is read as a zero tax. For example, a quote that has unknowns or is partial, a stay with no VAT in force, or a charge whose last version ended with nothing after it.

That last case matters. A law that stops a charge with no successor is read as unresearched, not as no charge, even where the calculator itself answers in full:

Bucharest · 1 Jan 2027
The calculator's quotecompleteRON 0.00
StayParity's readingno lawnot compared
fig. 2The same room on 1 Jan 2027: the release's last version of the special tourism promotion tax ends on 31 Dec 2026 and nothing follows it.

StayParity's website badge makes no claim for such a stay either, and no alert opens, moves or closes from a check that compared nothing.

What's live

The current release carries 12 countries. An operator can apply any of them to StayParity:

AT AustriaBG BulgariaCY CyprusCZ CzechiaDE GermanyES SpainFR FranceGR GreeceHR CroatiaIT ItalyPT PortugalRO Romania

Each country's places, charges and sources are on the coverage page.

Read it the way StayParity does.

Start with a token and the quickstart, or book a demo on a stay of yours.