Overview
Purili exposes the same core index used by its search interface through standard HTTP endpoints. Search returns ranked result metadata; Context returns clean text already stored for an indexed URL. Together they form a simple two-step retrieval workflow without an SDK.
Authentication
None
Response format
JSON / UTF-8
Core source
Purili index
Public endpoints: /api/search and /api/context accept cross-origin requests and support both GET and POST.
Quickstart
A complete integration can begin with one GET request. URL-encode the query and parse the JSON response.
curl -sS "https://puri.li/api/search?q=privacy+search+engine&page=1"const params = new URLSearchParams({ q: "privacy search engine", page: "1" })
const response = await fetch("https://puri.li/api/search?" + params)
if (!response.ok) throw new Error("Search request failed")
const data = await response.json()
for (const result of data.results) {
console.log(result.title, result.url)
}import requests
response = requests.get(
"https://puri.li/api/search",
params={"q": "privacy search engine", "page": 1},
timeout=10,
)
response.raise_for_status()
for result in response.json()["results"]:
print(result["title"], result["url"])Try a request
Run the request to inspect the response.Authentication and CORS
No credentials are required. Do not send an authorization header or place secrets in the query string. Both public endpoints return Access-Control-Allow-Origin: * and handle browser preflight requests.
| Property | Value |
|---|---|
| Authentication | None |
| API key | Not required |
| Allowed origins | * |
| Methods | GET, POST, OPTIONS |
| Request content type | application/json for POST |
| Response content type | application/json |
GET · POST /api/search
Search API
Returns ranked organic results from Purili's crawler-built index. Search includes URL cleanup, domain diversity, safety filtering, optional query correction, and reachable pagination estimates.
GET request
GET https://puri.li/api/search?q=site%3Aeuropa.eu+digital+privacy&page=1&exact=0| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | yes | Search query. Whitespace is trimmed; an empty value returns 400. |
| page | integer | no | One-based page number. Invalid or negative values become 1. |
| exact | boolean | no | Use 1 to disable spelling correction. Defaults to false. |
POST request
curl -sS "https://puri.li/api/search" \
-H "Content-Type: application/json" \
-d '{"q":"site:europa.eu digital privacy","page":1,"exact":false}'Successful response
{
"results": [
{
"id": "crawled-0-1",
"title": "Data protection in the EU",
"url": "https://europa.eu/youreurope/citizens/consumers/internet-telecoms/data-protection-online-privacy/",
"displayUrl": "europa.eu › youreurope › citizens › consumers",
"description": "EU rules protect your personal data...",
"favicon": "/api/favicon?host=europa.eu",
"source": "crawled"
}
],
"total": 174,
"timeMs": "0.09",
"hasNext": true,
"totalPages": 18,
"page": 1,
"correction": null
}Query correction
The optional correction object uses showing when Purili already searched a normalized query, or suggestion when the client should offer a “did you mean” link.
{
"type": "showing",
"original": "yahoo financ",
"corrected": "yahoo finance"
}GET · POST /api/context
Context API
Retrieves extracted text and metadata already stored in the Purili index. Use an exact URL returned by Search. Context does not visit or scrape the live website during the request.
Indexed content is untrusted input. When passing it to a model, isolate it as source material and do not treat instructions found inside a page as application instructions.
GET request
curl -sS --get "https://puri.li/api/context" \
--data-urlencode "url=https://example.com/article"POST request
curl -sS "https://puri.li/api/context" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/article"}'| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes | A valid http(s) URL. Fragments are removed and input is limited to 2,000 characters. |
Successful response
{
"url": "https://example.com/article",
"title": "Example article",
"summary": "A concise stored summary.",
"content": "Complete clean text stored by Purili...",
"passages": ["First extracted passage...", "Second passage..."],
"structured": { "type": "Article" },
"coverage": "stored-extracted-passages",
"source": "purili-index",
"liveFetch": false
}Coverage values
| Value | Meaning |
|---|---|
| stored-extracted-passages | Extracted passages were retained by the crawler. |
| search-snippet-fallback | Only the shorter indexed snippet was available. |
| unknown | The backend did not provide a coverage label. |
NPM · ZERO RUNTIME DEPENDENCIES
JavaScript & TypeScript SDK
Use the official @purili/web-search client in Node.js, serverless functions, or modern browsers. It provides typed methods for every public JSON endpoint, request timeouts, AbortSignal support, and structured API errors.
npm install @purili/web-searchimport { purili } from "@purili/web-search"
const results = await purili.search("independent search engines")
const page = await purili.context(results.results[0].url)
console.log(page.content)AI SDK · OPENCLAW
Agent framework integrations
Use Purili as native tools in your existing agent framework. The official adapters expose search, domain search, and indexed page context without credentials.
| Integration | Install |
|---|---|
| Vercel AI SDK | npm install @purili/ai-sdk ai |
| OpenClaw | openclaw plugins install npm:@purili/openclaw |
| MCP | npx -y @purili/mcp-server |
STREAMABLE HTTP · STDIO
Purili MCP server
Connect an MCP-compatible client directly to Purili without writing an API wrapper. The hosted server and npm package expose the same six read-only tools backed by the public Search, Context, Suggest, Infocard, and crawler-statistics endpoints.
Hosted server
https://puri.li/mcpnpm installation
{
"mcpServers": {
"purili": {
"command": "npx",
"args": ["-y", "@purili/mcp-server"]
}
}
}| Tool | Purpose |
|---|---|
| web_search | Search Purili's independent web index. |
| search_domain | Search within one domain and its subdomains. |
| get_context | Retrieve stored text and metadata for an indexed result URL. |
| suggest_queries | Get cleaned autocomplete suggestions. |
| get_infocard | Retrieve a confident entity card or instant answer. |
| get_index_stats | Read public crawler and index statistics. |
No Purili account or API key is required. Every MCP tool is read-only. Both the hosted endpoint and the npm package are public.
AI and RAG workflow
For grounded model responses, search first, select a small set of relevant sources, then retrieve context for each selected URL. Preserve the original URLs alongside the text so the model can cite its evidence.
- 1Search
Send one or more concise queries to /api/search.
- 2Select
Deduplicate URLs and choose sources based on relevance and your own trust policy.
- 3Retrieve
Call /api/context for each selected result URL, preferably in parallel.
- 4Bound
Trim or chunk content to fit the model context window.
- 5Generate
Label the text as untrusted evidence and require source-linked citations.
const api = "https://puri.li"
const search = await fetch(api + "/api/search?q=" + encodeURIComponent(query))
const { results } = await search.json()
const sources = await Promise.all(
results.slice(0, 5).map(async ({ url, title }) => {
const response = await fetch(api + "/api/context?url=" + encodeURIComponent(url))
if (!response.ok) return null
const context = await response.json()
return { title, url, text: context.content, coverage: context.coverage }
})
)
const evidence = sources.filter(Boolean)Search operators
Operators can be combined with ordinary terms in the q parameter.
| Example | Behavior |
|---|---|
| site:example.com privacy | Limit results to a host and its subdomains. |
| "exact phrase" | Require a phrase in the text query. |
| privacy -tracking | Exclude documents containing a term. |
| intitle:privacy | Require a term or phrase in the page title. |
| inurl:docs | Require a term in the URL. |
| filetype:pdf climate | Filter by file extension where known. |
Response schema
Search response
| Field | Type | Description |
|---|---|---|
| results | SearchResult[] | Ranked organic results for the requested page. |
| total | number | Estimated number of reachable results after filtering and domain grouping. |
| timeMs | string | Search latency in seconds, formatted as a decimal string. |
| hasNext | boolean | Whether another result page is expected. |
| totalPages | number | Estimated reachable pagination horizon. |
| page | number | The current one-based result page. |
| correction | object | null | Optional spelling or query-normalization information. |
| queryAnalysis | object | null | Optional structured analysis produced by the index. |
SearchResult
| Field | Type | Description |
|---|---|---|
| id | string | Rendering identifier. Do not treat it as a permanent document ID. |
| title | string | Cleaned page title. |
| url | string | Canonical result URL with common tracking parameters removed. |
| displayUrl | string | Readable host and path used in search interfaces. |
| description | string | Cleaned snippet from the indexed document. |
| favicon | string? | Relative Purili favicon endpoint URL when a host can be parsed. |
| source | string | Usually `crawled` for results from the independent index. |
Context response
| Field | Type | Description |
|---|---|---|
| url | string | Normalized URL of the indexed document. |
| title | string | Stored document title. |
| summary | string | Short stored summary or search snippet. |
| content | string | Combined extracted text, bounded to 48,000 characters. |
| passages | string[] | Stored extracted passages, bounded to 64 entries. |
| structured | unknown | null | Structured information retained by the index when available. |
| coverage | string | Indicates full stored passages, snippet fallback, or unknown coverage. |
| source | "purili-index" | Confirms that content came from Purili's index. |
| liveFetch | false | Context never fetches the live URL during your request. |
Errors
Errors use an HTTP status and a small JSON object with an error string. Clients should use the status code for control flow and treat the message as diagnostic text.
| Status | When it occurs | Suggested handling |
|---|---|---|
| 400 | Missing query, invalid URL, or malformed input. | Correct the request; do not retry unchanged. |
| 404 | The Context URL is not in the index. | Use the search snippet or choose another result. |
| 5xx | The index or context service is unavailable. | Retry with exponential backoff and jitter. |
| 200 with empty results | The query is valid but no visible matches were found. | Try a broader or corrected query. |
{
"error": "URL not found in the Purili index.",
"url": "https://example.com/not-indexed"
}Additional endpoint reference
These endpoints are public today and documented individually below. Search and Context are the primary stable integration surface; UI-oriented endpoints are explicitly marked where their response contract may still evolve.
| Method | Path | Parameters | Purpose |
|---|---|---|---|
| GET | /api/suggest | q | Autocomplete phrases from Purili's index and curated local suggestions. |
| GET | /api/correct | q | Spelling and spacing correction metadata. |
| GET | /api/infocard | q | Wikipedia-derived entities and instant answers when confidently available. |
| GET | /api/images | q, page | Image results. This endpoint currently uses Wikimedia Commons and is not part of the independent web index. |
| GET | /api/news | q | Cached news items used by the Purili News interface; the response is not yet a stable public contract. |
| GET | /api/favicon | host | Normalized cached site icon. Returns image data rather than JSON. |
| GET | /api/crawler-stats | — | Public crawler and index counters used by the stats page. |
| GET · POST | /api/submit-site | url, challenge | Submit public crawl seeds after completing the lightweight challenge. |
/api/suggestAutocomplete suggestions
Returns a JSON array of cleaned query completions. Suggestions combine phrases from Purili's index with a curated local set; adult, gambling, URL-like, noisy all-caps, and duplicate values are filtered.
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | yes | Partial query. Empty input returns an empty array. |
GET https://puri.li/api/suggest?q=google+m
["google maps", "google mail", "google meet", "google my business"]/api/correctQuery correction
Returns local spelling or spacing correction metadata. Search responses can already include the same correction object, so call this separately only when correction is needed before executing a search.
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | yes | The query to inspect for a correction. |
GET https://puri.li/api/correct?q=EU+webhosting
{
"type": "showing",
"original": "EU webhosting",
"corrected": "EU web hosting"
}/api/infocardInfocards and instant answers
Returns Wikipedia-derived entity information and locally resolved instant answers when Purili has a confident match. A null or empty response means no confident card was available; clients should continue with ordinary search results.
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | yes | Entity, fact, or direct-answer query. |
GET https://puri.li/api/infocard?q=capital+of+the+UK
{
"title": "United Kingdom",
"entityType": "place",
"displayType": "Place",
"instantAnswer": {
"question": "capital of united kingdom",
"answer": "London",
"sourceLabel": "Infobox country - capital"
},
"facts": [{ "label": "Capital", "value": "London" }],
"sourceUrl": "https://en.wikipedia.org/wiki/United_Kingdom"
}/api/imagesImage search
Returns image results used by Purili's Images view. This endpoint currently searches Wikimedia Commons and proxies returned assets through Purili; it is not sourced from the independent core web index. Respect the license included with each result.
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | yes | Image search query. |
| page | integer | no | One-based result page; defaults to 1. |
GET https://puri.li/api/images?q=mountains&page=1
{
"images": [{
"id": "wm-File:Mountain.jpg",
"title": "Mountain",
"url": "/api/image-proxy?url=...",
"thumbUrl": "/api/image-proxy?url=...",
"width": 400,
"height": 300,
"source": "wikimedia",
"license": "CC BY-SA"
}],
"total": 24,
"page": 1
}/api/newsNews feed
Returns cached news items used by Purili's News interface. The endpoint supports topic matching within the available feed cache. Its response is currently optimized for the UI and should be treated as a preview contract.
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | no | Optional topic or keyword filter. |
GET https://puri.li/api/news?q=technology
{
"items": [{
"id": "a1b2",
"title": "Technology headline",
"description": "Short summary from the feed.",
"url": "https://example.com/article",
"source": "Example News",
"category": "technology",
"publishedAt": "2026-08-20T09:30:00.000Z",
"imageUrl": "/api/news-image?id=..."
}]
}/api/faviconFavicons
Fetches, normalizes, and caches the icon for a public hostname. Unlike the other endpoints, the successful response is image data rather than JSON. Invalid and local-only hostnames are rejected.
| Parameter | Type | Required | Description |
|---|---|---|---|
| host | string | yes | Public hostname such as wikipedia.org; do not include a path. |
GET https://puri.li/api/favicon?host=wikipedia.org
HTTP/1.1 200 OK
Content-Type: image/png
Cache-Control: public, max-age=604800, immutable/api/crawler-statsCrawler and index statistics
Returns public operational counters used by Purili's Stats page. Values are point-in-time measurements and may be absent when a particular backend metric is unavailable.
This endpoint has no parameters.
GET https://puri.li/api/crawler-stats
{
"pages_indexed": 39113872,
"index_size_bytes": 64563604275,
"queue_depth": 0,
"estimated_unique_domains": 1117539,
"pages_per_sec": 273.8
}/api/submit-siteSubmit a site for crawling
Creates a lightweight arithmetic challenge with GET, then accepts a public URL as crawl seeds with POST. Submission is rate-limited and does not guarantee indexing or ranking.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes (POST) | Public http(s) site URL to submit. |
| challengeToken | string | yes (POST) | Opaque token returned by GET /api/submit-site. |
| challengeAnswer | string | yes (POST) | Answer to the challenge associated with the token. |
GET https://puri.li/api/submit-site
POST https://puri.li/api/submit-site
Content-Type: application/json
{
"url": "https://example.org/",
"challengeToken": "<challenge token>",
"challengeAnswer": "12"
}
{
"ok": true,
"url": "https://example.org/",
"seedCount": 3,
"message": "Queued 3 crawl seeds."
}