How Kaleidr works
This page explains the parts of Kaleidr that every integration touches and how they fit together. Read it once and the product pages, the key settings and the error messages will make sense.
In short:
- You load one script, the loader. It gives you four products: Chat, Viewer, Tile and Editor.
- Two products render in your page and two render in a Kaleidr iframe. That one fact decides what your Content Security Policy and your layout have to do.
- A publishable key in your page is exchanged for a short-lived session, and the session makes the calls. A server key calls the API directly from your backend.
- Keys belong to an organization. The organization's plan decides what its keys may do, and its usage is counted in two pools: AI credits and map loads.
The picture
YOUR PAGE (https://www.example.com)
+--------------------------------------------------------------------+
| <script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"> |
| the loader: window.Kaleidr and the <kaleidr-map> element |
| |
| RENDERS IN YOUR PAGE RENDERS IN A KALEIDR IFRAME |
| +------------------------------+ +---------------------------+ |
| | Chat panel on your map | | Viewer a published map | |
| | Editor map editor | | Tile a basemap | |
| +---------------+--------------+ +-------------+-------------+ |
+------------------|---------------------------------|---------------+
| |
session token | | iframe document
v v
api.kaleidr.com map-embed.kaleidr.com
sessions, answers, the map itself, running
places, routes under Kaleidr's own policy
^
| server key (kld_sk_live_...)
|
YOUR BACKEND
The loader
Everything in the browser starts with one script:
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
The loader is small (about 17 KB before compression) and contains no map library and no product code. Loading it does two things:
- It defines
window.Kaleidr, withmount(target, options)to mount a product from JavaScript, andenable(on)to hold or release the mounts made after the call. It cannot hold back<kaleidr-map>elements that are already in the HTML, because they mount as the loader runs; to hold those, set the page flagwindow.__KALEIDR_EMBED_ENABLED__ = falsebefore the loader script. See the enable gate. - It registers the
<kaleidr-map>element, which mounts a product from HTML.
When you mount a product, the loader fetches that product's code from https://cdn.kaleidr.com/embed/v1/ and runs it. A page that mounts only Viewer never downloads Chat.
Kaleidr.mount returns a handle immediately, before the product has loaded. Calls you make on the handle in the meantime are kept and applied, in order, once the product is ready; for Viewer and Tile that is when the map inside the frame reports ready. A failed mount does not throw: if something fails, the loader logs one console error that contains [kaleidr:<product>] and the handle does nothing. The one call that throws is Kaleidr.mount with no options object, which raises a TypeError. There is no promise to await and no event that says a mount succeeded; Troubleshooting lists what to look for instead.
Reference: kaleidr.js and the <kaleidr-map> element.
The four products
| Product | What it is | Renders | Key | Scope it needs |
|---|---|---|---|---|
| Chat | A chat panel that answers questions about places and shows the answers on a map: pins, camera moves, routes | In your page | Publishable key, or none on a published map | ai |
| Viewer | A map published from Kaleidr Studio | In a Kaleidr iframe | None | None |
| Tile | A Kaleidr-designed basemap as a ready-made interactive map | In a Kaleidr iframe | Publishable key | maps |
| Editor | A map editor: text, images, markers, drawing | In your page | Publishable key | design |
Chat is the product that attaches to something you already have. It works with a map you run yourself (Mapbox GL JS, MapLibre GL JS, the Google Maps JavaScript API or Leaflet), with a Viewer or Tile element on the same page, or with no map at all as a chat-only panel.
All four products run on every plan, Free included.
Two ways a product renders
Where a product's code runs is the most useful thing to know about it.
In your page: Chat and Editor
Chat and Editor run as scripts in your own document. Their elements are part of your DOM and their network requests come from your page.
What follows from that:
- Your Content Security Policy applies to them. If your site sends a policy, it must allow scripts from
https://cdn.kaleidr.com, connections tohttps://api.kaleidr.com, and inline styles, because both products add their styles to your page. Editor also loads its basemap fromhttps://tile.kaleidr.com. - Their styles share your page. Chat adds one
<style>tag to your document head and sets its theme values as CSS variables on the root element. Those are page-wide and remain after the panel is destroyed. - Layout is yours to provide. By default the chat panel is a fixed window at the top-left of the browser window. To dock it inside your map, mount it with
contained: trueinto an element that is positioned and has a height. See Theming and layout. - Chat can use your map object. Because it runs in your page, Chat calls your map library directly to add its own markers, move the camera and draw routes. It does not change your layers, sources or markers, and it sends none of them to Kaleidr.
In a Kaleidr iframe: Viewer and Tile
Viewer and Tile put an <iframe> inside the element you mount them on. The map runs inside that frame, in a document served from https://map-embed.kaleidr.com.
What follows from that:
- Your policy needs very little. Allow the loader script from
https://cdn.kaleidr.comand a frame fromhttps://map-embed.kaleidr.com. Tile also callshttps://api.kaleidr.comfrom your page to exchange the key. Nothing the map loads inside the frame is subject to your policy. - Nothing leaks into your page. The frame's styles and scripts cannot affect your document, and yours cannot affect the map.
- You size the box; the frame fills it. Give the element a height. A
<kaleidr-map>element isdisplay:blockby default, so the height applies. The snippets writestyle="display:block; height:480px", which also covers Safari before 16.4, where the element stays inline. An element with no height gets a frame 500 pixels tall. - You cannot reach the map object. There is no map instance to call. You control the map through the handle (
setCameraon both,setStyleon Tile) and learn that it loaded or failed from two messages the frame posts to your window. See Viewer embed.
| In your page (Chat, Editor) | In a Kaleidr iframe (Viewer, Tile) | |
|---|---|---|
| Your Content Security Policy must allow | Kaleidr scripts, API connections, inline styles | The loader script and one frame host |
| Styles | Share your page | Isolated in the frame |
| Layout | You position the panel or the editor | You size the element; the frame fills it |
| Access to a map object | Chat drives the map you pass it | None; use the handle |
| Needs a map library on your page | Chat uses yours; Editor brings its own | No |
The complete policy for each product is in Content Security Policy.
Keys
A key identifies your organization. There are two types, and they are used in different ways.
Publishable keys and sessions
A publishable key (kld_pk_live_…) is made to sit in page source. It cannot call the API by itself. Instead, when you mount a keyed product, the SDK performs a session exchange:
- The SDK sends the key to
POST https://api.kaleidr.com/inference-api/b2b/v1/sdk/sessionswith the product name. The browser adds your page's origin in theOriginheader. - Kaleidr checks, in this order: the key is a publishable key; the origin is in the key's allowed origins, if it has any; the key and your plan admit the product; the key carries the scope the product needs. For Tile it also checks that your organization has a map load left.
- Kaleidr returns a session token (
kld_sess_live_…) and its lifetime in seconds.
The session token is what makes every later call. It travels in a request header and is never put in a URL. It is:
- Short-lived. A session lasts about 15 minutes.
- Bound to one origin. A session used from any other origin is refused with 403
session_origin_mismatch. - Bound to one product. A Chat session carries only what Chat needs.
The SDK keeps the session fresh for as long as the product is mounted. It requests a new one 90 seconds before the current one expires. Browsers slow timers in background tabs, so it also checks when a tab becomes visible again and renews at once if the session is due. If a renewal fails it tries again every 30 seconds. You do not handle any of this.
Tile is the one keyed product with no later calls. Its map runs in a Kaleidr frame that needs no credential, so the exchange for a Tile mount exists to check the key and count the map load, and to tell the SDK your organization's default basemap. The frame's URL carries the style id and the camera and no key or token.
If the first exchange fails, the mount fails and is not retried. That is the failure you will meet while integrating, and it has a small number of causes: a wrong or revoked key, an origin that is not allowed, a product or scope the key does not carry. Each is described in Troubleshooting.
Server keys
A server key (kld_sk_live_…) is a secret for your backend. There is no exchange: you send the key itself on each request, as Authorization: Bearer kld_sk_live_… or in an X-Api-Key header.
A server key is refused on any request that carries an Origin header (403 server_key_in_browser), and the SDK will not mount with one. Server keys are available on Pro and Enterprise.
The server API offers what Chat uses, as plain HTTP: a streamed answer with places and map actions, place details, geocoding and routes. There is no JavaScript client library for it and no npm package; you call it with any HTTP client. Start at Server quickstart.
No key at all
Viewer needs no key. A published map's id is public, and the map's owner decides which domains may show it.
Chat can also run without a key when it is attached to a published map whose owner has switched Visitor AI Chat on. The SDK then exchanges the map's share id for a session at …/b2b/v1/sdk/share-sessions. That session lasts five minutes, which is why switching Visitor AI Chat off stops the chat within five minutes. See Chat on a published map.
Get an API key covers creating keys and their settings.
Organizations and plans
An organization is the account that owns things in Kaleidr: API keys, published maps, the plan and the usage. A new Kaleidr account gets its own organization. Owners and admins of an organization manage its keys.
The organization's plan is Free, Pro or Enterprise. The plan decides:
- which key types the organization can create (server keys start at Pro),
- which scopes its keys may carry (
vendor, which lets answers made with a key draw on the organization's uploaded data, is Enterprise only), - whether it may upload its own business data for AI answers (Enterprise only),
- how much data its members can add to maps in Kaleidr Studio (every plan can; the limits are larger on Pro than on Free),
- how many active keys it may hold,
- the size of its two usage pools.
Usage is counted per organization, not per key. Ten keys on one organization share one set of pools.
The two metered pools
Kaleidr meters two things.
AI credits
AI credits pay for answers. A streamed chat answer costs credits according to the size of the conversation sent and the answer returned; some server API lookups cost a fixed number of credits. The pool is one per organization, and it is shared by everything that organization does with Kaleidr AI: its API keys, the visitor chat on its published maps, and its members' own use of Kaleidr's apps.
When the pool cannot cover a request, the request is refused with HTTP 429 and "error": "quota_exceeded". In the chat panel the visitor sees the line "You've reached your usage limit for now." in the conversation.
Map loads
A map load is one start of a Kaleidr map: a published map viewed on a site, a shared map link opened, or a Tile session. Individual map tiles are not counted. The embedded Editor adds map loads in bulk for its basemap requests; Quota and rate limits has the rule.
When the pool is spent, a Tile mount fails its session exchange with 429 map_load_quota_exceeded, and a published map shows "This map has reached its map load limit. It will be available again when the owner's allowance resets." in its frame.
Both pools reset each billing period: your subscription cycle if you have one, otherwise the calendar month. Add-on packs extend either pool without a plan change. Allowances for Free and Pro are on the pricing page; Enterprise allowances are custom and set in your contract. Quota and rate limits has the request-rate limits and the exact response bodies.
Who pays
Usage is charged to the organization whose credential made it possible. A visitor never pays and never needs a Kaleidr account.
| Scenario | Credential | What is spent | Whose pool |
|---|---|---|---|
| Chat on your site, mounted with your publishable key | A session from your key | AI credits for each answer | Yours |
| Your backend calls the server API | Your server key | AI credits for each call | Yours |
| A Tile basemap on your site | A session from your key | One map load for each Tile session | Yours |
| A published map embedded on any site | None | Map loads | The map owner's |
| Visitor AI Chat on a published map, on any site | A session from the map's share id | AI credits, up to the monthly limit the owner set for that map | The map owner's |
| An Editor on your site | A session from your key | Nothing metered | — |
Two consequences are worth planning for:
- Your publishable key spends your credits wherever it runs. Without allowed origins, someone who copies the key can run Chat on their site at your expense. List your origins.
- A published map with Visitor AI Chat spends its owner's credits for every visitor. The owner can set a monthly AI credit limit on the map, and each visitor is rate-limited.
One question, start to finish
Here is everything that happens when a visitor opens a page with Chat attached to a MapLibre map and asks one question. The page is the one from the Quickstart.
1. The page loads the loader.
The browser fetches kaleidr.js from cdn.kaleidr.com. window.Kaleidr now exists. Nothing else has been requested.
2. Your code mounts Chat.
Kaleidr.mount('#stage', { product: 'chat', publishableKey, map, contained: true }) returns a handle. In the background the SDK starts two requests at once: it fetches the Chat code from the CDN, and it exchanges your key.
3. The session exchange.
POST /inference-api/b2b/v1/sdk/sessions HTTP/1.1
Host: api.kaleidr.com
Origin: https://www.example.com
Content-Type: application/json
X-Api-Key: kld_pk_live_…
{"product": "chat"}
{
"session_token": "kld_sess_live_…",
"expires_in": 900,
"product": "chat"
}
The response may also carry basemap_style_id, your organization's default basemap, which Tile and Editor use when you do not name a style.
4. The panel renders.
With the code and the session both ready, Chat adds its styles to your page, renders the panel inside #stage and attaches to the map you passed. No question has been sent and no AI credits have been spent.
5. The visitor asks a question. The panel sends the conversation to the stream endpoint, authenticated with the session token. The body is abridged here to the fields that carry data:
POST /inference-api/b2b/v1/chat/control/stream HTTP/1.1
Host: api.kaleidr.com
Origin: https://www.example.com
Authorization: Bearer kld_sess_live_…
Content-Type: application/json
Accept: text/event-stream
{
"session_id": "8b1f0c1e-…",
"messages": [
{ "role": "user", "content": "Where can I get ramen near Bryant Park?" }
],
"map_zoom": 11,
"lang": "en"
}
That is what leaves the page for a question: the messages of the conversation so far, an id the panel generates for each conversation (a new one starts when the visitor closes or minimizes the chat), the map's zoom level and a language tag. The panel does not send your layers, your markers or the content of your page. Capabilities and limits lists every request the panel makes.
6. The answer streams back. The response is a stream of events that stays open until the answer is complete. Abridged:
event: phase
data: {"type":"phase","phase":"thinking","session_id":"8b1f0c1e-…"}
event: start
data: {"type":"start","session_id":"8b1f0c1e-…","purpose":"control"}
data: {"text":"Three ramen shops are within a short walk of Bryant Park. "}
event: place
data: {"name":"…","coordinates":{"lat":40.7527,"lng":-73.9838},"address":"…","category":"…"}
event: end
data: {"type":"end","full_text":"…","places":[…],"actions":[…]}
event: quota
data: {"type":"quota","meter":"b2b.tokens","reset_at":"2026-11-01T04:00:00+00:00"}
The quota event of a stream made with a session token carries only type, meter and reset_at. Usage numbers are sent to a server key only. The stream contract defines every event.
7. The panel acts on the events.
As each piece arrives the panel writes the text into the conversation, adds a pin to your map for each place that has coordinates, and moves the camera to fit the pins. If the answer includes a route, the panel draws it. All of this happens on your map through your map library, inside your page.
8. The answer is charged.
When the stream ends, the answer's cost is taken from your organization's AI credits. The final quota event names the pool and says when it resets. In the browser it carries no usage numbers; read your usage on kaleidr.com/billing.
9. The session renews. About thirteen and a half minutes after step 3, the SDK exchanges the key again and the panel carries on with the new token. The visitor notices nothing.
If the visitor selects a pin, the panel makes further calls for that place's details and photos; those are described in Capabilities and limits.
A backend integration is the same from step 5 onward, with two differences: you send a server key instead of a session token, and your own code reads the events and decides what to do with them. See Reading the stream.
Next steps
- Choose your path: match what you want to build to a product, a key type and a first page.
- Get an API key: create the key and set its allowed origins.
- Security model: what each credential protects and the controls you have.
- Production checklist: what to settle before you ship.