Use a Kaleidr basemap
At the end of this guide your page shows an interactive Kaleidr basemap in the
style and at the camera you chose, and your code can move the camera and swap
the style. This works on every plan, Free included. You need a publishable key
that carries the maps scope, which new keys carry by default.
Before you start
- A publishable key (
kld_pk_live_…) from kaleidr.com/api-keys. See Get an API key. - The origin of the page you will test on, for example
http://localhost:3000, in the key's allowed origins. A key with no allowed origins works on any site. - A way to serve the page over HTTP, for example
npx serve .. A page opened from disk has no usable origin, so a key with allowed origins refuses it. - If your site sends a Content Security Policy:
https://cdn.kaleidr.cominscript-src,https://api.kaleidr.cominconnect-srcandhttps://map-embed.kaleidr.cominframe-src. See Content Security Policy.
Step 1: Add the map
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
<kaleidr-map product="tile"
publishable-key="kld_pk_live_…"
style-id="kaleidr-morning"
style="display:block; height:480px"></kaleidr-map>
What you should see: a "Loading map…" indicator, then a bright basemap.
In the Network tab there is a POST to
https://api.kaleidr.com/inference-api/b2b/v1/sdk/sessions with status 200,
followed by a document request to https://map-embed.kaleidr.com/. The
frame's URL carries the style id and no credential: your key is sent in the
session request only, and the session token is not put in the frame's URL.
The element needs a height; without one the map falls back to a fixed 500
pixels. <kaleidr-map> is display:block by default, so the height applies.
Keep display:block in the style all the same: it is harmless, and it covers
Safari before 16.4, where the element stays inline and ignores height.
Step 2: Choose a style
Set style-id to one of the general-purpose styles:
style-id | Look |
|---|---|
kaleidr-morning | Bright: white ground, cyan water, white roads. |
kaleidr-afternoon | Mid-tone ground with green vegetation and gray roads. |
kaleidr-evening | Golden hour: warm sand and olive tones. |
kaleidr-night | Dark navy and charcoal with amber highways. |
kaleidr-plain | White canvas with borders and place labels. 3D buildings appear from zoom 15. |
kaleidr-editorial | The afternoon palette with labels off, for small or decorative maps. |
<kaleidr-map product="tile" publishable-key="kld_pk_live_…"
style-id="kaleidr-night"
style="display:block; height:480px"></kaleidr-map>
What you should see: the same map area in the dark palette.
Always pass style-id. If you leave it out, the embed uses your
organization's default basemap when one is set. When none is set, the frame
shows "Couldn't find a map id in the URL."
Style ids are exact. There are no short aliases: night is not
kaleidr-night.
Step 3: Set the camera
Without a camera, the map opens on the style's own default view, which for
most styles is the whole world. Pass center (longitude first, then latitude)
and zoom to open on your area. pitch tilts the view and bearing rotates
it.
<kaleidr-map product="tile" publishable-key="kld_pk_live_…"
style-id="kaleidr-morning"
center="-0.1276,51.5072" zoom="12" pitch="45" bearing="-20"
style="display:block; height:480px"></kaleidr-map>
What you should see: central London, tilted and rotated.
zoom is ignored unless you also pass center. pitch and bearing
work alone.
Step 4: Move the camera and swap the style from code
The element is the declarative form. For control from JavaScript, mount with
Kaleidr.mount and keep the handle it returns.
<div id="basemap" style="height:480px"></div>
<p>
<button id="paris">Fly to Paris</button>
<button id="night">Night</button>
<button id="day">Morning</button>
</p>
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
<script>
const handle = Kaleidr.mount('#basemap', {
product: 'tile',
publishableKey: 'kld_pk_live_…',
styleId: 'kaleidr-morning',
center: [-0.1276, 51.5072],
zoom: 11,
});
document.getElementById('paris').addEventListener('click', () => {
// duration is in milliseconds. Leave it out to jump with no animation.
handle.setCamera({ center: [2.3522, 48.8566], zoom: 12, duration: 1200 });
});
document.getElementById('night').addEventListener('click', () => {
handle.setStyle('kaleidr-night');
});
document.getElementById('day').addEventListener('click', () => {
handle.setStyle('kaleidr-morning');
});
</script>
What you should see: each button changes the map in place. The iframe does not reload, and no new session request appears in the Network tab.
Things to know:
setCameraandsetStyleare on the handle, not on the element.setStyleexists only on tile. On other products it logs a warning and does nothing.- You can call the handle at any time after
Kaleidr.mountreturns. Calls made before the map inside the iframe is ready are kept and applied, in order, when the map reports ready, so you do not have to wait for thekaleidr-embed:readymessage (Step 6). To choose the camera and style the map opens with, use mount options. - On a mounted element, changing
center,zoom,pitchorbearingmoves the map. Changingstyle-iddoes nothing.
Step 5: Pair a chat panel with the map (optional)
A Kaleidr chat panel can use a tile map as its map: it plots the places in
its answers as pins, frames the camera on them, and draws routes and area
outlines. Wrap the tile element in a
positioned container, lay a chat element over it, and point the chat's
map-target at the tile element.
<div style="position:relative">
<kaleidr-map id="basemap" product="tile"
publishable-key="kld_pk_live_…"
style-id="kaleidr-morning"
center="-0.1276,51.5072" zoom="11"
style="display:block; height:560px"></kaleidr-map>
<kaleidr-map product="chat" publishable-key="kld_pk_live_…" contained
map-target="#basemap"
style="display:block; position:absolute; inset:0; z-index:1; pointer-events:none"></kaleidr-map>
</div>
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
What you should see: the chat panel docked at the top-left of the map. Ask "coffee near Covent Garden" and pins appear on the tile map.
What the chat element's attributes and styles do:
| Part | Why it is there |
|---|---|
map-target="#basemap" | A CSS selector for the tile element the chat should drive. |
contained | Docks the panel inside the chat element. Without it, the panel is a fixed window at the top-left of the viewport. |
position:absolute; inset:0 | Stretches the chat element over the map, so the panel docks at the map's corner. |
z-index:1 | Keeps the chat layer above the map. |
pointer-events:none | Lets the visitor drag and zoom the map everywhere the panel is not. The panel stays clickable. |
Notes:
- The same key works for both elements, because a default key carries both
the
mapsandaiscopes. The chat element makes its own session request, and each question spends AI credits. - The order of the two elements does not matter. The chat looks up its map when it needs it.
Layout and theme options for the panel are in Theming and layout. What the assistant can and cannot do is in Capabilities and limits.
Step 6: Know when the map loaded (optional)
The SDK has no ready promise and the element fires no events. The iframe
posts a message to your page when the map has drawn, and another if something
fails to draw:
const target = document.getElementById('basemap');
window.addEventListener('message', (event) => {
if (event.origin !== 'https://map-embed.kaleidr.com') return;
const frame = target.querySelector('iframe');
if (!frame || event.source !== frame.contentWindow) return;
const msg = event.data;
if (!msg || typeof msg.type !== 'string') return;
if (msg.type === 'kaleidr-embed:ready') {
// { type, id: 'kaleidr-morning', source: 'tile' }
console.log('basemap loaded', msg.id);
} else if (msg.type === 'kaleidr-embed:error') {
// { type, id, code: 'maplibre_error', message }, or
// { type, id: null, code: 'no_id', message } when there is no style
console.warn('basemap problem', msg.code, msg.message);
}
});
A failure of the session request does not produce a message, because no iframe is created. It appears in the console instead. See If it does not work.
A complete page
Save this as index.html, put your key in, and serve the folder over HTTP
from an origin the key allows.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kaleidr basemap</title>
<style>
body { font-family: system-ui, sans-serif; margin: 2rem; }
#basemap { height: 480px; }
</style>
</head>
<body>
<h1>Kaleidr basemap</h1>
<p id="status">Loading the map…</p>
<div id="basemap"></div>
<p>
<button id="paris">Fly to Paris</button>
<button id="night">Night</button>
<button id="day">Morning</button>
</p>
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
<script>
const target = document.getElementById('basemap');
const status = document.getElementById('status');
const handle = Kaleidr.mount(target, {
product: 'tile',
publishableKey: 'kld_pk_live_…',
styleId: 'kaleidr-morning',
center: [-0.1276, 51.5072],
zoom: 11,
});
window.addEventListener('message', (event) => {
if (event.origin !== 'https://map-embed.kaleidr.com') return;
const frame = target.querySelector('iframe');
if (!frame || event.source !== frame.contentWindow) return;
const msg = event.data;
if (msg && msg.type === 'kaleidr-embed:ready') {
status.textContent = 'Map loaded.';
}
});
document.getElementById('paris').addEventListener('click', () => {
handle.setCamera({ center: [2.3522, 48.8566], zoom: 12, duration: 1200 });
});
document.getElementById('night').addEventListener('click', () => {
handle.setStyle('kaleidr-night');
});
document.getElementById('day').addEventListener('click', () => {
handle.setStyle('kaleidr-morning');
});
// In a single-page app, call handle.destroy() when the map leaves the screen.
</script>
</body>
</html>
How it behaves
| Topic | Behavior |
|---|---|
| Session | The SDK exchanges your key for a short-lived tile session before it creates the iframe, and renews it for as long as the mount exists. The exchange checks the key and counts the map load. The session is not passed to the frame, whose URL carries the style id and the camera only. |
| Opening view | The camera you pass, otherwise the style's default view. |
| Scroll | Scrolling with the pointer over the map zooms the map. With the pointer outside the map, scrolling moves your page. On touch screens, dragging moves the map and pinching zooms it. |
| Height | Follows the mount target when it has a height at mount time, otherwise a fixed 500 pixels. |
| Loading | The iframe loads lazily, when it comes near the viewport. |
| Removal | handle.destroy() removes the iframe and stops the session renewals. Removing a <kaleidr-map> element does the same. |
| Re-insertion | Putting a removed element back, or moving it to another parent, mounts it again with a new session. |
How map loads are counted
One map load is counted each time the SDK obtains a tile session:
| Event | Map loads |
|---|---|
| The map mounts | 1 |
| The session is renewed while the mount exists | 1 per renewal |
| You destroy the mount and mount again, or re-insert the element | 1 |
setCamera, setStyle, panning, zooming | 0 |
Sessions last 15 minutes by default, and the SDK renews one 90 seconds before it expires, in the foreground or the background. A visitor who stays a few minutes uses one map load. A page left open for an hour uses about five.
To keep usage down:
- Destroy the mount when the map is hidden for good: when a dialog closes, when the route changes, when a tab panel is discarded.
- Do not re-create the element on every render. In React, Vue and other frameworks, see Framework integration.
- Use
setStyleto change the style, not a remount.
When your organization has used its map loads for the period, a new session
is refused with HTTP 429 and the body {"detail":"map_load_quota_exceeded"}.
A map that is already on screen stays on screen. A new mount renders nothing.
See Quota and rate limits.
Check that it worked
- On the page: the map is interactive and shows the credits line at the bottom.
- In the console: no line that contains
[kaleidr. - In the Network tab:
kaleidr.jsandtile.jsfromhttps://cdn.kaleidr.com; onePOST …/sdk/sessionswith status 200 and a JSON body that hassession_tokenandexpires_in; one document request tohttps://map-embed.kaleidr.com/?style=…. - In your listener: one
kaleidr-embed:readymessage whosesourceis"tile".
If it does not work
The SDK logs a failed mount to the console as [kaleidr:tile] …. For a
refused session it logs only the HTTP status. The reason is in the response,
which you can read in the Network tab by selecting the sessions request. A
403 carries its code in the detail field, and so does
map_load_quota_exceeded. A 429 rate_limited carries its code in the
error field.
| Symptom | Cause | Fix |
|---|---|---|
Nothing renders. The console shows a CORS error for …/sdk/sessions and a [kaleidr:tile] fetch error | The key is unknown or revoked, or the page's origin is not in the key's allowed origins. The refusal carries no CORS headers, so the browser reports it as a CORS error. | Check the key. Add the page's exact origin (scheme, host and port) to the key at kaleidr.com/api-keys, then allow a few minutes for the change to apply. The request's status in the Network tab is 401 or 403. |
Console: [kaleidr:tile] kaleidr session exchange failed (403) | The key does not carry the maps scope (insufficient_scope), is limited to other products (product_not_allowed), or your plan does not admit the request (tier_capability_not_allowed). | Read detail in the Network tab. Create a key with the default scopes. |
Console: [kaleidr:tile] kaleidr session exchange failed (429) | map_load_quota_exceeded: the organization's map loads are used up. rate_limited: too many session requests for this key in a short time. | Read detail for the first and error for the second. See Quota and rate limits. |
The sessions request returns 404 | An api-base override without the /inference-api path. | Remove the override, or use https://api.kaleidr.com/inference-api. |
Console: [kaleidr:tile] Kaleidr: a server key (kld_sk_…) must never be used in the browser… | You passed a server key. The SDK refuses it before any request is made. | Use a publishable key. Rotate the server key if the page was ever public. |
Console: [kaleidr:tile] Kaleidr tile: invalid style "…"… | style-id contains a character that is not allowed, such as a space or ?. | Use a style id from the table in Step 2. |
The frame shows "Couldn't find a map id in the URL.", and your listener receives an error with code: 'no_id' and id: null | No style-id, and your organization has no default basemap. | Pass style-id. |
The frame is empty and your listener receives kaleidr-embed:error with maplibre_error | The style does not exist, usually a misspelled id. | Use a style id from the table in Step 2. |
Console: [kaleidr] <kaleidr-map> requires product="chat|viewer|editor|tile" | The product attribute is missing or misspelled. | Set product="tile". |
The map is 500 pixels tall whatever height you set | The 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 it display:block and a height. |
| An empty box, and the console reports a Content Security Policy violation | Your policy blocks the loader, the session request or the frame. | Allow the three hosts listed in Before you start. |
setStyle or setCamera does nothing | Called on the element, or the map never loaded. | Call it on the handle from Kaleidr.mount. Check for a kaleidr-embed:error message and the console. A call made before the map is ready is applied when it becomes ready. If the map has not reported ready after a bounded wait, normally 10 seconds after the frame loads, the call is sent anyway and the console shows [kaleidr] the map frame has not reported ready: a frame that is showing an error has no map to change. |
Changing style-id on the element does nothing | Expected. The element reads style-id once. | Use handle.setStyle, or insert a new element. |
Limits
- Your page cannot add markers, layers or popups to a tile map, and receives no click or camera events from it. A paired chat panel can plot places on it.
- There is no method that reads the camera back.
- The Tile product is the supported way to show a Kaleidr basemap.
- A tile map does not render on a page whose origin the key does not allow.
Next steps
- Tile reference for every option, attribute and message.
- Framework integration to mount and destroy the map from React, Next.js or Vue without extra map loads.
- Chat to add the assistant to the map.
- Quota and rate limits for map load allowances.