Authentication
API keys and domain whitelisting.
API key
You get one key per org. It starts with emb_ and is 68 characters long. Keys can be viewed and rotated from the admin dashboard. After a rotation the old key stops working within 5 minutes, because key lookups are cached for that long. Plan the swap with that window in mind, and treat a leaked key as live until it has passed.
Pass it as a query param:
/sol/scanner/{ADDRESS}/map?embed=1&key=emb_a1b2c3d4e5f6...
/rh/scanner/{ADDRESS}/map?embed=1&key=emb_a1b2c3d4e5f6...Embed keys are publishable: they sit in your page's HTML, so treat them like a public identifier rather than a secret. Your domain list controls where the map can be framed and filters API calls on a best-effort basis. Your usage limits are what bound spend on a key.
Domain whitelisting
Both chains use the same key and domain settings.
No domains configured (public mode). If your org has no allowed domains, the key works on any site: the iframe can be framed anywhere and API calls are accepted from any origin. This is the default for a new org until you ask us to add domains.
Domains configured. Once at least one domain is set:
- The iframe response sets a
Content-Security-Policy: frame-ancestorsrule for your domains, and the browser blocks embedding on other sites. - API calls from inside the iframe originate from Scanner itself, so the backend trusts Scanner's frontend origin while separately validating the embed key and quota.
- Direct API calls from other origins are checked against the allowed-domain list and receive
403 EMBED_DOMAIN_REJECTEDif rejected. - Requests that carry the key but send neither an
Originnor aRefererheader (for examplecurlor a server-side fetch) are rejected with403 EMBED_DOMAIN_REJECTED. Browsers always sendOriginon the iframe's own API calls, so real embeds are unaffected.
Server-to-server calls
Embed keys are for browsers. If you need Scanner data from your backend, use an sk_live_ API key instead; see API keys. Header checks are a filter, not authentication: a script can send any Origin it likes, which is why quotas and rate limits apply to every key.
Matching rules:
| You register | Works on | Doesn't work on |
|---|---|---|
app.example.com | app.example.com | example.com, other.com |
example.com | example.com, sub.example.com | other.com |
localhost | http://localhost:3000, http://localhost:5173 (any port) | Remote origins |
localhost:5173 | http://localhost:5173 only | Other ports |
If you add example.com, all subdomains work too. To add or change domains, contact us.
Local development
For dev environments, register either localhost (matches any port) or the specific localhost:PORT you run your integrator on. Both schemes http:// and https:// are accepted by the iframe's frame-ancestors CSP.
Validate a key
Check if a key is valid without triggering a scan:
GET https://api.scanner.net/embeds/validate?key={API_KEY}No auth needed. Returns:
{ "valid": true, "domains": ["app.example.com", "staging.example.com"] }Or if invalid:
{ "valid": false, "domains": [] }Use this before mounting the iframe to fail fast when a key is rotated, disabled, or typo'd: the iframe itself cannot report invalid-key state to the host page (cross-origin), so a pre-flight check is the cleanest way to surface it.
Errors
| Status | Code | Meaning |
|---|---|---|
| 403 | EMBED_KEY_INVALID | Bad key or key not found |
| 403 | EMBED_ORG_DISABLED | Org disabled by admin |
| 403 | EMBED_DOMAIN_REJECTED | The request's Origin (or Referer) is not on your domain list, or your org has domains set and the request sent neither header. Check the page's domain against your list; for server-to-server calls, use an sk_live_ API key |