Documentation
A hybrid search API over a curated corpus of real web-security write-ups. Exact keyword matching and semantic embeddings, fused and ranked by relevance, so a natural-language query returns the most relevant article sections with their source URLs. Grounded, cited, fast.
Quickstart
Sign in with Google, generate one API key from the dashboard, then call
POST /search. The free tier includes 200 calls per week and 1,000 per month.
curl -X POST "https://api.preview.is/search" \
-H "Content-Type: application/json" \
-H "X-API-Key: rk_your_key" \
-d '{"query": "how to prevent stored XSS", "k": 5, "min_score": 0.1}'
Authentication
Every /search and /article
request needs your secret key in the X-API-Key header. Keys look like
rk_ followed by 40 hex characters, one active key per account.
Regenerating from the dashboard revokes the previous one.
X-API-Key: rk_your_key
A missing or invalid key returns 401. Keep keys server
side, never ship one in front-end code.
POST/search
Run a hybrid search. The query is matched against article sections, then results are grouped to the parent article so you get enough context to reason, not a blurb.
| Field | Type | Default | Description |
|---|---|---|---|
| query | string | required | Natural-language query. Exact tokens like CVE ids, headers, and tags work well too. |
| k | int | 8 | Requested parent articles to return. Standard accounts are capped at 5 results per request. |
| candidates | int (1-300) | 80 | Chunks retrieved before relevance ranking. |
| min_score | float (0-1) | 0.0 | Drop results below this relevance score. Real matches score ~0.95+, try 0.1 to reject off-topic noise. |
| full_content | bool | false | Request each article's full markdown body in content where enabled. |
Response
Each result groups all matched sections of an article, best first, with full section text and the source URL.
{
"query": "how to prevent stored XSS",
"count": 5,
"results": [
{
"rank": 1,
"score": 0.9876,
"title": "Understanding Stored XSS: Risks and Prevention",
"url": "https://www.legitsecurity.com/...",
"file": "002999-understanding-stored-xss.md",
"matched_sections": [
{ "heading": "How to Prevent Stored XSS", "score": 0.9876, "text": "..." },
{ "heading": "Stored XSS Attack Example", "score": 0.91, "text": "..." }
],
"content": null
}
]
}
GET/health
No auth. Returns liveness and the indexed chunk count.
Rate limits
429./search. Regenerating API keys does not reset usage.Every response carries
X-RateLimit-Limit and X-RateLimit-Remaining, plus weekly and monthly variants.
Errors
Errors return { "detail": "..." } with the matching status.
| Status | Meaning |
|---|---|
| 200 | Success. |
| 401 | Missing or invalid X-API-Key. |
| 422 | Invalid request body, for example k out of range. |
| 429 | Burst rate exceeded, or the weekly/monthly free quota is exhausted. |
| 502 | Upstream search failure. Retry shortly. |
Examples
import requests
r = requests.post(
"https://api.preview.is/search",
headers={"X-API-Key": "rk_your_key"},
json={"query": "chaining file upload bypass to admin takeover", "k": 5, "min_score": 0.1},
)
r.raise_for_status()
for hit in r.json()["results"]:
print(hit["rank"], round(hit["score"], 3), hit["title"], hit["url"])
const res = await fetch("https://api.preview.is/search", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "rk_your_key" },
body: JSON.stringify({ query: "DOM clobbering in Firefox", k: 5, min_score: 0.1 }),
});
if (!res.ok) throw new Error(`search failed: ${res.status}`);
const { count, results } = await res.json();
console.log(count, "articles");
for (const hit of results) console.log(hit.rank, hit.score, hit.title);