Guides
Security
API keys, public IDs, allowed origins, bots and rate limits, signed keys for logged-in visitors, and how to limit what browser search can read.
API keys
API keys are secrets for your servers. Create them on the API keys page, which only workspace owners and admins can see:
- The
writescope changes records, settings, and browser search restrictions. - The
searchscope searches, for server-rendered pages and backends. - The
eventsscope sends events, such as orders, and nothing else. Write keys can send them too. - The
analyticsscope reads an index's analytics: its searches, queries, events and product counts. It also reads its updates: what came in from each source, and what was refused. No other scope does, since queries are what shoppers typed. - A key can be limited to one index.
Give each service a key with only the scopes and index it needs, and keep keys in your server's environment, never in browser code or a repository. If a key leaks, delete it on the API keys page and create another.
Public IDs and allowed origins
Browser search uses an index's public ID instead of a key. It is visible in your page's source, so it can only search, only its own index, and only from the origins on the index's Public access page.
- Add each origin exactly, with its scheme and any port:
https://shop.example.com,https://www.example.com,http://localhost:5173. An index has up to 20. From a trusted address, local pages need no listing: see local development. - Browser search is off until an owner or admin saves at least one origin.
- A search without an
Originheader is refused, with one exception: a crawler Cloudflare verifies, rendering your pages, may leave it out (Meta's ad crawler does), so the page in itsRefererstands for the site and must be one of your origins. - Each visitor can make 240 searches a minute on an index by default, and an index takes up to 6,000 a minute; see Bots and rate limits.
- If a public ID is misused, rotate it on the Public access page. Searches with the old ID stop within a minute, so put the new one in your storefront right away.
Bots and rate limits
Every browser search is sorted into one kind of traffic, and each index decides what to do with each kind under Limits and bots on its Public access page.
| Traffic | How it's recognized | By default |
|---|---|---|
| Visitors | Browsers | 240 searches a minute each |
| Verified crawlers | Search engines and AI crawlers that Cloudflare confirms are who they say, such as Googlebot, Bingbot and GPTBot | Cached results first, then up to 120 others a minute, and free |
| Other crawlers | Say they're bots, unverified | Cached results first, then up to 120 others a minute |
| AI crawlers and assistants | Bots that gather data for AI models, AI search engines, and AI assistants fetching for a person, such as GPTBot, ClaudeBot and PerplexityBot: Cloudflare's AI categories, or a user agent that says so | As the crawlers or visitors they otherwise are |
| Scripts and automation | HTTP libraries, headless browsers, and requests without the headers every current browser sends | 30 searches a minute per address, results up to 1,000 deep |
| Data-center networks | Addresses at cloud hosts such as AWS and Google Cloud, whatever kind of traffic they are. Verified crawlers are exempt, since crawlers run there. | 60 searches a minute per address, results up to 1,000 deep |
Each setting has fixed choices:
- Searches a minute per visitor: 60, 120, 240, 600 or 1,200.
- Searches a minute for the index: 1,500, 6,000 or 15,000, from all visitors together.
- Crawlers: allowed, or blocked.
- Heavy visitors: limited, blocked, or left alone. A heavy visitor takes more than 15,000 results from the index in a day, or 4,000 from a script, unverified crawler or data-center network, far more than shoppers do. Clusters return no records, so they don't add to it. Limited, the default, a browser is held to 30 searches a minute and results up to 1,000 deep, and a script, crawler or data-center network is refused, until the day ends (UTC). Visitors are checked every minute, so one gets a minute or two past the line first. Verified crawlers are never heavy visitors. An owner or admin can unblock a visitor for the rest of the day under Analytics → Traffic. To keep your own tools and office off the list, add their addresses to trusted addresses, or choose "Left alone" for an index they search a lot.
- AI crawlers and assistants: allowed, or blocked. Blocked, they're refused while search engines' crawlers keep the crawler rule, so your pages stay in search results.
- Scripts and automation, and Data-center networks: limited, blocked, or the same as visitors. Your own tests count as scripts, so choose "Same as visitors" on a test index if they run into the limit.
A search over a limit answers 429 with a Retry-After header, and blocked traffic 403, as does a limited script's or data-center network's search past result 1,000. These answers also carry a notice saying the index's owner doesn't allow automated collection of its data: AI coding agents asked to collect a site's data otherwise take a refusal as something to work around. Visitors' browser searches go 5,000 deep, and API keys' further; a script that needs more should search from your server with a key. Neither counts toward your plan. Visitors are counted by their address, and by the /64 block for IPv6. Limits are counted at each Cloudflare location and are approximate, and floods from one address are stopped before they reach your index.
Crawlers are served cached results first, so a big crawl costs little. Searches from verified crawlers are free, even when the cache can't answer them, and they keep working when a workspace's searches are paused. If a crawler runs into its limit, it still gets any result the cache holds for that search.
API keys have a limit of their own, in case a key leaks: 600, 3,000 (the default) or 12,000 searches a minute, set on the API keys page.
Server-rendered search pages search with an API key, so the visitors loading them are your server's. Pass each visitor with the server SDK's visitor option, and the index's settings apply to them too, verified crawlers included.
Analytics → Traffic shows each kind of traffic, what counted toward your plan, what was free, and what was refused and why. Crawlers' searches are counted only there: the other analytics, the weekly report and the analytics API show what people searched for, from 16 October 2026. If a bot gets past all of this, write to us and we'll look at it.
Trusted addresses
Trusted addresses are your own, such as your office or your servers, set once for the whole workspace under Settings → Trusted addresses. Each is an address, such as 203.0.113.7, or a range, such as 198.51.100.0/24: at most a /16 of IPv4 or a /32 of IPv6, and 20 in all. Trust my address adds the one you're using, and for IPv6 its /64.
Browser searches from them skip the per-visitor rules on every index: the heavy-visitor list, the visitor limit, the rules for scripts and data-center networks, and the 1,000-deep limit. The crawler rules still apply, they count toward your plan and each index's limit, and each index's public settings still decide what they can read. Everyone on a trusted address is trusted, so add only addresses your own people use, not a shared or mobile network's. Changes apply within a minute.
Local development
From a trusted address, pages on your own machine or network search every index in the workspace without being added to its allowed origins, so you can build your site against its live indexes: localhost and names under it (shop.localhost), 127.0.0.1 and [::1], and private network addresses such as 192.168.1.20, for a phone on the same Wi-Fi, on any port, over http or https. Their searches, suggestions, lists and events work and count like any others. From any other address, local pages are refused unless the index lists them.
What browser search can read
By default, browser search returns every field of the records you upload, and can search, filter, facet, and sort by everything the index's settings allow. Anyone can read what it returns from your pages' searches, so limit it to what your pages show, and leave things like cost prices, supplier details, and internal notes out of your records.
Under What browsers can see on the Public access page, Only some fields ticks the fields to return. The fields your pages asked for in the last week come ticked, or without those, what a page of the index's kind usually shows. Fields that look like they're for you, such as stock counts, costs, ranking signals and internal IDs, are marked. Searches that named no fields get every field today, and are counted there, so you can check those pages before saving.
From your server, a policy of returnFields alone does the same:
await products.setPublicSearchPolicy({
returnFields: ["objectID", "title", "brand.name", "price", "image", "url", "variants"],
})Browser searches then get only those fields, and a parent such as variants returns every field inside it. A field a page asks for in attributesToRetrieve that the policy doesn't return is left out of the results, and the rest still come back. Highlights and "did you mean" come only from returned fields. Searching, filters, facets and sorts follow the index's settings.
To limit those too, list them as well. Each list you give limits its part of a search, and requests for others are refused:
await products.setPublicSearchPolicy({
returnFields: ["objectID", "title", "brand.name", "price", "image", "url"],
searchFields: ["title", "brand.name"],
filterFields: ["brand.name", "price", "inStock"],
facetFields: ["brand.name"],
sortIds: ["relevance", "price-asc"],
defaultSort: "relevance",
location: true,
})defaultSort comes with sortIds. With any policy, location search needs location: true, because distances, map boxes and map clusters show roughly where each record is, even when its location field isn't returned. A cluster's stats only cover fields in filterFields. usePublicIndexDefaults() removes the policy, and getPublicSearchAccess() shows the public ID, origins, and current policy.
Without a policy, attributesToRetrieve only asks for fewer fields; it doesn't keep the others private.
Logged-in visitors
For people who log in, such as subscribers, trade customers or staff, your server can sign a key for each of them, and their browser searches with it. A signed key says what that person may search, and replaces the index's public settings for their searches:
- Filters forced onto every search, such as the customer group whose prices they see. They hold for everything the search returns, facet counts and stats included, and the page's own filters apply as well.
- Fields: the only ones their results return.
- Who they are (
userToken): rate limits and heavy visitors count the person rather than their address, so people sharing one, such as a mobile carrier's or an office's, don't share a limit. - Indexes, sites and addresses it works from.
- When it expires: 7 days ahead at most. An hour suits most sites.
The key decides instead of the index's allowed origins, so an index searched only with signed keys needs none, and its public ID searches nothing for anyone else. The index's bot rules still apply, and signed searches count toward your plan as other searches do.
Create a signing key under Search for logged-in visitors on the API keys page, for every index or chosen ones. It's shown once: keep it in your server's environment, as FINDLANE_SIGNING_KEY. A signing key only signs keys. It can't search.
Then, in a route that checks who's logged in, sign a key with the server SDK:
import { signSearchKey } from "@findlane/server"
export async function GET(request: Request) {
const user = await currentUser(request)
if (!user) return new Response("Not logged in", { status: 401 })
const signedKey = await signSearchKey(process.env.FINDLANE_SIGNING_KEY!, {
validUntil: new Date(Date.now() + 60 * 60 * 1000),
indexes: ["products"],
filters: { customerGroup: user.group },
returnFields: ["objectID", "title", "price", "image", "url"],
userToken: user.id,
})
return new Response(signedKey)
}And give the browser SDK a function that fetches it:
const client = new SearchBrowserClient({
publicId: "pub_your_public_id",
signedKey: () => fetch("/api/search-key").then((response) => response.text()),
})The client asks for a key before its first search, again shortly before the key expires, and once more if a search is refused because it has. With React, pass signedKey to FindlaneProvider. Make a new client when the person changes.
Searches and their did-you-mean suggestions take signed keys. Query suggestions, lists, recommendations, subindexes and multi-index searches don't yet, so those still need the index's allowed origins.
A signed key that's refused answers 401, with expired: true when its time is up, and otherwise an error that says why. Revoking a signing key on the API keys page stops every key it signed within a minute. On the Traffic tab, signed keys' searches are their own row, Signed-in visitors, and refused keys a reason of their own.
If your site searches through the Algolia-compatible API, the secured keys your server makes there work with a signing key too: see secured API keys.
Servers in other languages can sign keys too. A signed key is base64(hex(HMAC-SHA256(signingKey, params)) + params), where params are its restrictions as a URL query string with the keys sorted: validUntil (Unix seconds), and any of restrictIndices, filters (the filters as JSON), returnFields, userToken, restrictSources (IP addresses or ranges) and referers (sites, with * for any part), lists joined with commas. A restriction Findlane doesn't know refuses the key rather than being ignored. returnFields of * alone returns every field.
Workspace members
A workspace's people each have a role, under Settings → Members:
- Members see indexes and their settings, try searches, and read analytics. They change nothing.
- Admins also change everything about search: indexes, records, settings, API keys, allowed origins, trusted addresses, feeds, merchandising, alerts and shared reports. They invite people as members or admins, change members' and admins' roles, and remove them.
- Owners also manage billing, the plan, the spending limit and the workspace's name, and make someone an owner or change an owner's role.
People are invited as members or admins; to make someone an owner, change their role once they've joined. Nobody changes their own role or removes themselves, so a workspace always keeps an owner.
AI agents
Agents connected through the MCP server sign in as a person and manage the workspaces that person chose, with their role in each. Owners and admins see every app connected to their workspace under Connected apps on the API keys page, and can disconnect any of them from it. Members can disconnect their own.