Skip to main content

Endpoints

Every public endpoint of the Kaleidr platform API, one section each.

  • Base URL: https://api.kaleidr.com/inference-api/b2b/v1
  • Credential: Authorization: Bearer … or X-Api-Key: …. From a backend, a server key (kld_sk_live_…). From a browser, a session token (kld_sess_live_…). A publishable key is accepted only by the session exchange. See Auth and scopes.
  • Bodies: JSON, UTF-8. Send Content-Type: application/json on every POST. A request body can be up to 2 MiB, or 5 MiB on the /design/* endpoints. A larger one is refused with 413 request_too_large.
  • Errors: a 4xx or 5xx with a JSON body, described in Errors. One endpoint reports failure with a 200: see Directions.

The examples use a server key in an environment variable:

export KALEIDR_KEY="kld_sk_live_…"

Summary​

EndpointPurposeScopeCharged
POST /sdk/sessionsExchange a publishable key for a sessionThe product's scopeOne map load for tile; otherwise nothing
POST /sdk/share-sessionsExchange a published map's id for a chat sessionnoneNothing
POST /chat/control/streamAsk a question; stream the answer with places, actions and routesaiAI credits, by tokens used
POST /chat/control/routeDirections between known coordinatesaiNothing
GET /retrieval/geocode/forwardTurn typed text into places and addressesaiAI credits, flat fee
POST /spotWhat is at a point: the place, its address, its regions, and optionally its contentaiAI credits, flat fee
POST /spot/overview/streamStream a short overview of one thing from /spotaiAI credits, by tokens used
GET /retrieval/poi/enrichDeprecated, use /spot. Details and photos for one placeaiAI credits, flat fee
POST /chat/summary/streamDeprecated, use /spot/overview/stream. Stream a short summary of a place or buildingaiAI credits, by tokens used
POST /chat/button/streamStream an answer to one question about one placeaiAI credits, by tokens used
POST /design/analyzeRecommend a map design for a table of datadesignAI credits, flat fee
POST /design/apply-directBuild a map layer from a table and a chosen styledesignNothing
GET /design/styles, /design/samples, /design/themesDesign catalogsdesignNothing

How charges, holds and limits work is in Quota and rate limits.

Every runtime endpoint can return the errors in Errors shared by all runtime endpoints. Each section below lists only what is specific to it.


POST /sdk/sessions​

Exchanges a publishable key for a short-lived session token bound to the calling origin and one product. The SDK calls this for every keyed mount. Call it yourself only from your own browser client.

Auth: publishable key. Requires an Origin header (a browser adds it). Charge: one map load when product is tile; nothing otherwise.

FieldTypeRequiredNotes
productstringYeschat, editor, viewer or tile.
curl -s https://api.kaleidr.com/inference-api/b2b/v1/sdk/sessions \
-H "X-Api-Key: kld_pk_live_…" \
-H "Origin: https://www.example.com" \
-H "Content-Type: application/json" \
-d '{"product":"chat"}'
{
"session_token": "kld_sess_live_eyJhbGciOi…",
"expires_in": 900,
"product": "chat",
"basemap_style_id": null
}

basemap_style_id is your organization's default basemap, or null when none is set.

StatusCodeWhen
401Invalid or missing API keyThe key is unknown, revoked or expired.
403publishable_key_requiredThe key is a server or legacy key.
403origin_requiredNo Origin header.
403origin_not_allowedThe origin is not in the key's allowed origins.
403product_not_allowedThe key's allowed products exclude product.
403tier_capability_not_allowedYour plan does not include the product or its scope.
403insufficient_scopeThe key was created without the product's scope.
422validationproduct is missing or not one of the four values.
429rate_limitedToo many exchanges for this key. Retry-After: 5.
429map_load_quota_exceededtile only: the map-load allowance is used up.
404sessions_disabledSessions are switched off on the platform. Contact support.
503sessions_not_configuredA platform fault. Retry; contact support if it persists.

The lifetime, the refresh rule and the order of these checks are in Auth and scopes: session exchange.

POST /sdk/share-sessions​

Exchanges the id of a published map for a chat session, so the map's visitors can use chat with no key in the page. The map's Kaleidr AI tile in Studio must turn on the feature asked for.

Auth: none. Requires an Origin header. Charge: nothing; chat calls made with the session are charged to the map owner's organization.

FieldTypeRequiredNotes
share_idstringYes1 to 128 characters: letters, digits, _ and -.
featurestringNocontrol_tower (the default), the map's Ask Kaleidr panel, or popups, the AI chat in its popups. Each is granted only while its switch is on.
curl -s https://api.kaleidr.com/inference-api/b2b/v1/sdk/share-sessions \
-H "Origin: https://www.example.com" \
-H "Content-Type: application/json" \
-d '{"share_id":"your-map-id","feature":"control_tower"}'
{ "session_token": "kld_sess_live_eyJhbGciOi…", "expires_in": 300, "product": "chat" }
StatusCodeWhen
403origin_requiredNo Origin header.
404not_foundNo published map or share link has this id, or the share link's map is unpublished.
403chat_disabledThe map's Kaleidr AI does not turn on the feature asked for.
403origin_not_allowedThe page is not in the map's allowed domains.
403owner_has_no_organizationThe map's owner has no organization.
403tier_capability_not_allowedThe owner's plan does not include chat.
422validationshare_id is missing or has other characters, or feature is neither value.
429rate_limitedToo many exchanges for this map or from this address. Retry-After: 5.
404sessions_disabledSessions are switched off on the platform.
503share_grant_unavailable, sessions_not_configuredA platform fault. Retry.

See Kaleidr AI on a published map.


POST /chat/control/stream​

Asks a question and streams the answer: prose as it is written, the places it mentions with coordinates, map actions, and routes. This is the endpoint behind Kaleidr Chat.

Auth: server key, session token or legacy key. Scope: ai. Charge: AI credits, the tokens the answer used. Response: text/event-stream, described in the SSE wire contract.

FieldTypeRequiredDefaultNotes
messagesarray of { role, content }No[]The conversation so far, oldest first, ending with the user's new message. role is user or assistant. Messages with any other role, including system, are ignored, as are messages with empty content. The question answered is the last user message. Size limits are below.
session_idstringNogeneratedAn id for the conversation: 1 to 128 characters from letters, digits, _, ., : and -. Send your own stable id and repeat it on every turn. If you omit it, the server generates one for this request and returns it in the phase and start events.
locationobject { lat, lng }NononeWhere the user is looking: the map center. Used to interpret "near here".
map_zoomnumberNononeThe map's zoom level.
langstringNo"en"The language to answer in, as a BCP 47 tag such as fr or pt-BR. At most 35 characters.
app_snapshotobjectNononeAccepted. Not needed; leave it out.
auto_actionsbooleanNononeAccepted. Not needed; leave it out.

Unknown fields are accepted and ignored. There is no field for system instructions, a persona, tools or your own context: the request carries the conversation, a location and a language.

The server does not store the transcript for you. Send the earlier turns in messages each time, together with the same session_id.

A session_id is yours alone. The short-term context of a conversation is kept per organization, so another organization that sends the same id neither sees nor changes your conversation. Within your own organization, give each end user's conversation its own id.

Request limits​

A request over one of these limits is refused with 422 before any answer is produced, and nothing is charged. The body is {"detail": "<code>"}.

LimitValueCode
Messages in messages400too_many_messages
Length of one message's content64,000 charactersmessage_too_long
Length of all content values added together200,000 charactersconversation_too_long
session_id1 to 128 characters: letters, digits, _, ., :, -invalid_session_id
lang35 characterslang_too_long

Every message counts toward these limits, including the ones that are ignored. A long conversation reaches the total length before it reaches the message count, so send the most recent turns and drop the oldest: see Reading the stream.

curl -N https://api.kaleidr.com/inference-api/b2b/v1/chat/control/stream \
-H "Authorization: Bearer $KALEIDR_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"session_id": "trip-42",
"messages": [{"role": "user", "content": "Where is the Louvre, and what is next to it?"}],
"location": {"lat": 48.8566, "lng": 2.3522},
"map_zoom": 12,
"lang": "en"
}'

-N turns off curl's output buffering so frames print as they arrive. A shortened response, with illustrative values:

event: phase
data: {"type": "phase", "phase": "thinking", "session_id": "trip-42"}

: keep-alive

event: start
data: {"type": "start", "session_id": "trip-42", "purpose": "control"}

data: {"text": "The [[Louvre Museum]] is on the right bank of the Seine"}

event: place
data: {"name": "Louvre Museum", "coordinates": {"lat": 48.8606, "lng": 2.3376}, "category": "museum"}

data: {"text": ", with the [[Tuileries Garden]] directly to its west."}

event: place
data: {"name": "Tuileries Garden", "coordinates": {"lat": 48.8635, "lng": 2.3275}, "category": "park"}

event: early_actions
data: {"type": "early_actions", "actions": [{"type": "map.fitBounds", "payload": {"coordinates": [[2.3376, 48.8606], [2.3275, 48.8635]], "padding": 50, "maxZoom": 15, "duration": 1000}}], "places_count": 2, "is_partial": true}

event: end
data: {"type": "end", "full_text": "The [[Louvre Museum]] is on the right bank of the Seine, with the [[Tuileries Garden]] directly to its west.", "places": [{"name": "Louvre Museum", "coordinates": {"lat": 48.8606, "lng": 2.3376}, "category": "museum"}, {"name": "Tuileries Garden", "coordinates": {"lat": 48.8635, "lng": 2.3275}, "category": "park"}], "actions": [], "question_type": "…", "response_time": 6.4, "total_tokens": 3120}

event: quota
data: {"type": "quota", "product": "b2b", "meter": "b2b.tokens", "tier": "pro", "used": 184223, "limit": 5000000, "reset_at": "2026-11-01T04:00:00+00:00", "period": "month"}
StatusCodeWhen
413request_too_largeThe request body is larger than 2 MiB.
422validationThe body is not valid JSON, or a field has the wrong type.
422too_many_messages, message_too_long, conversation_too_long, invalid_session_id, lang_too_longThe request is over a request limit.
429quota_exceededThe AI-credit pool, or this key's monthly cap, cannot cover the call.

The quota event in the example is what a server key receives. A stream read with a session token carries a shorter one: see the quota event.

After the stream has started, a failure arrives as an error event, not as a status. To build a client, follow Reading the stream.

POST /chat/control/route​

Plans a route between coordinates you already have. No question is asked and no text is generated.

Auth: server key, session token or legacy key. Scope: ai. Charge: nothing. The call still needs headroom in the AI-credit pool to be admitted.

FieldTypeRequiredDefaultNotes
placesarray of objectsYes, at least two[]Each is { lat, lng, name? } with numeric lat and lng at the top level of the object. label is accepted in place of name. An entry without numeric lat and lng is skipped without an error.
profilestringNo"driving"driving, walking, cycling or transit. Any other value routes as driving.
raw_querystringNo"Show directions"The words the user typed, if any.

The first place is the start and the last is the destination.

The shape of a place differs from the stream

A place event on the chat stream nests its position: { "coordinates": { "lat": …, "lng": … } }. This endpoint wants lat and lng directly on the object. Passing a stream place through unchanged gives you an error answer, because no entry has usable coordinates.

curl -s https://api.kaleidr.com/inference-api/b2b/v1/chat/control/route \
-H "Authorization: Bearer $KALEIDR_KEY" \
-H "Content-Type: application/json" \
-d '{
"places": [
{"name": "Louvre Museum", "lat": 48.8606, "lng": 2.3376},
{"name": "Tuileries Garden", "lat": 48.8635, "lng": 2.3275}
],
"profile": "walking"
}'

Success is a 200 with an action (values are illustrative):

{
"action": {
"type": "map.showRoute",
"payload": {
"id": "control-directions",
"label": "Directions",
"profile": "walking",
"coordinates": [[2.3376, 48.8606], [2.3352, 48.8619], [2.3275, 48.8635]],
"distance_m": 870,
"duration_s": 640,
"turn_by_turn": [{ "text": "Walk west on Rue de Rivoli." }],
"attribution": {
"text": "© OpenStreetMap contributors",
"url": "https://www.openstreetmap.org/copyright",
"license": "https://opendatacommons.org/licenses/odbl/"
}
}
},
"route": { "…": "the full planning result the action was built from" }
}

Draw action.payload.coordinates, a list of [lng, lat] pairs. distance_m is meters and duration_s is seconds. Show attribution.text, linked to attribution.url, wherever you draw the route.

For profile: "transit", payload.transit carries a one-seat journey: a walk, one ride, a walk. It has no attribution at the top of the payload; the operator's credit is in payload.transit.attribution. Show it wherever you show the journey.

This endpoint reports failure with HTTP 200​

A route that cannot be planned is not a 4xx. The response is 200 with an error string and no action:

{ "error": "Route planning failed." }

This happens when the planner cannot connect the places and when planning fails for any other reason. When fewer than two places have usable coordinates the text is At least two resolved places are required to build a route. The text is written to be shown to a user as it is, so branch on the presence of error, not on its wording. A transit trip that no direct ride serves answers the same way, with the reason in words and three extra fields, reason, covered and available.

Branch on the body, not the status:

const res = await fetch(`${API}/chat/control/route`, { method: 'POST', headers, body });
if (!res.ok) throw new Error(`route request refused: ${res.status}`); // 401, 403, 422, 429
const data = await res.json();
if (data.error) {
showMessage(data.error); // a 200 that is a failure
} else {
drawLine(data.action.payload.coordinates);
}

Authentication, scope, validation, quota and rate-limit failures are still ordinary 401, 403, 422 and 429 responses.

GET /retrieval/geocode/forward​

Turns typed text into matching places and street addresses. It is built for a search box or a From/To field.

Auth: server key, session token or legacy key. Scope: ai. Charge: AI credits, a flat fee per call, charged even when results is empty.

ParameterTypeRequiredDefaultNotes
qstringYes1 to 200 characters: a place name, a street address with a house number, or a typed lat,lng.
near_latnumberNo−90 to 90. Ranks nearer matches first. Used only together with near_lng.
near_lngnumberNo−180 to 180. Used only together with near_lat.
limitintegerNo51 to 20.
modestringNoA travel mode (driving, walking, cycling, transit). A short trip ranks nearby matches higher.
curl -s -G https://api.kaleidr.com/inference-api/b2b/v1/retrieval/geocode/forward \
-H "Authorization: Bearer $KALEIDR_KEY" \
--data-urlencode "q=20 W 34th St, New York" \
--data-urlencode "limit=3"
{
"query": "20 W 34th St, New York",
"results": [
{
"lat": 40.7484,
"lng": -73.9857,
"display_name": "20 W 34th St, New York, NY 10001",
"type": "address",
"source": "…",
"id": "…",
"confidence": "exact",
"attribution": {
"dataset": "us/ny/city_of_new_york",
"license": "…",
"attribution": "Address data from the NYC Department of City Planning."
}
}
],
"attribution": {
"text": "Overture Maps Foundation, overturemaps.org",
"licenses": [
"https://cdla.dev/permissive-2-0/",
"https://www.apache.org/licenses/LICENSE-2.0",
"https://opensource.foursquare.com/places-notice-txt/",
"https://docs.overturemaps.org/attribution/"
],
"sources": [
{
"dataset": "us/ny/city_of_new_york",
"license": "…",
"attribution": "Address data from the NYC Department of City Planning."
}
]
}
}

Every result has lat, lng, display_name, type and source. The rest are present when they apply:

Result fieldMeaning
idOn a street address: Kaleidr's identifier for that door. It stays the same across data updates, so you can store and compare it.
confidenceHow the result matched your text (table below). Absent on a typed lat,lng.
approximate, method, matched_numbers, within_m, scoreOn an estimated address only (below).
attributionThe terms of the dataset this one result came from: dataset, license, and where they apply license_url, attribution (the credit to show) and use_restriction.
confidenceThe result is
exactthe typed number on the typed street, in the place asked for
fuzzythe same, through a misspelled street name
name_exacta place whose name is your words
localitythe town, region or neighborhood your text names
interpolated, nearestan estimate for a house number that is not in the data
partiala longer street that begins with the one you typed
name_partiala place whose name shares some of your words
ambiguousan address in no named place and near no anchor: one of several

Estimated addresses. When the typed house number is not in the data, the answer places it on its street: between the nearest known numbers on the same side, at the nearest one, or along the block whose number range holds it. That result carries approximate: true and:

FieldMeaning
method"interpolated" (between two known numbers), "nearest" (at the one known number nearby) or "range" (along the block).
matched_numbersThe numbers it was placed from.
within_mThe distance in meters between those numbers, or the length of the block: the most the estimate can be off while the street is numbered in order. Absent for "nearest".
scoreFrom 0 to 1, how sure the estimate is. Treat a low score as a street-level answer, not a door.

An exact match never carries these fields.

Attribution. Pass the top-level attribution on with the results. Its text is the credit to show, and licenses are the terms the data's sources ask to travel with it. When results include street addresses, attribution.sources lists each address dataset the answer drew on. You are bound by a dataset's use_restriction for those results:

DatasetCredit to showUse restriction
au/countrywide (G-NAF)G-NAF © Geoscape Australia licensed by the Commonwealth of Australia under the Open Geo-coded National Address File (G-NAF) End User Licence Agreement.Not for generating an address, or a compilation of addresses, for sending mail unless each address is verified as able to receive mail against a source other than G-NAF; use must be consistent with the Australian Privacy Principles.
ca/countrywide (Statistics Canada)Adapted from Statistics Canada, National Address Register, 2025-07-29. This does not constitute an endorsement by Statistics Canada of this product.Not to identify any individual, business or organization, or to suggest endorsement by Statistics Canada.
us/ny/city_of_new_yorkAddress data from the NYC Department of City Planning.None.
us/ma/statewide (MassGIS)MassGIS (Bureau of Geographic Information), Commonwealth of Massachusetts EOTSSNone (public domain).

No match and failure look the same. A query with no matches returns 200 with "results": []. So does a lookup that fails internally; that case is not charged. Treat an empty list as "nothing to show", not as an error.

StatusCodeWhen
422validationq is missing, empty or longer than 200 characters; limit or a coordinate is out of range.
429quota_exceededThe AI-credit pool cannot cover the call.

POST /spot​

Answers what is at a point: the place the user clicked, the nearest street address, and the regions that contain the point. Each value names the credit you must show with it. On request it adds the place's descriptive content and photos.

Auth: server key, session token or legacy key. Scope: ai. Charge: AI credits, a flat fee per call by the most expensive thing want asks for, charged whether or not anything was found: content or photos cost the fee of /retrieval/poi/enrich when the content is fetched for this call; otherwise address costs the fee of /retrieval/geocode/forward. Content already fetched for an earlier request costs nothing, so such a call that also asks for address is charged the geocode fee. A call that asks only for entities, regions, boundaries or tenants is free. The default want includes address, so a call that leaves want out is charged the geocode fee.

FieldTypeRequiredDefaultNotes
latnumberYes−90 to 90. The point.
lngnumberYes−180 to 180.
zoomnumberNo0 to 24. The map's zoom level.
hintobjectNoWhat the user clicked (below).
wantarray of stringsNo["entities", "address", "regions"]The parts to return (below).
langstringNo"en"Language for content. Up to 16 characters.

The point decides; the hint only picks. A hint selects among what stands at the point and never moves the answer: a place more than 75 meters from lat, lng is not returned, whatever the hint names.

hint fieldNotes
idKaleidr's id for the place: the part of a poi: ref after the colon.
nameThe place's name, as on the map label. Up to 200 characters. Matched exactly against the places at the point, and names the place for content. With a region_level of county, state or country, it also selects the region at that level whose name matches, ignoring case and accents, from those at the point (for a state or country, also those up to 5 km from it). A name that matches none of them changes nothing.
categoryThe place's category. Up to 100 characters. Used for content.
region_levelWith boundaries, return only the outline of the region at this level (city, county, state or country); without it, every region's outline. With name, the region at this level in both regions and boundaries is the one the name selects.
layer, wiki_title, ta_location_id, yelp_business_id, data_sourceAccepted. Used by Kaleidr's own apps; leave them out.
want valueAdds
entitiesThe clicked place, when hint names one that stands at the point.
addressThe nearest street address.
regionsThe city and county (United States only), state and country that contain the point. A point up to 5 km off a coast gets the nearest state and country.
boundariesregions, each with its outline (one level's only, with hint.region_level).
contentThe place's descriptive content: a summary, photos and a link. Needs a place name, from the clicked place or hint.name.
photosThe place's photos only. Costs the same as content.
tenantsThe named places near the point, nearest first: the businesses in a building. Your app keeps the ones inside the footprint it shows.
curl -s https://api.kaleidr.com/inference-api/b2b/v1/spot \
-H "Authorization: Bearer $KALEIDR_KEY" \
-H "Content-Type: application/json" \
-d '{
"lat": 40.7484,
"lng": -73.9857,
"zoom": 17,
"hint": {"name": "Empire State Building", "category": "landmark"},
"want": ["entities", "address", "regions"]
}'

A shortened response, with illustrative values:

{
"spot": { "lat": 40.7484, "lng": -73.9857, "zoom": 17 },
"entities": [
{
"ref": "poi:3f1c2a9e-…",
"kind": "poi",
"role": "clicked",
"name": { "value": "Empire State Building", "license": "CDLA-Permissive-2.0", "attr": "a1" },
"at": { "lat": 40.74844, "lng": -73.98566 },
"facts": {
"category": { "value": "landmark_and_historical_building", "license": "CDLA-Permissive-2.0", "attr": "a1" },
"website": { "value": "https://www.esbnyc.com", "license": "CDLA-Permissive-2.0", "attr": "a1" }
}
}
],
"address": {
"ref": "address:ka1…",
"line": { "value": "20 W 34th St, New York, NY 10001", "license": "CDLA-Permissive-2.0", "attr": "a2" },
"at": { "lat": 40.7484, "lng": -73.9857 }
},
"regions": [
{
"ref": "region:tiger:3651000",
"kind": "region",
"level": "city",
"name": { "value": "New York", "license": "LicenseRef-Public-Domain", "attr": "a3" },
"label": { "lat": 40.6976, "lng": -73.9795 }
},
{
"ref": "region:tiger:36",
"kind": "region",
"level": "state",
"name": { "value": "New York", "license": "LicenseRef-Public-Domain", "attr": "a3" },
"label": { "lat": 42.7559, "lng": -75.8099 }
}
],
"attributions": {
"a1": { "text": "© Overture Maps Foundation", "license": "CDLA-Permissive-2.0" },
"a2": { "text": "Address data from the NYC Department of City Planning.", "license": "CDLA-Permissive-2.0" },
"a3": { "text": "U.S. Census Bureau, TIGER/Line", "license": "LicenseRef-Public-Domain" }
},
"withheld": ["building_facts"]
}
FieldMeaning
spotThe point you sent: { lat, lng, zoom }.
entitiesThe clicked place, then the tenants when you asked for them, or an empty list. Each has a ref, kind ("poi" for the clicked place, "tenant" for a tenant), role ("clicked" or "inside"), name, at (the place's own position, { lat, lng }) and facts (category, and the clicked place's website, when known). A tenant's ref is a poi: ref, so its overview can be streamed too. Ignore an entity of a kind you do not recognize.
addressThe nearest street address, or null: { ref, line, at }.
regionsThe regions that contain the point, smallest first. Each has a ref, kind ("region"), level (city, county, state or country), name, and label: a point for placing the region's name, { lat, lng }, or null. With want: ["boundaries"] each also has geometry, its outline as a GeoJSON geometry.
contentPresent when want has content or photos, a place name is known and content may be returned. The object /retrieval/poi/enrich returns (fields), or { photos, source, cached } for photos alone, with license and attr added like any other value. Can be null when no content could be fetched.
attributionsThe credits this response uses: { text, license }, keyed by the attr of the values.
withheldWhat this response left out because the API may not return it, by kind. An empty list when nothing was left out.

A ref names a thing for /spot/overview/stream: poi:<id> for a place, address:<id> for an address, region:<source>:<id> for a region (<source> is tiger, nuts or ne). Kaleidr's apps also use building:<id>, which the API does not return. Treat a ref as an opaque string.

Show the credit of every value​

Every value from a dataset is an object, { value, license, attr }: the data, its license, and a key into attributions. Wherever you display a value, show attributions[attr].text with it, next to it or in a credits area reachable from it. One response mixes several sources, so a single credit for the whole response is not enough. Keep license and the credit with a value you store or pass on.

content is credited the same way, through its own attr. A value carrying live_only: true may be shown to the current user only, never stored or passed on.

See Data licenses for what each license asks of you.

What the API does not return​

Kaleidr's own apps show more at a point than the API returns, because some data is not licensed for use on other sites:

  • Values under the ODbL, derived from OpenStreetMap: a building's facts (floors, height, class). A building's overview is not available either.
  • Answers from third-party providers: address lookups from geocoding services, and ratings, reviews and photos from Yelp and Tripadvisor.

withheld exists so you can tell "nothing is known here" from "not available through the API". It names kinds, never values: building_facts when the point is in a building, content when the only content for the place may not be returned. Other entries name another part of the response the same way.

Errors for POST /spot:

StatusCodeWhen
413request_too_largeThe request body is larger than 2 MiB.
422validationlat or lng is missing or out of range, zoom is outside 0 to 24, want has an unknown value, or a field is longer than its limit or malformed.
429quota_exceededThe AI-credit pool cannot cover the call.

A part that could not be looked up comes back empty (null or []). The call is still charged.

POST /spot/overview/stream​

Streams a short written overview of one thing that /spot returned, named by its ref.

Auth: server key, session token or legacy key. Scope: ai. Charge: AI credits, the tokens used. Every overview is written for its request. Response: text/event-stream: usually a place frame with the full name of the place, such as "Springfield, Ohio, United States of America", then the frames of a place summary: see Summary and button streams.

FieldTypeRequiredDefaultNotes
refstringYesA ref from a /spot response, up to 128 characters, or place: (below).
lat, lngnumberYesThe point you sent to /spot.
langstringNo"en"The language to write in. Up to 16 characters.
hintobjectFor place: only{ name, category? }: the label to describe.
refOverview
poi:<id>The place.
region:<source>:<id>The region.
place:A named label Kaleidr holds no record for, such as a place /spot returned no entity for. Send its name in hint.name.
address:<id>, building:<id>, building:Not available through the API: the stream sends one error frame. building: with no id, the building at the point, is accepted for Kaleidr's apps; through the API it gets the same single error frame. See What the API does not return.

The thing is found again at lat, lng, so nothing in the request is taken as a fact about it. A poi: ref more than 75 meters from the point, a region: ref that does not contain it, and place: without hint.name also get the error frame.

curl -N https://api.kaleidr.com/inference-api/b2b/v1/spot/overview/stream \
-H "Authorization: Bearer $KALEIDR_KEY" \
-H "Content-Type: application/json" \
-d '{"ref": "poi:3f1c2a9e-…", "lat": 40.7484, "lng": -73.9857, "lang": "en"}'

A ref that cannot be described answers with this frame in place of a summary. It carries the ref and no partial_text:

event: error
data: {"type": "error", "message": "Nothing to describe here.", "ref": "address:ka1…"}
StatusCodeWhen
413request_too_largeThe request body is larger than 2 MiB.
422validationref is not one of the forms above, lat or lng is missing or out of range, or lang is longer than 16 characters.
429quota_exceededThe AI-credit pool cannot cover the call.

Moving from the deprecated endpoints​

GET /retrieval/poi/enrich and POST /chat/summary/stream are deprecated. They answer as before until 2027-01-15 and can be removed at any time after it. Every response from them says so in its headers; Deprecation is the day they were deprecated, 2026-10-16, as a Unix time:

Deprecation: @1792108800
Sunset: Fri, 15 Jan 2027 00:00:00 GMT
Link: </inference-api/b2b/v1/spot>; rel="successor-version"

The Link of /chat/summary/stream names /inference-api/b2b/v1/spot/overview/stream. Log the Sunset header to find the calls you still make.

From GET /retrieval/poi/enrich to POST /spot:

BeforeAfter
lat, lon (query)lat, lng (body). Note the name.
name, categoryhint.name, hint.category
langlang, up to 16 characters
A full lookupwant: ["content"]
photos_only=truewant: ["photos"]
radius, dataSource, ta_location_id, regionLevelLeave them out.
The response bodycontent: the same object, with live_only added
attributioncontent.attribution

The charge is the same or lower: /spot charges nothing for content already fetched for an earlier request. A lookup is charged for the most expensive thing in want, so when the content is fetched for the call, adding entities, address or regions gets them in the same call at no extra charge.

From POST /chat/summary/stream to POST /spot/overview/stream:

BeforeAfter
A place: location, building_properties.name, .categoryCall /spot with the point and hint: { name, category }, then stream the overview of the ref of the place in entities, with the same lat and lng. When entities is empty, send ref: "place:" with the same hint.
building_properties.pidref: "poi:<id>"
langlang, up to 16 characters
max_tokensLeave it out.
A building: type: "building" and its fieldsNo successor. A building's overview is not available through the API.
The framesThe frames of a place summary, after a new place frame that names the place.

GET /retrieval/poi/enrich​

Deprecated

This endpoint is deprecated and can be removed after 2027-01-15. Use POST /spot with want: ["content"]. See Moving from the deprecated endpoints.

Returns descriptive content for one place: a title, a summary, photos and a link.

Auth: server key, session token or legacy key. Scope: ai. Charge: AI credits, a flat fee per call. The fee is charged whether or not content was found.

ParameterTypeRequiredDefaultNotes
latnumberYesLatitude of the place.
lonnumberYesLongitude of the place. Note the name: lon, not lng.
namestringNoThe place's name. Recommended: a point alone often matches nothing.
categorystringNoThe place's category, as given on a place event.
radiusintegerNo250Search radius in meters around the point.
langstringNo"en"Language for the content.
photos_onlybooleanNofalseReturn photos only (smaller response, below).
dataSource, ta_location_id, regionLevelstringNoAccepted. Hints used by Kaleidr's own panel; leave them out.
curl -s -G https://api.kaleidr.com/inference-api/b2b/v1/retrieval/poi/enrich \
-H "Authorization: Bearer $KALEIDR_KEY" \
--data-urlencode "lat=48.8606" \
--data-urlencode "lon=2.3376" \
--data-urlencode "name=Louvre Museum" \
--data-urlencode "category=museum"
{
"source": "wikipedia",
"found": true,
"title": "Louvre",
"photos": ["https://upload.wikimedia.org/…/Louvre_Museum.jpg"],
"summary": "The Louvre is a national art museum in Paris, France…",
"rating": null,
"phone": null,
"hours": null,
"reviews": [],
"url": "https://en.wikipedia.org/wiki/Louvre",
"attribution": ["Wikipedia"]
}
FieldTypeMeaning
sourcestringWhere the content came from. "omitted" means content exists for this place but may not be returned through the API (below).
foundbooleanfalse when nothing was found. The other fields are then empty.
titlestringThe place's title in the source.
summarystring or nullA short description.
photosarray of URLsCan be empty.
urlstring or nullA link to the source page.
attributionarray of stringsThe names to credit, for example ["Wikipedia"]. Show them with the content.
rating, phone, hours, reviewsCan be null or empty. Ratings and reviews from licensed providers are never returned through the API.
unverifiedbooleanOptional. true when the match to your place is uncertain.

With photos_only=true the response is { "photos": […], "source": "…", "cached": false }.

Withheld content. Some content is licensed for Kaleidr's own apps only. When that is the only content available for a place, the response is 200 with "source": "omitted", "found": false and no content. The call is still charged. See Data licenses.

StatusCodeWhen
422validationlat or lon is missing or not a number.
429quota_exceededThe AI-credit pool cannot cover the call.
500An internal error occurred. Please try again.The lookup failed. The call is still charged.

POST /chat/summary/stream​

Deprecated

This endpoint is deprecated and can be removed after 2027-01-15. Use POST /spot/overview/stream. A building summary has no successor. See Moving from the deprecated endpoints.

Streams a short written summary of one place or one building. It backs the place card in Kaleidr Chat.

Auth: server key, session token or legacy key. Scope: ai. Charge: AI credits, the tokens used. Every summary is written for its request. Response: text/event-stream. See Summary and button streams.

The body's type selects the kind of summary.

A building ("type": "building"):

FieldTypeRequiredNotes
typestringYes"building".
addressstringYesThe building's full address. Up to 500 characters.
lat, lngnumberNoThe building's position.
building_levelsintegerNoNumber of floors, if you know it.
building_height_mnumberNoHeight in meters, if you know it.
tenant_namesarray of stringsNoBusinesses in the building. Up to 100 names of up to 200 characters each.
poi_countintegerNoHow many places the building holds.
langstringNoDefault "en". Up to 35 characters.
curl -N https://api.kaleidr.com/inference-api/b2b/v1/chat/summary/stream \
-H "Authorization: Bearer $KALEIDR_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "building", "address": "20 W 34th St, New York, NY 10001", "lat": 40.7484, "lng": -73.9857}'
event: start
data: {"type": "start", "address": "20 W 34th St, New York, NY 10001"}

data: {"text": "A landmark office tower in Midtown Manhattan"}

event: end
data: {"type": "end", "full_text": "A landmark office tower in Midtown Manhattan…", "parsed": {"…": "…"}, "total_tokens": 910}

event: quota
data: {"type": "quota", "product": "b2b", "meter": "b2b.tokens", "tier": "pro", "used": 185133, "limit": 5000000, "reset_at": "2026-11-01T04:00:00+00:00", "period": "month"}

A place (type absent, or "poi"). The body accepts location (object { lat, lng }), building_properties (object), lang and max_tokens (integer). This variant is shaped for Kaleidr's own panel, and the meaning of each field is not a published contract yet. Prefer /chat/control/stream to ask about a place from your own code.

Place fieldLimit
building_properties.name, .type, .category, .poi_type200 characters each
building_properties.pid128 characters
Each text field of building_properties.event4,000 characters
lang35 characters
max_tokensA value above the endpoint's default is lowered to the default. You cannot ask for a longer summary.

Both variants read only the fields listed here. Any other field in the body is ignored. A field over its length limit is refused with 422 field_too_long, before any summary is produced.

StatusCodeWhen
413request_too_largeThe request body is larger than 2 MiB.
422field_too_longA field is longer than its limit, or tenant_names has more than 100 entries. The body is {"detail": "field_too_long"}.
500Summary stream errorThe body could not be read, or a building request has no address. This endpoint answers any other malformed body with 500, not 422.
429quota_exceededThe AI-credit pool cannot cover the call.

POST /chat/button/stream​

Streams the answer to one question about one place, such as "What are the opening hours?". It backs the question buttons on the place card in Kaleidr Chat. It has no conversation: each call stands alone.

Auth: server key, session token or legacy key. Scope: ai. Charge: AI credits, the tokens used. Response: text/event-stream. See Summary and button streams.

FieldTypeRequiredDefaultNotes
querystringYesThe question. Up to 4,000 characters.
place_namestringNoThe place the question is about. Up to 200 characters.
place_typestringNoIts category. Up to 200 characters.
addressstringNoIts address. Up to 500 characters.
locationobject { lat, lng }NoIts position.
langstringNo"en"Language to answer in. Up to 35 characters.
descriptionstringNoA description of the place you already hold. Up to 4,000 characters.
websitestringNoThe place's website, if you know it. Up to 2,048 characters.
stores_previewarray of objects { name }NoBusinesses at the place, if you already hold them. Up to 50 entries; each name up to 200 characters. Other members of an entry are ignored.
button_id, session_id, place_idstringNoYour own identifiers, for correlation. Up to 128 characters each.

Fields that are not in this table are ignored. A field over its length limit is refused with 422 field_too_long, before any answer is produced.

curl -N https://api.kaleidr.com/inference-api/b2b/v1/chat/button/stream \
-H "Authorization: Bearer $KALEIDR_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "What is this place known for?",
"place_name": "Louvre Museum",
"place_type": "museum",
"location": {"lat": 48.8606, "lng": 2.3376}
}'
event: start
data: {"type": "start", "query_type": "…"}

data: {"text": "The Louvre is best known for"}

event: end
data: {"type": "end", "full_text": "The Louvre is best known for…", "reply": "The Louvre is best known for…", "query_type": "…", "first_token_ms": 820, "total_ms": 3900, "total_tokens": 640}
StatusCodeWhen
413request_too_largeThe request body is larger than 2 MiB.
422field_too_longA field is longer than its limit, or stores_preview has more than 50 entries. The body is {"detail": "field_too_long"}.
500Button chat stream errorThe body could not be read or has no query. This endpoint answers any other malformed body with 500, not 422.
429quota_exceededThe AI-credit pool cannot cover the call.

POST /design/analyze​

Looks at the columns and a sample of a table and recommends how to map it.

The Editor embed does not call the design endpoints. They are a REST surface for your own tools.

Auth: server key, session token or legacy key. Scope: design. Charge: AI credits, a flat fee per successful call. A failed call is not charged.

FieldTypeRequiredNotes
columnsarray of stringsYes1 to 256 column names. Each must be unique.
sampleRowsarray of objectsNoUp to 1,000 rows, each an object keyed by column name.
datasetNamestringNoUp to 512 characters.
rowCountintegerNoTotal rows in the full table, 0 or more.
userGoalstringNoUp to 1,000 characters: what the map should show.
supportedStylesarray of stringsNoStyle ids to choose from, as lower-case words joined by dashes.

Field names are camelCase. Unknown fields are ignored.

curl -s https://api.kaleidr.com/inference-api/b2b/v1/design/analyze \
-H "Authorization: Bearer $KALEIDR_KEY" \
-H "Content-Type: application/json" \
-d '{
"datasetName": "stores.csv",
"columns": ["name", "lat", "lng", "revenue"],
"sampleRows": [
{"name": "Downtown", "lat": 40.7128, "lng": -74.0060, "revenue": 120000},
{"name": "Uptown", "lat": 40.7831, "lng": -73.9712, "revenue": 86000}
],
"rowCount": 240,
"userGoal": "Show which stores earn the most"
}'
{
"datasetProfile": { "…": "what was detected in each column" },
"designPlan": { "…": "the recommended style, theme and reasoning" },
"issues": [],
"recommendation": { "…": "…" },
"suggestedConfig": { "…": "…" },
"explanation": "…"
}

Read designPlan. It can be null. recommendation, suggestedConfig and explanation are older fields kept for compatibility and are due to be removed; do not build on them. issues lists problems found in the data.

StatusCodeWhen
413request_too_largeThe request body is larger than 5 MiB. The same limit applies to /design/apply-direct.
422validationcolumns is missing, empty, too long or has duplicates; a limit above is exceeded.
429quota_exceededThe AI-credit pool cannot cover the call.
500map_design_analyze_failedThe analysis failed. Not charged.

POST /design/apply-direct​

Builds a map layer from a table and a chosen style, without analysis.

Auth: server key, session token or legacy key. Scope: design. Charge: nothing.

FieldTypeRequiredNotes
stylestringYesA style id, 1 to 64 characters. Ids are listed by /design/styles.
configobjectYesAn object with location, metric and visual members. location.mode ("coordinates" or "region") and visual.layerName are required; the other sub-fields are not documented yet.
tableobjectYes{ fileName?, columns[], rows[] }. Each row is an object of column name to value, and every value must be a JSON string: send "120000", not 120000. A row with a number or null is refused with a 422.
layerLabelstringYes1 to 512 characters. The layer's display name.
themePackIdstringNoA theme id from /design/themes. An unknown id is ignored.
datasetSourceobjectNo{ label, url? }: who published the data, for the layer's citation.
datasetIdstringNoUp to 128 characters. Used as the layer's id.
yearintegerNo1800 to 2100.

The response always has a status:

{ "status": "applied", "message": null, "spec": { "…": "a partial map specification" } }
statusMeaning
appliedspec holds the layer.
unsupported-styleThe style cannot be built from this payload. message says why.
insufficient-dataThe table lacks what the style needs, for example position columns. message says what.

All three are 200 responses. Check status.

GET /design/styles, /design/samples, /design/themes​

Three read-only catalogs: the available map styles, worked sample maps, and theme packs.

Auth: server key, session token or legacy key. Scope: design. Charge: nothing. These calls count toward the key's requests per minute but not toward concurrency.

curl -s https://api.kaleidr.com/inference-api/b2b/v1/design/styles \
-H "Authorization: Bearer $KALEIDR_KEY"

Each response wraps its list in one key:

{ "styles": [ { "…": "…" } ] }

/design/samples returns { "samples": [ … ] } and /design/themes returns { "themes": [ … ] }.


Errors shared by all runtime endpoints​

Every endpoint except the two session exchanges can return these, before any endpoint-specific work is done. They are checked in this order.

OrderStatusCodeWhen
1401Invalid or missing API keyNo credential, or one that is unknown, revoked or expired.
2401publishable_requires_sessionA publishable key was sent. Exchange it first.
2403server_key_in_browserA server key arrived with an Origin header.
2403session_origin_mismatchA session token used from another origin.
2403ip_not_allowedA server or legacy key with an IP allowlist, called from outside it.
3403insufficient_scopeThe key lacks the endpoint's scope.
3403tier_capability_not_allowedThe key has the scope; your plan no longer includes it.
3403product_not_allowedA server or legacy key limited to certain products called an endpoint of another product. See Restricting a key.
4429quota_exceededCharged endpoints only: the pool or the key's cap cannot cover the call.
5429visitor_limit_reachedPublished-map sessions only: this visitor has asked too much.
6429rate_limitedThe key's requests per minute are used up.
7429b2b_overloaded, org_overloadedToo many calls in progress.

A request body over the size limit is refused with 413 request_too_large: 2 MiB on every endpoint, 5 MiB on the /design/* endpoints. When the request declares its length, this refusal comes before every check above.

Bodies and handling for each are in Errors and Quota and rate limits.