Origin allow-list
Restrict which web origins may use a browser-facing API key.
Origin allow-list
When you ship an API key in browser code, you must assume that anyone can read it. The origin allow-list is the layer that makes that acceptable: it tells AACsearch to reject any request whose Origin header is not in the list you configured for that key.
This is not CORS. CORS controls whether browsers allow a response to be read. The origin allow-list is a server-side check that rejects the request before the search even runs, regardless of whether the caller is a browser at all.
The browser security model
Browser-side search has three layers of defense, used together:
- Origin allow-list — only your domains can use the key.
- Scope
searchonly — the key cannot mutate data, even from your own domain. - Tight rate limit per minute — caps abuse from any single client.
For per-user filtering, add a fourth layer: Scoped tokens.
Configuring the allow-list
From the dashboard:
- Project → API keys → Create key (or edit an existing one).
- In Allowed origins, enter one origin per line. An origin is
scheme://host[:port], with no path, no trailing slash, no wildcards in path segments. - Save.
Examples:
https://shop.example.com
https://www.example.com
https://staging.example.com:3000For local development, add http://localhost:3000 (or whatever port you use). Remove it from the production key once you're shipping.
How matching works
On every public search request, the server reads the Origin request header and checks for an exact, case-sensitive match against the list. If the list is non-empty and the header is missing or unmatched, the request fails with HTTP 403 origin_not_allowed.
If the list is empty, no origin check runs. This is the right default for a server-side key (no browser ever calls it). It is the wrong default for a browser-side key.
Browser sends: Server checks:
Origin: https://shop.example.com → allowedOrigins.includes("https://shop.example.com") ?
yes → continue
no → 403 origin_not_allowedThe check is O(n) over a small list. There is no startsWith, no wildcard, no regex. If you need to support *.example.com, list each subdomain.
When to use it
| Where the key is used | Allow-list? |
|---|---|
| Browser (search widget, your own JS) | Required. Without it, the key is unrestricted. |
| Mobile app (native HTTP) | Optional — the app does not send a browser Origin. |
| Server-side worker | Not needed. Use a server-side ingest/admin key. |
| CMS connector (PrestaShop, Bitrix) | Not needed. Connector keys (ss_connector_*) are server-side. |
What it does not protect against
- A stolen key. If a request comes from a server-side attacker, they will send no
Originheader at all — and your check rejects them. But if the attacker scripts a real browser on an allowed origin (XSS, malicious extension), theOriginheader is correct and the request goes through. The downstream layers (search-only scope, rate limit, scoped-token filter) are what protect against that. - Cross-origin abuse via a vulnerable extension. Extensions can sometimes inject scripts into your page. CSP and a tight scope reduce the impact.
- An attacker on your own domain. If your site has XSS, an attacker can call your API from your own origin. Fix the XSS.
Common mistakes
- Trailing slashes or paths.
https://example.com/does not equalhttps://example.com. TheOriginheader never has a path; strip them. - Forgetting staging or preview deployments. If your preview environment has a different host (e.g.
pr-123.preview.example.com), it will fail the check. Either use a separate key per environment, or list the preview hosts. - Hard-coding the same key into the mobile app. A native app sends no
Origin, so a strict allow-list rejects it. Use a separate key, or leave the list empty on the mobile key and rely on the other layers. Origin: null. Some sandboxed iframes andfile://pages sendOrigin: null.nullmatches only if you explicitly listnull— usually you shouldn't.
Example: production setup
Key name: Frontend Search (prod)
Scopes: search
Allowed origins: https://example.com
https://www.example.com
Rate limit: 600 / minute / key
Indexes: products, articlesThis key, if leaked from a browser bundle, can only be replayed from example.com or www.example.com. It cannot write data, and bursts above 600 requests per minute are rejected before they reach Typesense.
See also
- API keys — scopes and rotation
- Scoped tokens — for per-user filtering
- Best practices — broader hardening checklist