Public API

East African hospitality market data as JSON. No key, no signup, no rate limit.

← Back to the Pulse

How it works

Every dataset behind this publication is available as a static JSON file. There is no authentication, no API key and no quota — these are flat files served over HTTPS with permissive CORS, so you can fetch them directly from a browser, a notebook, a dashboard or a spreadsheet.

curl https://eahospitalitypulse.com/api/v1/rate-index.json

Start at the endpoint index, which lists everything available and its size:

https://eahospitalitypulse.com/api/v1/index.json
Please cache. These files change a few times a day at most. Read meta.generated and re-fetch on a sensible interval rather than polling. There is no rate limiter, which means abuse degrades the service for everyone rather than just for you.

Endpoints

EndpointWhat it holds
rate-index.jsonChain-linked, matched-sample published-rate index by market, with the full weekly series
rate-observations.jsonEvery individual rate observation behind the index — the full audit trail, with source and conversion note per row
advisories.jsonTravel advisory levels by market from issuing governments
pipeline.jsonHotel development pipeline: chain benchmark plus individually sourced signings, openings and closures
mice.jsonConfirmed conference and major-event calendar driving compression
cost-index.jsonFX, fuel, energy and food inputs that move operator margin
rules-and-fees.jsonLevies, park fees, visa rules and tax changes with effective dates

Response shape

Every endpoint returns the same envelope: a meta block describing provenance and licence, and a data block with the payload.

{
  "meta": {
    "endpoint":    "https://eahospitalitypulse.com/api/v1/rate-index.json",
    "description": "EA Pulse Rate Index — chain-linked, matched-sample ...",
    "source":      "Weekly direct-booking-engine observations ...",
    "generated":   "2026-08-09T12:00:00Z",
    "publisher":   "EA Hospitality Pulse",
    "docs":        "https://eahospitalitypulse.com/api.html",
    "methodology": "https://eahospitalitypulse.com/methodology.html",
    "licence":     "Free for reasonable use with attribution ...",
    "version":     "v1"
  },
  "data": { ... }
}

Read the confidence flags before you display anything

This is the part that matters most, and the part most integrations get wrong. The Rate Index ships its own caveats in the payload, and they are there to be honoured rather than stripped.

FieldMeaning
levelComparableIf false, do not compare rate levels across properties in that market. The meal bases differ — a room-only rate and a fully-inclusive safari rate are not the same quantity. Movement is still valid; levels are not.
matchedHow many properties had a rate in both this period and the previous one. The index is matched-sample, so this is the real sample size behind a movement — not n.
confidentFalse where too few distinct properties reported to treat the reading as stable.
wowPctWeek-on-week change. Null means we are withholding it, usually because the matched sample is too thin or the interval is irregular. Null is a decision, not a gap.
A worked example. At the time of writing, the coast market shows index: 110.5 but matched: 4 and wowPct: null. Four matched pairs with wide dispersion is not a 10.5% market move, and the index deliberately withholds the headline figure. If your dashboard renders that as "Kenyan coast rates up 10.5%", your dashboard is wrong and we would rather you did not ship it.

Example: fetch and render safely

const r = await fetch('https://eahospitalitypulse.com/api/v1/rate-index.json');
const { meta, data } = await r.json();

for (const [key, m] of Object.entries(data.markets)) {
  // Only show a movement when the publisher is willing to stand behind it
  if (m.wowPct === null || !m.confident) {
    console.log(`${m.label}: baseline only (matched=${m.matched})`);
    continue;
  }
  console.log(`${m.label}: ${m.wowPct > 0 ? '+' : ''}${m.wowPct}% WoW`);
}

console.log(`Source: EA Hospitality Pulse, generated ${meta.generated}`);

Python, for the same data into a dataframe:

import pandas as pd, requests

obs = requests.get('https://eahospitalitypulse.com/api/v1/rate-observations.json').json()
df = pd.DataFrame(obs['data'])
df['rate_usd'] = pd.to_numeric(df['rate_usd'])

# every row carries its own source and conversion note
print(df[['observed_date','market','property','rate_usd','basis','channel']].head())

Terms of use

Shapes may change between versions; breaking changes get a new version path (/api/v2/) rather than being made in place. If you are building something substantial, email ceo@eahospitalitypulse.com and we will tell you before anything moves.