Dataset contracts — config-based API slices
The standard Journeys API is a general-purpose surface: catalog, baskets, pricing, payment, and booking, designed for building storefronts, advisor portals, and conversational experiences. Some integrations need something different — a partner already has a schema, and wants your catalog and pricing delivered in their wire shape rather than adapting their systems to yours.
Dataset contracts solve this without a second booking stack. A dataset is a configured, read-optimized slice of the same commerce engine, declared in journey.yaml and served as versioned HTTP resources. The platform maps its canonical catalog and pricing data into the partner's schema through a named format adapter — configuration selects the format; no tenant-specific code is written or forked.
Same engine, two surfaces:
Kaptio commerce platform (catalog, pricing, inventory)
│
├── Journeys API /api/journeys/v1/* ← storefronts, portals, AI agents
│
└── Datasets /api/journeys/v1/datasets ← partner feeds in the partner's schema
Configuration
A dataset is declared per journey in journey.yaml:
datasets:
- id: partner-feed
format: ndc-offerprice-v1
partition_by: package
schema_file: schemas/partner/packagesearch_request.schema.json
| Key | Meaning |
|---|---|
id | The dataset's URL segment: /datasets/{id}. |
format | A registered format adapter — the mapping from platform data to the partner's wire shape (for example ndc-offerprice-v1, targeting an NDC 21.3 OfferPrice response). Unknown format ids fail configuration load, not requests. |
partition_by | How the dataset splits into independently fetchable partitions. package produces one partition per package. |
schema_file | A JSON Schema in your configuration repository, served back to the partner from the dataset itself so both sides validate against the same contract. |
The HTTP surface
GET /api/journeys/v1/datasets/{datasetId} — index: which partitions exist, when they were built
GET /api/journeys/v1/datasets/{datasetId}/packages/{code} — one rendered partition, with an ETag
GET /api/journeys/v1/datasets/{datasetId}/bulk — every partition merged into one document, with a dataset-level ETag
GET /api/journeys/v1/datasets/{datasetId}/schema — the response JSON Schema (?type=request serves the request schema)
POST /api/journeys/v1/datasets/{datasetId}/quote — a real-time price quote in the partner's shape
Every dataset route requires an x-api-key header. Keys are per tenant, issued by Kaptio, and delivered through a secure channel — dataset routes carry commercial data, so they are authenticated from day one rather than using the session model of the hosted booking UI.
Request and response shapes are defined by the dataset's own schemas: GET .../schema serves the exact JSON Schema the partitions and quote responses validate against, and GET .../schema?type=request serves the quote request contract, so both sides build from the same documents. The quote endpoint accepts the partner's own request formats (including tolerant, envelope-style shapes with correlation-id echo) alongside the platform's strict contract — configuration decides which schemas apply, no code fork.
Multi-currency: a dataset is warmed in every currency its configuration lists; each partition carries per-currency price entries side by side, and quotes price in any requested currency (natively priced or derived through the tenant org's currency conversion setup).
Bulk retrieval: GET .../bulk returns the whole dataset as one document — every package's offers and price lists merged, with a dataset-level ETag so a feed consumer polls one URL and re-downloads only on change. The document is bounded by design: a dataset serves the packages its configuration scopes (typically tens of products), not an operator's full catalog, so the payload stays in single-digit megabytes. Catalog-scale distribution is a different integration pattern (delta synchronisation) agreed with Kaptio separately — the bulk route intentionally does not paginate. All routes return a structured JSON error body on failure:
| Status | Meaning |
|---|---|
401 | Missing or wrong x-api-key for the tenant. |
404 | Unknown dataset id, or a partition that is not (yet) in the warm store — see the freshness model below. |
400 | Quote validation failure (for example a missing service_level_id), with the failing field named in the error body. |
429 | Quote rate limit exceeded for the tenant. Back off and retry. |
304 | Not an error — the conditional partition request matched the ETag; reuse your cached copy. |
Freshness and caching
Partitions are not rendered per request from live upstream calls. A warm engine rebuilds each package's source document on a rolling cycle, and the format adapter's rendering is memoized by content hash — unchanged content is never re-rendered and never re-downloaded:
- Poll the index, don't hardcode partitions. The index lists exactly which partitions are currently available and when each was built.
- Use
If-None-Match. Every partition response carries anETag; a conditional request returns304 Not Modifiedwhen nothing changed. - Expect a short cold window after platform deploys. The warm store rebuilds from scratch; partitions reappear on the index as they complete.
Quote responses are the bare partner document — the body validates against the published response schema exactly as served, with pricing provenance in the X-Price-Source response header (warmed when served from the rebuilt store, live when priced in real time).
Quotes take the fastest honest path: when the requested currency and occupancy match the basis the dataset was last rebuilt with, the quote serves from that store (X-Price-Source: warmed — the same price the partition shows, answered in milliseconds); any other combination prices in real time against the platform (X-Price-Source: live). Occupancy, service level, and date are honored on every call either way, and the result is returned in the partner's schema. Quote routes are rate-limited per tenant.
When to use which surface
| Standard Journeys API | Dataset contract | |
|---|---|---|
| Schema | Kaptio's published contract | The partner's schema |
| Best for | Building UIs: storefronts, advisor portals, chat and AI-agent experiences | Feeding an existing partner system that already speaks its own format |
| Writes | Baskets, payments, bookings | Read-only, plus real-time quotes |
| Auth | Tenant session model | Per-tenant x-api-key |
| Freshness | Live per request (with platform caching) | Rolling warm rebuilds + ETag; quotes warmed-or-live (X-Price-Source header) |
The two surfaces share the same underlying catalog, pricing, and inventory — there is one engine underneath. They can differ temporarily: dataset partitions are snapshots rebuilt on a rolling cycle, while the standard API prices per request, so a change in the platform reaches the standard API first and the dataset on its next rebuild. Quotes on the dataset surface either match the latest rebuild (warmed) or price in real time (live) — the X-Price-Source header says which.
Adding a new format
Format adapters are platform code, versioned and validated (a format ships with fixture-based tests against the partner's schema). If a partner's schema is not yet in the registry, Kaptio adds the adapter once and any tenant can then select it in configuration. Talk to your Kaptio contact about lead times for a new format.
For endpoint definitions of the standard surface, see the Journeys API reference; for how the warm engine and caching behave under load, see Performance.