Authentication
Calls to protected data APIs, including maps, geocoding, routing and Optimize, require an Unmap API key on every plan. Most dev-origin traffic is free; Optimize bills per performed shipment even from dev origins. See Plans & Limits.
Key forms
A key looks like um_live_... or um_test_.... The prefix is checked before anything else: a
string that does not start with one of the two is rejected without a lookup, so a stray value
never even reaches key storage.
um_live_is your production key.um_test_is for local development and staging.@unmap/sdk's quickstart ships a publicum_test_demo key that is rate-limited and safe to embed for trying the API before you have your own.
Both forms follow the same authentication, rate-limit and billing rules. The prefix identifies
the key's environment and does not exempt a request from billing. For most endpoints, the
Origin or Referer determines whether dev-origin traffic is free. Optimize uses its own
per-shipment rules on all origins. See Plans & Limits.
Presenting the key
Four forms are accepted:
Authorization: Bearer <key>is the normal form for server-side calls.X-API-Key: <key>is for clients that reserve theAuthorizationheader for something else.X-Goog-Api-Key: <key>accepts an Unmap key for clients that use Google's API-key header. It works on every protected data endpoint. Google Route Optimization uses OAuth; see the migration guide for replacing its credentials.?key=<key>is a query parameter. Generated style URLs use this form for tiles, terrain, contours, glyphs, and sprites. Those endpoints also accept the three headers above for direct calls. SDK clients attach the key automatically;optimizeTours()usesX-API-Key.
# Authorization header
curl "https://api.unmap.dev/geocode/search?q=Calgary+Tower" \
-H "Authorization: Bearer $UNMAP_API_KEY"
# X-API-Key header
curl "https://api.unmap.dev/geocode/search?q=Calgary+Tower" \
-H "X-API-Key: $UNMAP_API_KEY"
# X-Goog-Api-Key header, as a Google REST client sends it
curl "https://api.unmap.dev/geocode/search?q=Calgary+Tower" \
-H "X-Goog-Api-Key: $UNMAP_API_KEY"
# Query parameter, the form MapLibre uses for the tile, glyph and sprite requests it fires
curl "https://api.unmap.dev/tiles/11/375/685.mvt?key=$UNMAP_API_KEY" -o tile.mvt// Authorization header
await fetch("https://api.unmap.dev/geocode/search?q=Calgary+Tower", {
headers: { Authorization: `Bearer ${key}` },
});
// X-API-Key header
await fetch("https://api.unmap.dev/geocode/search?q=Calgary+Tower", {
headers: { "X-API-Key": key },
});
// X-Goog-Api-Key header, as a Google REST client sends it
await fetch("https://api.unmap.dev/geocode/search?q=Calgary+Tower", {
headers: { "X-Goog-Api-Key": key },
});
// Query parameter, the form MapLibre uses for the tile, glyph and sprite requests it fires
await fetch(`https://api.unmap.dev/tiles/11/375/685.mvt?key=${key}`);If more than one form is present, the headers win, in this order: Authorization first, then
X-API-Key, then X-Goog-Api-Key, then ?key=.
The gateway strips ?key= before it forwards a request to a backend or files a response in the
edge cache, so a cached tile or geocoding response is shared across customers and never carries
anyone's key. What it does keep is a hash of the key, counted per endpoint for billing.
Can the key go in the browser?
Yes, and it has to. The browser fetches the tiles itself, so the key travels in URLs anyone can read in the network panel. A maps key is a public credential in the same sense as a Stripe publishable key: finding it is not an exploit, and there is no server-only variant that would let a map render without it.
Secrecy is therefore not the control. Two other things are, and both are in the dashboard:
- Allowed origins, set per key. List the exact origins a key may be presented from, one per
line, on the key itself. A key with a list is strict: requests from anywhere else are refused
with a
403and the codeorigin_not_allowed, and so are requests with noOriginorRefererat all. A key with an empty list is unrestricted, which is what a server key should be. Dev origins are not implicit: listhttp://localhost:3000if that is where you develop. - The spend cap, which starts at
$0on every paid plan. Someone using a key they found cannot run up an overage you did not opt into. They could still spend your included calls, which is what the origin list is for.
Use two keys, not one: a scoped key for the browser and an unscoped one for your backend. A
backend sends no Origin, so scoping the key your server uses would refuse your own server.
Be honest about what this buys. Origin and Referer are set by the browser, and anything that
is not a browser can send whatever it likes. An allowed-origins list stops a key lifted off your
page from working on someone else's page. It does not stop a script. If you think a key has been
taken, roll it from the dashboard: you get a new key with the same name, origins and add-ons,
and the old one stops working. Full rules on
Plans & Limits.
Getting a key
Sign in at unmap.dev/signin with a magic-link email or GitHub, then create a key from the dashboard. The dashboard can show or copy a key again whenever you need it. That sign-in flow is a browser session (a cookie, not an API key). It is how you manage your account, not how your application authenticates. Your application always uses the key itself, presented one of the four ways above.
What a bad key looks like
A missing or unrecognised key gets a 401:
// no key presented
{ "error": "missing API key", "code": "unauthorized" }
// key does not exist, or has been deleted
{ "error": "invalid or disabled API key", "code": "unauthorized" }Keys are looked up by a hash of the presented value, not stored in plaintext, so there is nothing to leak from key storage itself beyond the hash.
Going over the rate limit (1,000 requests per 60 seconds per key, per Cloudflare location) gets a
429 with a Retry-After header:
{ "error": "rate limit exceeded", "code": "rate_limited" }See Errors for the full error contract, including the plan-enforcement codes
(quota_exceeded, spend_cap, billing_required, origin_not_allowed) that a request can hit
once it is past authentication.