East African hospitality market data as JSON. No key, no signup, no rate limit.
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
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.| Endpoint | What it holds |
|---|---|
| rate-index.json | Chain-linked, matched-sample published-rate index by market, with the full weekly series |
| rate-observations.json | Every individual rate observation behind the index — the full audit trail, with source and conversion note per row |
| advisories.json | Travel advisory levels by market from issuing governments |
| pipeline.json | Hotel development pipeline: chain benchmark plus individually sourced signings, openings and closures |
| mice.json | Confirmed conference and major-event calendar driving compression |
| cost-index.json | FX, fuel, energy and food inputs that move operator margin |
| rules-and-fees.json | Levies, park fees, visa rules and tax changes with effective dates |
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": { ... }
}
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.
| Field | Meaning |
|---|---|
levelComparable | If 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. |
matched | How 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. |
confident | False where too few distinct properties reported to treat the reading as stable. |
wowPct | Week-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. |
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.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())
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.