Skip to main content

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.com in script-src, https://api.kaleidr.com in connect-src and https://map-embed.kaleidr.com in frame-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-idLook
kaleidr-morningBright: white ground, cyan water, white roads.
kaleidr-afternoonMid-tone ground with green vegetation and gray roads.
kaleidr-eveningGolden hour: warm sand and olive tones.
kaleidr-nightDark navy and charcoal with amber highways.
kaleidr-plainWhite canvas with borders and place labels. 3D buildings appear from zoom 15.
kaleidr-editorialThe 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:

  • setCamera and setStyle are on the handle, not on the element.
  • setStyle exists only on tile. On other products it logs a warning and does nothing.
  • You can call the handle at any time after Kaleidr.mount returns. 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 the kaleidr-embed:ready message (Step 6). To choose the camera and style the map opens with, use mount options.
  • On a mounted element, changing center, zoom, pitch or bearing moves the map. Changing style-id does 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:

PartWhy it is there
map-target="#basemap"A CSS selector for the tile element the chat should drive.
containedDocks the panel inside the chat element. Without it, the panel is a fixed window at the top-left of the viewport.
position:absolute; inset:0Stretches the chat element over the map, so the panel docks at the map's corner.
z-index:1Keeps the chat layer above the map.
pointer-events:noneLets 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 maps and ai scopes. 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​

TopicBehavior
SessionThe 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 viewThe camera you pass, otherwise the style's default view.
ScrollScrolling 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.
HeightFollows the mount target when it has a height at mount time, otherwise a fixed 500 pixels.
LoadingThe iframe loads lazily, when it comes near the viewport.
Removalhandle.destroy() removes the iframe and stops the session renewals. Removing a <kaleidr-map> element does the same.
Re-insertionPutting 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:

EventMap loads
The map mounts1
The session is renewed while the mount exists1 per renewal
You destroy the mount and mount again, or re-insert the element1
setCamera, setStyle, panning, zooming0

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 setStyle to 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.js and tile.js from https://cdn.kaleidr.com; one POST …/sdk/sessions with status 200 and a JSON body that has session_token and expires_in; one document request to https://map-embed.kaleidr.com/?style=….
  • In your listener: one kaleidr-embed:ready message whose source is "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.

SymptomCauseFix
Nothing renders. The console shows a CORS error for …/sdk/sessions and a [kaleidr:tile] fetch errorThe 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 404An 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: nullNo 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_errorThe 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 setThe 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 violationYour policy blocks the loader, the session request or the frame.Allow the three hosts listed in Before you start.
setStyle or setCamera does nothingCalled 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 nothingExpected. 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​