Upload Your Business Data
Kaleidr can answer questions from your organization's own data — your properties, listings, rooms, rates, opening hours and documents — alongside its baseline knowledge of the world.
Two things are worth stating before anything else, because they shape every decision below.
Your data is served back to your organization only. It is isolated at the database level, not by application logic, and it never reaches an answer on the consumer Kaleidr Map. It is available on your own B2B surfaces and nowhere else.
A language model never writes your data. It reads the header of your export and a handful of sample rows and proposes a column mapping; deterministic code then applies that mapping to every row. A wrong mapping is a correction you make in a form. A model transcribing 500 rows would be 500 chances to invent a price that is later quoted back with a confident timestamp on it, and an invented price is indistinguishable from a real one.
Two ways in
The upload page. Sign in at kaleidr.com and open Vendor Data in your account area, beside Billing and API keys. It walks the same four steps as the API: pick a file, review the proposed mapping, preview what we understood, publish. This is the right route for a spreadsheet.
The API, documented below. Use it when your data already lives in a system that can push, or when you want uploads on a schedule.
Either way you need to be an owner or admin of the organization. This is organization-level data, not per-project — which is why it lives in your account area rather than inside Studio.
Authentication
All calls take a Cognito access token as a bearer, the same one your session already holds:
Authorization: Bearer <access token>
Not a platform API key. Platform keys identify an integration; uploading data
is an act of organization administration, so it authenticates as a person with
a role. Every path below is under https://api.kaleidr.com.
The shape of an upload
One upload produces one ingest (the job) and one release (the versioned
result). A release is draft until it publishes, and publishing supersedes the
previous one atomically — readers never see a half-applied update, and there is
no window where your organization has no data.
1. Propose a mapping
Send the front of your file — the header row and a few data rows, not the whole export.
| Method | Path | Body | Returns |
|---|---|---|---|
| POST | /inference-api/vendor/mapping/propose | { csv_head } | { mapping_spec, columns, sample_result, notes, errors[] } |
csv_head is capped at 16 KB, and the sample is sliced server-side — posting
a bigger file does not widen what the model sees.
sample_result is the part to read. The proposed mapping is dry-run over your
own sample rows before you ever see it, and sample_result reports what it
produced per section. A mapping that reads plausibly and yields nothing is
otherwise something you discover only after uploading.
If the proposal cannot be applied you still get 200 with errors populated,
not a 5xx. The mapping is editable and fixing one column is the whole
interaction, and errors reports everything knowable in one pass — a missing
required field and a column name your sheet does not have arrive together
rather than one per round trip.
What it catches before you upload: a section missing a field every row needs
(stay_date, price_amount, units_available, a place's name or
timezone), a {"const": …} timezone that is not a real IANA zone, a currency
const that is not three letters, and any object rule that is not a const
container. Each of those otherwise fails once per row after the upload.
Limited to 30 proposals per user per hour.
2. Stage a preview
| Method | Path | Body | Returns |
|---|---|---|---|
| POST | /shared-api/vendor-data/ingests | { filename, artifact, source, mapping_spec?, preview?, org_id? } | { ingest_id, release_id, status, status_url, dispatched } |
filenamemust end.jsonor.csv.artifactis the raw file text.sourceiscsv,jsonormanual.mapping_specis what step 1 returned, optionally edited. Omit it if you are sending an already-normalized JSON bundle or a places-only CSV.preview: truestages without publishing — strongly recommended for a first upload, and for any change to the mapping.org_idis optional; it defaults to the organization you administer.
Idempotency-Key is required on this call (8–128 characters), and it is
scoped per organization. Retrying with the same key returns the existing ingest
rather than creating a second one. Use a fresh key when you deliberately
re-stage after editing a mapping — that is a genuinely different request, and a
key derived from the file would hand you back the ingest built from the old
mapping.
3. Watch it
| Method | Path | Returns |
|---|---|---|
| GET | /shared-api/vendor-data/ingests/{ingest_id} | { status, attempts, row_counts, error_report, created_at, started_at, finished_at } |
status moves pending → running → succeeded or failed. On success
row_counts carries what we understood — "47 places, 312 rates" — and
error_report carries the rows we could not read, with the reason for each.
4. Publish
| Method | Path | Returns |
|---|---|---|
| POST | /shared-api/vendor-data/ingests/{ingest_id}/publish | { ingest_id, release_id, status, status_url, dispatched } |
Only for an ingest staged with preview: true that has succeeded.
Publishing re-runs the job rather than flipping a flag. It re-reads the same artifact and re-applies the same mapping, so counts and identifiers match by construction — but geocoding is a live call, so a coordinate the provider has since corrected can differ from the one in your preview.
Do not send an Idempotency-Key here. This endpoint does not accept one,
and it does not need one: a second publish of the same ingest cannot happen. If
your first request timed out on your side but succeeded on ours, the retry
returns 409 — ingest_is_not_a_preview, or publish_already_in_progress if
two attempts overlap exactly. Both mean your publish is already under way.
Treat them as success, not as an error to retry.
Listing releases
| Method | Path | Returns |
|---|---|---|
| GET | /shared-api/vendor-data/releases | [{ id, status, published_at, superseded_at, created_at }] |
Optionally filtered by ?status=. Exactly one release per organization is
published at a time.
Previews expire
A preview you never approve is retired after 14 days, measured from when it
finished staging. It is marked superseded rather than deleted, so the upload
stays in your history — but it can no longer be published, and you would upload
again. Publishing an expired preview returns 409 no_previewed_release_to_publish.
Field notes
timezone is required on every place, and it matters. Rate and availability
answers are computed in the property's local calendar — "tonight" at a Sydney
hotel is a different day from "tonight" on the server. If your export genuinely
has no timezone column, map a constant explicitly:
{ "timezone": { "const": "UTC" } }
Write that deliberately rather than letting it default. UTC is a correct answer for a property that keeps UTC hours and a subtly wrong one for a property that does not, and the wrongness shows up only at the edges of a stay window.
Omitting timezone, or naming a zone that does not exist, is rejected at the
propose step rather than after the upload.
Supply coordinates when you have them. A place with lat/lng is used as
given — your coordinates are authoritative and we will not re-derive them. A
place with only an address is geocoded, and a match we are not confident in is
refused rather than guessed: a property placed wrongly on a map is worse than
one reported as unresolvable. Half a coordinate is treated as a typo, not a gap.
Re-uploading is cheap and safe. Identifiers are derived from the columns your mapping names as identifying, so the same export produces the same references — re-uploading updates rather than duplicating.
Errors
| Status | Meaning |
|---|---|
| 401 | Missing or expired access token |
| 403 | Your organization is not enabled for data upload |
| 404 | No such ingest for your organization |
| 409 | The ingest is not in a state that allows this call — see the publish notes above |
| 413 | The file exceeds the upload limit |
| 422 | The file or mapping could not be read; the body names the rows and reasons |
| 429 | Rate limited — retry after a pause |
What is coming
The API accepts a complete bundle today. Once a mapping is a stored, reusable
object it will also accept your own export plus a mapping id, so an
integration can post the file it already produces without reshaping it first.
A delta API — upsert and delete by natural key, with a since cursor — follows
the same route.