This is the documentation of version 0.64.0. Go to the latest version, 1.0.0-rc.5.

Hardening your proxy

With a proxy in front of TomTom, the key is off the page. But the proxy has a public URL and uses your key for whoever calls it: a proxy anyone can call is your key with extra steps. The checks below fix that, and the operating habits after them keep it predictable.

Four checks, in order

A request meets them in sequence, so the cheapest rejection happens first.

POST /sessionALL /map/*, in orderRefused, nothing reaches TomTomVerify challenge token, check reported hostnameSet short-lived session cookie1. Origin allowlist2. Session cookie3. Rate limit4. Inject key and forward successclaims one of your pagescookie is validwithin budget the cookie the browser then sends 403403401429

1. Origin allowlist

Require that a request claims one of your pages. Cross-origin requests carry Origin. Same-origin GETs do not, so when the proxy shares your app's hostname you fall back to Referer.

Refuse requests that claim neither. That refusal is what makes this more than decoration, because it turns away the simplest scripted attempt.

Be clear about the ceiling. Both headers are set by browsers and easily set by anything else. This stops casual curl and other people's pages hotlinking your tiles. It does not stop someone who reads your JavaScript.

For local development, allow loopback origins on any port. A dev server that hops to the next free port will otherwise have you editing the allowlist constantly, and an allowlist you edit constantly is one that gets switched off.

2. A challenge gate

This is the check that a set header cannot pass. Have a POST /session endpoint that verifies an invisible challenge (hCaptcha, Cloudflare Turnstile, or equivalent) and, on success, sets a short-lived cookie. Every data-plane route then refuses requests without one.

Verification happens server-side: your endpoint posts the token and your secret to the provider's verify endpoint, and reads the result. The result carries one field that is easy to overlook.

Alongside success, the verifier reports the hostname where the challenge was actually solved. That is the one claim in the exchange the client does not control, and checking it is what stops a token solved on somebody else's page from working on yours, since the sitekey is public. Accept the session only when both success and the reported hostname pass, and derive the expected hostnames from the same allowlist as check 1 so the two cannot drift apart.

Two rules that are easy to get backwards:

The honest ceiling here too: a real browser driven by automation still solves the challenge, and solving services are inexpensive. What changes is the cost curve, from a shell loop to per-session CPU and a per-session budget.

3. The session cookie

The cookie is what saves you from challenging on every request. Its attributes are not incidental detail:

Attribute Why
HttpOnly JavaScript, yours or anything injected into the page, cannot read it
Secure Never travels in the clear
SameSite Strict or Lax when the proxy is same-origin with your app. Cross-site setups need SameSite=None, which then requires Secure and, in browsers enforcing cookie partitioning, Partitioned. Otherwise the cookie is silently dropped and every request looks unauthenticated
Short lifetime 15 to 30 minutes bounds how long a copied cookie is useful and how much any one challenge solve is worth
Authenticated encryption Seal the payload with AEAD (AES-256-GCM) or sign it (HMAC), so the client cannot forge or edit it
A key id in the payload Lets you rotate the sealing key: sign new cookies with the new key while the old one still opens existing cookies, then retire it

A stateless design is worth considering: make the cookie the session, an encrypted or signed blob carrying an id and an expiry, with nothing stored server-side.

It costs you the ability to say "this session has spent N of M requests" and to revoke a session instantly. It buys horizontal scaling with no shared state, and no session store to evaporate when your service scales to zero. If you later need true per-session counters, add a small cache for the counters alone. The cookie design does not have to change.

4. Rate limits

This is the layer that actually bounds your bill. Key the limit on the session when there is one and the client IP otherwise, so a single challenge solve cannot buy the whole budget.

Set the ceiling generously. One map load is tens of requests, and a busy page multiplies that. This limit exists to cap spend, not to queue users fairly.

Put it behind the origin check and the gate, so a refused origin or an unauthenticated caller can never consume a real user's allowance.

Only trust X-Forwarded-For if something you control overwrites it. On a directly reachable service it is client-supplied, so believing it lets one script present a fresh address per request and never reach any ceiling.

An in-process limit only bounds one replica. The durable limit belongs at the edge, in a WAF or CDN rule, where a rejected request costs you nothing at all.

Operating it

The proxy is infrastructure now, and its failure modes are yours.

Checklist

Everything from this page and Proxying your API calls, in one place.

Things that look like solutions

What to expect from it

A proxy keeps the key off the page and puts a ceiling on the bill. It does not make your API usage private: anyone can open your application, watch what it requests, and replay those requests inside a real session at whatever rate you allow.

That is the right trade. Each layer is inexpensive, and together they move the effort from one afternoon, once, to an ongoing cost, which is the point at which getting an API key of one's own becomes the easier path.

Related guides