Quickstart
Create a map in Studio, copy its ID, and embed it. A non-technical user is live in under three minutes; a developer adds an interactive pinned map in under ten lines.
<!-- Paste anywhere you can paste HTML --> <div data-tint="abc123" style="height:420px"></div> <script src="https://embed.tintmap.dev/v1/embed.js" async></script>
AI agents & LLMs
Tint is built so an AI coding agent — Cursor, Claude, Copilot, v0 — can add a branded map to a site without a human reading these docs. Point your agent at tintmap.dev/llms.txt, a plain-text spec of everything below.
A map is designed and published in the Studio, where it gets a stable ID like m_ab12cd34 and is served from embed.tintmap.dev. To place it on a page, an agent needs only that ID and one snippet.
<!-- Paste anywhere you can paste HTML --> <div data-tint="abc123" style="height:420px"></div> <script src="https://embed.tintmap.dev/v1/embed.js" async></script>
An agent can also generate a brand-new map programmatically with POST /v1/maps and a secret API key — see REST API below.
Embeds
Two modes from one map ID. Both are domain-locked, lazy-loaded, and carry automatic OpenStreetMap attribution.
<!-- iframe: simplest, fully isolated --> <iframe src="https://embed.tintmap.dev/m/abc123" width="100%" height="420" loading="lazy" style="border:0;border-radius:12px"></iframe>
JavaScript SDK
Embed the map with the script tag, then drive its markers from your own data at runtime. Once embed.js loads, the global Tint object exposes setMarkers, addMarker, clearMarkers, flyTo, fitBounds, openPopup, closePopup (only one popup is open at a time, except in always mode), plus markerClick / popupOpen / popupClose / ready events. Requires a Starter plan or higher.
<!-- Embed the map, then drive its markers from your own JS (Starter plan & up) --> <div data-tint="abc123" style="height:420px"></div> <script src="https://embed.tintmap.dev/v1/embed.js"></script> <script> Tint.ready("abc123", (map) => { // push markers from your data — e.g. after a search/filter map.setMarkers([ { id: "hq", lat: 41.015, lng: 28.979, label: "HQ", address: "Open until 9pm" }, ]); map.fitBounds(); // frame them all (padding accounts for the pins) map.openPopup("hq"); // open one by id map.on("markerClick", (m) => openDrawer(m.id)); }); </script>
React
A thin React wrapper over the <tint-map> web component. Install @tintmap/react and pass a published map ID; pass markers to drive runtime pins, onMarkerClick / onPopupOpen to handle interaction, and a ref for the imperative calls (fitBounds, openPopup, flyTo).
import { useRef } from 'react'; import { TintMap } from '@tintmap/react'; export default function StoreMap() { const map = useRef(null); return ( <TintMap ref={map} id="m_ab12cd34" height={420} markers={stores} onReady={() => map.current.fitBounds()} // frame every pin onMarkerClick={(m) => map.current.openPopup(m.id)} /> ); }
Web Component
Framework-agnostic — works in plain HTML, Vue, Svelte, Webflow, or anywhere you can add a script. Load the module once, then use the <tint-map> element with a published map ID. The ID can go in id or map-id — use map-id when the same map appears twice on a page (a desktop panel and a mobile modal, say), so the DOM id stays unique.
<script type="module" src="https://embed.tintmap.dev/v1/tint-map.js"></script> <tint-map id="m_ab12cd34" style="height:420px"></tint-map> <!-- same map twice on one page: keep the DOM id unique, pass the map ID as map-id --> <tint-map map-id="m_ab12cd34" id="stores-mobile" style="height:100%"></tint-map>
Mobile apps
There is no native SDK and you don’t need one — point a WebView at https://embed.tintmap.dev/m/MAP_ID and the map renders exactly like the web embed. The runtime API works there too: loaded top-level, window.parent === window, so a message the app posts into the page reaches the map and its events come back to the same window. Same commands and events as the JS SDK.
// WKWebView — load https://embed.tintmap.dev/m/MAP_ID, then bridge both ways. // map -> native (inject at document start) let js = """ window.addEventListener('message', function (e) { if (e.data && e.data.__tintEmbed) window.webkit.messageHandlers.tint.postMessage(e.data); }); """ config.userContentController.addUserScript( WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: true)) config.userContentController.add(self, name: "tint") // native -> map webView.evaluateJavaScript(""" window.postMessage({ __tintMsg: 1, id: 'MAP_ID', cmd: 'setMarkers', markers: \(json) }, '*'); window.postMessage({ __tintMsg: 1, id: 'MAP_ID', cmd: 'fitBounds' }, '*'); """)
// Android WebView
webView.settings.javaScriptEnabled = true // the map is WebGL + JS
webView.addJavascriptInterface(object {
@JavascriptInterface fun onEvent(json: String) { /* markerClick, popupOpen, ... */ }
}, "TintNative")
// after onPageFinished:
webView.evaluateJavascript("""
window.addEventListener('message', function (e) {
if (e.data && e.data.__tintEmbed) TintNative.onEvent(JSON.stringify(e.data));
});
window.postMessage({ __tintMsg: 1, id: 'MAP_ID', cmd: 'setMarkers', markers: $json }, '*');
""", null)Don’t wait for the ready event — it can fire before your listener is attached, and you don’t need it: commands are applied whenever they arrive. A WebView sends no Referer, so the domain allow-list can’t gate a mobile app — the map’s embed token does. Append ?t=TOKEN (publish screen → Mobile tab). It is only enforced once you switch on Require a token for app embeds under Settings → Allowed domains; website embeds are never affected, and the token is public by design since it ships inside your app. Loads always meter against your quota.
Location features
Optional touches for store, venue and office maps — set them visually in the Studio, or pass them to POST /v1/maps.
REST API
Create and publish maps from your own backend or an AI agent. Authenticate with a secret key (Authorization: Bearer sk_live_…) from the API Keys page — keep it server-side. Requires a Pro plan.
curl -X POST https://tintmap.dev/v1/maps \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "name": "Our stores", "theme": "midnight", "center": [41.0289, 28.974], "zoom": 12, "markers": [{ "lat": 41.0289, "lng": 28.974, "label": "HQ" }] }'