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.

XSSCSPCORS SSRFPrototype pollution Cache deceptionRequest smuggling Auth / JWT

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.

Base URL
https://api.preview.is
Auth header
X-API-Key: rk_…
curl
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.

header
X-API-Key: rk_your_key

A missing or invalid key returns 401. Keep keys server side, never ship one in front-end code.


Response

Each result groups all matched sections of an article, best first, with full section text and the source URL.

200 OK · application/json
{
  "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

Burst
4 / min
Per account. Regenerating a key does not reset it. Exceeding it returns 429.
Free quota
200 / wk
Also capped at 1,000 per month. Per account, counted on /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.

StatusMeaning
200Success.
401Missing or invalid X-API-Key.
422Invalid request body, for example k out of range.
429Burst rate exceeded, or the weekly/monthly free quota is exhausted.
502Upstream search failure. Retry shortly.

Examples

python
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"])
javascript
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);
Get a key