Browse documentation
Search the index — GET /search
Search the agent-first web index. Returns metadata-only hits (url, host, snippet, score, trust and graph signals) — never the page body; fetch that with get_document when a snippet is not enough.
/search Request
curl \
-H "x-api-key: $UNLOB_API_KEY" \
"https://api.unlob.com/search?q=<q>" Behaviour
The query supports boolean operators: AND, OR, -term to exclude, and "exact phrase". Name a vertical to disambiguate, or omit it and the router picks one from the query.
The index is admission-controlled, so a small result set usually means the index holds little on the topic, not that the query was wrong. One exception: if `partial` comes back true, part of the corpus was unreachable and the answer really is incomplete.
Costs 1 credit.
Parameters
These are the search filter set. IN-list parameters take a comma-separated string here, because a URL query string cannot carry an array; the same filters reach the MCP tool as arguments that also accept a JSON array. Filters and sorting groups them by what they narrow, separates the two that only bias the ranking from the ones that cut results, and works through combining them.
| Name | Type | Description |
|---|---|---|
authority | string | Publisher-authority IN-list |
collapse | "none" | "host" | "page" | "story" | Group results: one per host, per page, or per story (near-dup cluster). collapse=story is how you stop reading the same wire copy five times. |
community | string | Graph signal: restrict to these graph communities |
content_type | string | IN-list: article, news, docs, reference, qa, forum, academic, product, code, how-to, opinion, video, document |
exclude_site | string | Hosts to exclude |
facets | boolean | Also return host/vertical/source/content-type facet counts and a publish-year histogram |
fields | string | Project the hit down to these fields, to save context |
from | integer | Only results fetched at/after this unix-second timestamp |
lang | string | Single language code, e.g. en |
langs | string | Language IN-list |
limit | integer | 0 returns only the match count; a value above the maximum is clamped |
max_words | integer | Passage length ceiling |
min_centrality | number | Graph signal: only passages this central to the corpus |
min_host_rank | number | Trust floor (0..1) |
min_independent_sources | integer | Graph signal: only stories asserted by at least this many distinct hosts. The corroboration filter. |
min_quality | integer | Quality floor |
min_words | integer | Passage length floor |
mode | "keyword" | "semantic" | "hybrid" | |
prefer_authority | boolean | Soft rerank bias toward higher-authority hosts |
prefer_recent | boolean | Soft rerank bias toward newer passages — a nudge, not a filter |
published_from | integer | Content publish date at/after this unix-second timestamp (not crawl time) |
published_to | integer | Content publish date at/before this |
qrequired | string | Boolean-capable: rust AND async, -python, "exact phrase" |
safe | boolean | true (the default) drops explicit content; false allows everything |
site | string | Restrict to an exact host, e.g. docs.rs |
sort | "relevance" | "recency" | "host_rank" | "quality" | "published" | "words" | "centrality" | |
source | "cc" | "delta" | cc = Common Crawl, delta = freshly crawled |
term | string | Exact-needle: only passages carrying this salient term, e.g. CVE-2024-3094. Use it for identifiers an embedder is blind to. |
tld | string | Top-level-domain IN-list, e.g. edu, gov |
to | integer | Only results fetched at/before this unix-second timestamp |
topic | string | Topic-tag IN-list |
vertical | string | e.g. code, cooking, science. Omit to auto-route. |
Responses
| Status | Meaning |
|---|---|
200 | Ranked passages. Check `partial` before treating a short answer as complete. |
401 | Missing, unknown or revoked key. A key minted moments ago can answer 401 until it propagates to the serving fleet. |
429 | Over the per-minute rate (spent in credits), or a hard-capped key without the credits this call costs. `retry-after` is present on the rate-limit case only. |
Response body
WebSearchResponse
| Name | Type | Description |
|---|---|---|
facets | object | Present only when facets=true |
moderequired | "keyword" | "semantic" | "hybrid" | The mode actually used |
partial | boolean | true when at least one shard failed or timed out — the answer is short because a slice of the corpus was unreachable, NOT because that is all there is. Never treat a partial result as complete. |
resultsrequired | object[] | — |
routedrequired | boolean | true when the router chose the vertical, false when you named it |
totalrequired | integer | — |
vertical | string | null | The vertical actually queried |
The same call over MCP
This capability is web_search on the
MCP server, taking the same parameters and returning the same body. A parity test
in the API fails the build if the two ever diverge, so you can read either
reference and use the other.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "web_search",
"arguments": {
"query": "<q>"
}
}
} Most clients build that frame for you — see Connect a client.
See Errors for what to do with each status, and Retries and backoff for which are worth repeating.
FAQ
- What does 200 mean on GET /search?
- Ranked passages. Check `partial` before treating a short answer as complete.
- What does 401 mean on GET /search?
- Missing, unknown or revoked key. A key minted moments ago can answer 401 until it propagates to the serving fleet.
- What does 429 mean on GET /search?
- Over the per-minute rate (spent in credits), or a hard-capped key without the credits this call costs. `retry-after` is present on the rate-limit case only.