Chat on a published map
A map you publish from Kaleidr Studio can offer its own chat. Visitors of the page where you embed the map ask questions in a chat panel, and the answers appear as pins on the embedded map. There is no API key in the page: the published map itself authorizes the chat, and the map's owner pays for the questions. This is called Visitor AI Chat, and it works on every plan, including Free.
At the end of this page you have a published Studio map embedded in your site with a chat panel docked inside it.
Before you start
- A Kaleidr account and a map in Studio.
- AI credits on the owner's organization. Every plan includes a monthly allowance.
- A page you can add HTML to, served over HTTP or HTTPS. The page needs to be able to run a script tag; site builders that accept only an iframe can show the map but not the chat.
- If your site sends a Content Security Policy, the hosts on the Content Security Policy page allowed.
Step 1: Publish the map
In Studio, open the map's Publish & embed page and choose Go Live.
You should see: the status change from "Draft" to "Live", and an Embed code section appear below it. From then on the published map shows the map as it is currently saved, so an edit you save is public the next time the page loads, with no re-publish. To change the map privately, unpublish it first and publish it again when you are done: see Embed a map.
Step 2: Switch on Visitor AI Chat
In the map editor, choose Share, then the Embed tab. Switch on Visitor AI Chat.
You should see: a second field appear, Monthly AI Credit Limit, with the placeholder "No limit".
Optionally type a limit. It is the most AI credits this map's chat may spend in one billing period of the owner's organization.
- Leave it empty for no per-map limit. The organization's own allowance is then the only ceiling.
- The value is a whole number of credits, 1 or more. The field saves when you leave it or press Enter.
- Set it to at least a few thousand. A question is admitted only while 2,000 credits are still available under the limit, so a limit below 2,000 means no question is ever answered.
Step 3: Decide which sites may embed the map (optional)
In the same Embed tab, a menu above the Visitor AI Chat switch sets who can embed the map. It has two choices:
| Choice | Effect |
|---|---|
| Any domain | Any page can embed the map and, with Visitor AI Chat on, open its chat. |
| Specific domains | Only pages on the domains you list, one per line. A domain covers its subdomains: example.com also allows www.example.com and shop.example.com. |
The list applies to both the map and the chat. Because the chat spends your credits, restrict the domains unless you intend the chat to be usable from any site. Embed a map covers the domain list in detail.
You should see: "Saved" next to the menu after each change.
Step 4: Copy the map's id
Go back to the Publish & embed page. Under Embed code, choose Developer. With Visitor AI Chat on, the
snippet contains two <kaleidr-map> elements, and both carry the same share-id. That value is your map's id.
Use the id from this page. A share link's id shows the map in a viewer element, but the chat refuses it with
404 not_found: only the published map's id opens a chat.
Step 5: Add the two elements to your page
The snippet Studio copies places the chat element after the map, so the panel appears below the map. To put the panel on the map, wrap both elements in one positioned container and stretch the chat element over it:
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js" async></script>
<div style="position:relative">
<kaleidr-map id="store-map" product="viewer" share-id="YOUR_MAP_ID"
style="display:block; height:480px"></kaleidr-map>
<kaleidr-map product="chat" share-id="YOUR_MAP_ID" contained
map-target="#store-map"
style="display:block; position:absolute; inset:0; z-index:1; pointer-events:none"></kaleidr-map>
</div>
| Part | Why it is there |
|---|---|
position:relative on the wrapper | The chat element is positioned against it. The wrapper takes its height from the map element. |
display:block; height:480px on the viewer | The height sizes the map. <kaleidr-map> is display:block by default; writing it out also covers Safari before 16.4, where the element stays inline and a height has no effect. |
share-id on both elements | On the viewer it selects the map. On the chat element, with no key, it selects the map that provides the chat. |
contained | Docks the panel inside the chat element instead of floating it over the browser window. |
map-target="#store-map" | A CSS selector for the map the panel drives. Use an id when the page has more than one map. |
position:absolute; inset:0 on the chat element | Stretches it over the map, so the panel docks at the map's top-left corner. |
pointer-events:none | Lets the visitor drag and zoom the map everywhere the panel is not. |
You should see: your published map, with an input bar that reads Ask Kaleidr at its top-left corner.
The complete page
Save this as index.html, replace YOUR_MAP_ID in both places, and serve the folder over HTTP.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Our locations</title>
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js" async></script>
<style>
body { margin: 0 auto; max-width: 960px; padding: 24px; font-family: system-ui, sans-serif; }
.map-frame { position: relative; }
</style>
</head>
<body>
<h1>Our locations</h1>
<div class="map-frame">
<kaleidr-map id="store-map" product="viewer" share-id="YOUR_MAP_ID"
style="display:block; height:480px"></kaleidr-map>
<kaleidr-map product="chat" share-id="YOUR_MAP_ID" contained
map-target="#store-map"
style="display:block; position:absolute; inset:0; z-index:1; pointer-events:none"></kaleidr-map>
</div>
</body>
</html>
How it behaves
What the panel does. It is the same chat panel as on a map of your own: answers stream in, places become pins on the embedded map, the view fits them, and a clicked pin opens a place card. Directions draw a route on the embedded map. Pins from earlier answers stay on the map for the rest of the conversation, and closing the chat window removes the chat's pins and its route. Capabilities and limits describes it in full.
Who pays. Every question spends AI credits of the organization that owns the map, on every plan. Visitors need no account and no key.
The limits that apply.
| Limit | Value | What the visitor sees when it is reached |
|---|---|---|
| The map's Monthly AI Credit Limit, if you set one | Your value, per billing period | "You've reached your usage limit for now." |
| The organization's AI credit allowance | By plan | The same message |
| Requests from one visitor | 20 per minute and 200 per day. Every request the panel makes counts: one for the answer, one photo request for each result card in it, two when a place card opens, one for a follow-up question in a place card, one for each address lookup while typing in Directions, and one for a route. A visitor is one network address on one map, so people behind the same address share these limits. | The same message for an answer. A result card whose photo request is refused shows no photos. |
| Requests across all visitors of one map | 60 per minute | The same message |
| Starting chat sessions | 20 per minute from one network address, 600 per minute for one map | The panel does not appear |
What a visitor can learn about your account. Nothing about your plan or usage. A visitor's session is told only that a limit was reached and when it resets. When your AI credits or the map's monthly limit run out, the 429 response is:
{
"error": "quota_exceeded",
"detail": "Usage limit reached. Please try again later.",
"meter": "b2b.tokens",
"reset_at": "2026-11-01T04:00:00+00:00",
"reset_in_seconds": 1296000
}
It carries no tier, limit, used or remaining, and the quota event that ends each answer's stream
carries only type, meter and reset_at. Read your usage on
kaleidr.com/billing.
How switching it off takes effect. A chat session lasts five minutes, and the page renews it shortly before it expires. When you switch Visitor AI Chat off, unpublish the map, or remove a page's domain from the list, the next renewal is refused. Chat on already-open pages therefore stops within five minutes. The panel stays visible but every question fails with "Sorry — that didn't go through. Please try again." New page loads show no panel. After a refusal the page does not ask again until it is reloaded.
If the element also has a key. A publishable-key on the chat element takes precedence over share-id. The
chat then runs on that key's organization and limits, and the map's switch no longer governs it.
Answers from the map's own places
A published map usually shows places of its own: the stores, venues or sites its author put on it. Visitor AI Chat can answer from those places, so that a visitor who asks "which of these is open late?" is answered about the places on the map and not about places in general.
This behavior is switched on by Kaleidr for the whole platform, and it is off by default. There is no setting for it in Studio or in the embed code. While it is off, the chat answers every question from general knowledge and does not read the map's markers, layers or datasets. Build your page so that it is useful either way, and check before you tell visitors that the chat knows your places: How to tell whether it is on.
What counts as a place
A place is a point the published map shows that has a name. The chat reads three kinds:
| The map shows | It is a place when | What the chat reads |
|---|---|---|
| A marker | The marker has a name, a title or a label | The name, the coordinates, and the marker's category and description when it has them |
| A point in data written into the map itself | The point has a name, title or label property | The same |
| A row of a dataset uploaded in Studio's Data panel | The published map includes the dataset, the dataset has a Name column, and the row has point coordinates | The row's name, its coordinates, and its Category and Description columns when the dataset has them |
For an uploaded dataset, the Name, Category and Description columns are the dataset's column roles. Studio detects them at upload, and you correct them in the Column Roles fold of the dataset in the Data panel. Things to know about datasets:
- A dataset with no Name column contributes no places. Set the Name role and the rows become places.
- Rows without point coordinates are skipped. Lines and areas are drawn on the map but are not places.
- Only datasets the published map includes are read, the same rule that decides which datasets are shown to visitors. Delete the dataset in the Data panel, or unpublish the map, and the chat no longer reads it.
- The Address column is not read by the chat. A place is located by its coordinates.
How answers use them
- Questions about the kind of places the map shows are answered from the map first. The chat names each place as the map names it and puts it at the map's own coordinates.
- General knowledge can add to a place on the map. It does not add places to the map: the chat does not present a place as being on your map unless the map shows it.
- Other questions are answered as usual. A visitor who asks about the weather or about a city gets the same answer as on any map.
- The map draws the pin. A place from the map arrives on the stream as a
placeevent with"source": "map", and the panel adds no pin of its own for it, because the map already shows it. - What you wrote is quoted to visitors. Names, categories and descriptions of the places on a published map can appear in answers word for word. Publish only text that anyone may read.
Limits
| Limit | Value |
|---|---|
| Places read from one map | 200. Markers come first, then points written into the map, then dataset rows, in the order the map draws them. Places past the 200th are not read. A change to the map's places, such as a deleted dataset, can take up to a minute to reach the chat. |
| Places used in one answer | All of them when they fit. On a map with many places or long descriptions, the chat works from as many as fit, taken in the order above, so a place late in that order can be left out of an answer although the map shows it. The panel does not send the visitor's current view, so the choice does not follow where the visitor is looking. |
| Length of a name | 200 characters. Longer text is cut. |
| Length of a category | 100 characters |
| Length of a description | 500 characters |
How to tell whether it is on
Ask the chat on your published map about a place that only your map shows, by a name general sources would not
know. Then open the Network tab, select the control/stream request and read its event stream.
- On: the answer names your place, and the stream carries a
placeevent for it with"source": "map". - Off: no
placeevent carries"source": "map", and the answer does not know the place.
The place event and its source field are described in the
SSE wire contract.
Check that it worked
In the page
- The map renders, and the Ask Kaleidr input bar is at its top-left corner.
- Ask a question that names a place. Text streams in and pins appear on the embedded map.
In the console
- No error or warning that contains
[kaleidr.
In the Network tab
| Request | Expect |
|---|---|
POST https://api.kaleidr.com/inference-api/b2b/v1/sdk/share-sessions with the body {"share_id":"YOUR_MAP_ID"} | 200, with the JSON body below |
POST https://api.kaleidr.com/inference-api/b2b/v1/chat/control/stream (after you ask) | 200, content-type: text/event-stream |
{ "session_token": "kld_sess_live_…", "expires_in": 300, "product": "chat" }
If it does not work
When the session request is refused, the panel does not appear and the console shows
[kaleidr:chat] kaleidr session exchange failed (<status>). The reason is in the response, in the Network tab.
A 403 or 404 carries its code in the detail field. A 429 carries its code in the error field, with a
sentence in detail.
| Status and code | Cause | Fix |
|---|---|---|
403 chat_disabled | Visitor AI Chat is off for this map. | Switch it on in Share, Embed. |
404 not_found | No published map has this id. The map is unpublished, the id is mistyped, or it is a share link's id. | Publish the map and copy the id from the Developer snippet on the Publish & embed page. |
403 origin_not_allowed | The map lists specific domains and this page's origin is not among them. | Add the domain in Share, Embed, or choose Any domain. |
403 origin_required | The request carried no Origin header. This happens outside a browser. | Open the page in a browser over HTTP or HTTPS. |
403 owner_has_no_organization | The map's owner account is not attached to an organization. | Contact Kaleidr support. |
403 tier_capability_not_allowed | The owner's plan does not currently include Chat. | Check the owner's subscription. |
| 422 | The id is malformed. For chat it is 1 to 128 characters: letters, digits, _ and -. | Copy the id again. |
429 rate_limited | Too many sessions were started from this network address or for this map in the last minute. | Wait a few seconds and reload. |
503 share_grant_unavailable | Kaleidr could not confirm the map's settings at that moment. | Retry shortly. |
404 sessions_disabled | Session exchange is switched off on the platform. Not caused by your map. | Contact Kaleidr support. |
503 sessions_not_configured | A platform fault. | Retry shortly. |
Other symptoms:
| Symptom | Cause | Fix |
|---|---|---|
| The panel is below the map, not on it | The two elements are siblings with no positioned wrapper. | Use the layout in step 5. |
| The panel is at the top-left of the browser window | contained is missing on the chat element. | Add it. |
| The panel answers, but the map shows no pins | map-target does not match the viewer element. | Check the selector. With several maps on the page, target an id. |
| The map is 500 pixels tall whatever height you set | The viewer element had no height when it mounted, your CSS sets display:inline on it, or the browser is Safari before 16.4 and the element has no display:block. | Give the element display:block and a height. |
| The map area shows a message that the site is not allowed, and the chat is refused | The page's domain is not in the map's list, or the page sends no referrer. | Add the domain. A page with Referrer-Policy: no-referrer cannot show a domain-locked map. |
| The panel shows "You've reached your usage limit for now." | One of the limits in the table above was reached. | For the monthly limit, raise it in Share, Embed. A 429 response names the limit in its error field: quota_exceeded, visitor_limit_reached or rate_limited. |
| The panel worked, then every question fails | The chat was switched off, the map was unpublished, or the domain was removed. | Restore the setting, or publish the map again (it keeps its id), and reload the page. |
Limits
- Chat only, for one map. The session opens the chat for this published map and nothing else.
- The owner pays, and cannot identify visitors. There is no per-visitor account and no transcript.
- No callbacks. Your page is not told about questions, answers or pin clicks.
- The assistant reads the map's places only where that is on. With answers from the map's own places off, which is the default, the assistant adds its own pins and does not answer from the markers or data of the Studio map. With it on, it reads named points only, up to 200 for one map; it does not read lines, areas, styles or legends.
- The plain iframe embed has no chat. Chat needs the
kaleidr.jsloader and the two elements. - The language of the answers, the panel's title and its placeholder are set with the
lang,chat-titleandplaceholderattributes on the chat element. The panel's other labels are in English. See the Chat reference.
Next steps
- Embed a map: publishing, the iframe form, allowed domains, and what the published map shows of your uploaded data.
- Datasets on a map: uploading data to a map and setting its column roles.
- Theming and layout: restyle the panel and place it.
- Viewer reference: the viewer element's attributes.
- Quota and rate limits: how AI credits are counted.