Chora

The context layer for travel booking pages.

Chora is grouped into six topics — one per section a guest cares about when they’re planning a stay. Pick the topics you need, compose them in your booking flow, or embed the whole thing as one HTML widget.

Topics

The shared envelope

Every Chora JSON endpoint returns the same envelope, so you can compose sections uniformly:

{
"location": { "lat": 51.5074, "lng": -0.1278 },
"applicable": true,
"sources": [
{ "provider": "Weather Provider", "url": "https://...", "license": "CC BY 4.0" }
],
"meta": {
"cached_at": "2026-09-14T18:00:00Z",
"fresh_until": "2026-09-14T18:10:00Z",
"partial": false,
"request_id": "req_abc123"
},
"...": "topic-specific payload"
}

Design principles

Applicable, not empty. Every endpoint returns an applicable flag. Marine data on an inland coord returns applicable: false. Wildlife data in a low-coverage rural area returns applicable: false. Callers hide the section rather than branching on an error.

Facts, attributed. Every response carries a sources[] array naming the upstream provider and license. We republish only facts — never editorial content or copyrighted images.

Cached at the edge. Every response carries a meta.fresh_until timestamp. Weather refreshes every 10 minutes; MICHELIN monthly; festivals annually. You never need to think about upstream rate limits.

Booking-safe. No travel advisories, no safety warnings, no cost-of-living data. Nothing in a Chora response would discourage a booking.

Composition

Every JSON endpoint is independent. Fan out with Promise.all and filter to what applies:

const params = new URLSearchParams({ lat, lng, start, end }).toString();
const responses = await Promise.all([
fetch(`/v1/pulse/weather?${params}`, { headers }),
fetch(`/v1/pulse/astronomy?${params}`, { headers }),
fetch(`/v1/pulse/michelin?lat=${lat}&lng=${lng}`, { headers }),
fetch(`/v1/pulse/events?${params}`, { headers }),
]).then(rs => Promise.all(rs.map(r => r.json())));
const applicable = responses.filter(r => r.applicable);

Or embed the pre-composed HTML widget in one <iframe> — see Embed the widget.