Skip to content

Authentication

Every gateway request (maps, geocoding, routing) needs an unmap API key. There is no unauthenticated tier: even the free plan works by key, it just never gets billed for dev traffic (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 public um_test_ demo key that is rate-limited and safe to embed for trying the API before you have your own.

Both forms are checked the same way at the gateway: authenticated, rate-limited, and (outside of dev-origin traffic) metered against your plan. The prefix identifies the key's environment; it does not by itself exempt a request from billing. Whether a request counts as free "dev traffic" depends on the request's Origin. See Plans & Limits for the exact rule.

Presenting the key

Three forms are accepted:

  • Authorization: Bearer <key> is the normal form for server-side calls.
  • X-API-Key: <key> is for clients that reserve the Authorization header for something else.
  • ?key=<key> is a query parameter. Required for tile, glyph, and sprite requests, since MapLibre GL cannot attach custom headers to the requests it fires for those. The @unmap/* clients use this form for every request, tiles and JSON alike, so with the SDK you never present the key yourself; the header forms are for calling the gateway directly.
# 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"
 
# 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

If more than one form is present, the header wins: Authorization is checked first, then X-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 403 and the code origin_not_allowed, and so are requests with no Origin or Referer at all. A key with an empty list is unrestricted, which is what a server key should be. Dev origins are not implicit: list http://localhost:3000 if that is where you develop.
  • The spend cap, which starts at $0 on 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, revoke it from the dashboard and create another. 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. 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 three 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 revoked
{ "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.