Site search: one box for everything a site publishes
A site's search covers every space the site publishes — docs and API reference pages, blog posts, forum topics, changelog releases, roadmap ideas, legal documents, status incidents and events (when an events space opts in) — in one ranked list. Use it from the site's own search box, or build your own UI on it: every hit says what it is, where it lives on your domain, and how to get there.
GET /public/orgs/{orgId}/sites/{slug}/search?q=payouts
GET /public/orgs/{orgId}/sites/{slug}/search/suggest?q=pay
POST /public/orgs/{orgId}/sites/{slug}/search/answer { "q": "How are payouts paid?" }
POST /public/orgs/{orgId}/sites/{slug}/search-events { "query": "payouts", "hits": 3 }All four are anonymous, so the first is a public search endpoint for your docs that an agent can call without a key; /llms-quickstart.txt points at it (see robots.txt and llms.txt). A signed-in reader's session (or a token) widens the search to the private member spaces their role may read — never because a space is in the site — and that answer is then private, no-store.
The answer
{
"hits": [
{
"id": "8c1…",
"kind": "page",
"title": "Payouts",
"snippet": "How referral <b>payouts</b> are paid each month.",
"url": "https://acme.com/docs/payouts",
"breadcrumb": [
{ "label": "Acme", "url": "https://acme.com" },
{ "label": "Docs", "url": "https://acme.com/docs" },
{ "label": "Payouts", "url": "https://acme.com/docs/payouts" }
],
"spaceId": "…", "spaceSlug": "docs", "spaceName": "Docs", "spaceType": "DOCS",
"slug": "payouts", "pageId": "8c1…", "collectionId": "…",
"rank": 0.42, "updatedAt": "2026-10-20T09:12:00.000Z"
}
],
"facets": { "page": 1, "reference": 0, "post": 0, "topic": 0, "release": 0, "idea": 0, "legal": 0, "incident": 0, "event": 0 },
"total": 1,
"offset": 0,
"minQueryLength": 3
}kind—page,reference(a page generated from an OpenAPI document by an import, a resync or the generator, or given the reference shape),post,topic,release,idea,legal,incident,event.url— absolute. The space's own custom domain when it has one, else the site's domain plus the space's segment, else our reader path. Every URL is an address that answers 200.snippet— HTML-escaped text; the only tag is<b>around a match.breadcrumb— the site, the space, the collection a page sits in (when it is not the space's only one), the hit. The site's crumb is on the same address as the hits: the site's own domain, else the custom domain its spaces answer on, and our/s/path only when none of them has one — a white-label site never gets a forumulate.com link in its breadcrumb.facets— matches per kind, for filter chips.corrected— present when the query found nothing as typed and a corrected one was run (below).
Filters and paging
Parameter | Meaning |
|---|---|
| Only these kinds. An unknown kind is a 422 listing the legal ones. |
| Only these member spaces, by slug or id. A space the site does not search is a 422 naming the ones it does. |
| 20 by default, 50 at most; |
| Prefer that translation of each page. |
| Add a grounded answer (below). |
A term shorter than the organization's search.min_search_term_length answers no hits and states minQueryLength, rather than an error under a reader's cursor.
Typos
When a query finds nothing as typed, each word of four letters or more is corrected by one edit (a swapped pair counts as one) against the words the site actually uses — its titles, topic and category names — and the corrected query is run. payuot finds Payouts; cat is never turned into car. The answer says what changed: "corrected": { "query": "payouts", "from": ["payuot"], "to": ["payouts"] }.
Off with the org setting search.typo_tolerance, or per site with config.searchSettings.typoTolerance: false (PATCH /orgs/{orgId}/sites/{siteId}).
Caching
An anonymous answer is Cache-Control: public, s-maxage=60, so a CDN in front of your custom UI keeps it for a minute. Change it with search.site_cache_seconds (org) or config.searchSettings.cacheSeconds (site); 0 sends private, no-store. An answer shaped by a reader's credential, and any answer carrying a grounded answer, is always private, no-store.
Suggestions
GET …/search/suggest?q=pay answers the best few titles — { id, kind, title, url, spaceName } — from the same spaces, filters and typo tolerance, for a type-ahead list. Up to 10 (limit, default 8).
Recording searches: search-events
The search GET records nothing. A search box reports a settled search with the beacon, once the reader's consent allows analytics:
POST /public/orgs/{orgId}/sites/{slug}/search-events
{ "query": "payouts", "hits": 3, "clickedUrl": "https://acme.com/docs/payouts", "spaceId": "…" }It lands in the space analytics and the knowledge-gap report ("searches with no results") as a search event with source: "site", counted in the clicked hit's space (else the site's first member). 204, nothing returned. So a reader who refused measurement is never in the report.
A grounded answer
Off by default. Turn it on with the org setting search.site_answer (or a site's config.searchSettings.answer):
byok— your organization's own AI key (PUT /orgs/{orgId}/ai/provider). There is no platform key; without yours the answer saysunavailable.provider— the answer service you connect below.
?answer=true on the search, or POST …/search/answer { q, types?, spaces? }, adds:
"answer": {
"state": "answered",
"provider": "byok",
"answer": "Payouts are paid on the 1st of each month…",
"citations": [{ "id": "8c1…", "kind": "page", "title": "Payouts", "url": "https://acme.com/docs/payouts" }]
}state is answered, unanswered, off, unavailable (nothing connected, with detail) or error (the provider failed, with detail). The answer is grounded on the hits this reader's search returned and nothing else, and a citation is kept only when it names one of them.
Bring your own answer service
Any service that speaks one small contract can answer — Chatly is a preset; a RAG service of your own, or any vendor behind a small adapter, is custom.
PUT /orgs/{orgId}/search/answer-provider
{ "preset": "custom", "url": "https://answers.example.com/forumulate", "secret": "…" }
GET /orgs/{orgId}/search/answer-provider
POST /orgs/{orgId}/search/answer-provider/test
DELETE /orgs/{orgId}/search/answer-providerWe POST to your URL:
{
"version": 1,
"question": "How are payouts paid?",
"locale": "en",
"site": { "id": "…", "slug": "hub", "name": "Acme", "orgId": "…" },
"sources": [
{ "ref": "s1", "kind": "page", "title": "Payouts", "url": "https://acme.com/docs/payouts", "text": "…" }
]
}and read { "answer": "…", "answered": true, "citations": ["s1"] }.
Authentication. The
secretis sent asAuthorization: Bearer <secret>, or as<headerName>: <secret>withauth: "header".Signature. Every request carries
Forumulate-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<body>")>, the same scheme as our webhooks.Limits.
timeoutMs(1–15 s, default 8 s),maxSources(1–12, default 6). The URL must behttpsand public; a preset holds it to the vendor's domain (chatly:*.chatlychat.com).Failures never fail the search: the answer says
errorwith the reason, andGET …/answer-providershowslastErrorandlastUsedAt.The secret is write-only;
secretHintshows its last four characters. Send the hint back unchanged (or omitsecret) to keep the stored one.
Per-site settings
config.searchSettings on a site (PATCH /orgs/{orgId}/sites/{siteId}). config.search is the header search control (none | icon | pill), the same key as a space's chrome.search; an object sent there is read as these settings and moved to searchSettings, with a note on X-Field-Warnings.
Key | Unset falls back to |
|---|---|
|
|
|
|
|
|
| every kind |
| none — a listed member space stays published and in the nav, but out of search |
In the API
Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go SDKs are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP agent server — its api_* family tools, or call_api. Where a CLI command or a hand-made MCP tool exists too, the last column names it. Operation names link to the API reference.
Answer Provider
Operation | Request | What it does | Shortcuts |
|---|---|---|---|
| The connected answer provider |
| |
| Connect an answer provider for site search |
| |
| Change part of the answer provider (the secret is kept unless sent) |
| |
| Disconnect the answer provider |
| |
| Send the answer provider one sample question |
|
Site Search
Operation | Request | What it does | Shortcuts |
|---|---|---|---|
| Type-ahead suggestions for a published site |
| |
| A grounded answer from a published site |
| |
| Record a settled site search (consent-gated beacon) |
|