cl0q Developer Documentation
An independent search engine, going open source soon — its own crawler, its own index, no third-party search backend.
cl0q crawls the open web, builds its own inverted index, and serves it from a small read-only HTTP API. It is not a front-end for anybody else's search results. This page documents the public API, the query language, how the index is built, and how cl0q's crawler behaves if it visits your site.
Privacy
cl0q is built so that there is no search history to leak, subpoena, sell, or breach. The guarantees below are architectural — they follow from how the system is built, not from a promise to behave.
no-referrer, so it receives
nothing.We do not track your searches
- Search never needs an account. No login, no key, no identity — there is nothing to attach a search to. Optional accounts (for voting, comments, collections and deeper browsing) use passkeys: no email, no password.
- Your queries are never written to any database. cl0q has no search-history table, no query log table, and no analytics store. A query exists in memory for the life of the request and is then gone.
- No third-party anything. The site loads zero external resources — no analytics, no tag managers, no ad pixels, no CDN fonts, no trackers of any kind. Every byte comes from cl0q. You can verify this by viewing source.
- Your query is not leaked to sites you click. cl0q sends
Referrer-Policy: no-referrer, so when you click a result, the destination site is told nothing about what you searched for or that you came from cl0q. Most search engines leak the referring URL here. - No cookies unless you sign in. Signed out, cl0q sets none. Signing in sets one strictly-necessary session cookie, removed on sign-out. None is ever used to track you or across sites.
- Traffic statistics are aggregate-only. A collector on our own server keeps daily totals — pages viewed, referring domain, country, browser family, whether a client is a bot — and nothing per request. Country comes from a local copy of DB-IP's free country table; no IP is sent to any lookup service. It never receives the query string, and IPs exist only in memory as a keyed hash whose secret rotates daily and is never stored.
- Rate limiting is anonymous and in-memory. Abuse protection counts requests per IP in RAM only. It is never persisted, and it holds no query text.
- API keys are stored hashed. Keys are kept only as a SHA-256 hash. cl0q cannot recover your key, and a key is never linked to the searches made with it.
What we do keep
cl0q keeps no per-request access log. For a normal request — including every search — no IP address, requested URL, query string, or user agent is written to disk. There is no per-request record to analyse, profile, share, or sell, because none is kept.
The only things logged are application errors and operational events (a failed or slow database query, the server starting or stopping), for a short retention window used strictly for uptime monitoring and debugging. These entries contain no IP address and no search terms — a rejected query, for instance, records only its length, never its content.
Things you explicitly opt into
Two features necessarily store data, because they cannot work otherwise. Both are opt-in and neither is required to use cl0q:
- Saved-search alerts (
/api/watch) store the query you asked to be alerted about, plus the browser push endpoint to notify. That is the entire point of the feature. Deleting the subscription removes it. - API key signup (
/api/signup) optionally accepts an email address so we can contact you about the key. It is optional — leave it blank and no contact information is stored at all. The key itself is always stored hashed.
See also the privacy policy.
Quick start
Search is public and needs no key:
curl 'https://cl0q.com/search?q=open+source+search&limit=5'
For the API-key-gated endpoints, mint a free key and send it as a header:
curl -X POST https://cl0q.com/api/signup
# -> {"api_key":"cl0q_...","tier":"free","per_min":30,"per_day":1000, ...}
curl -H 'X-API-Key: cl0q_...' 'https://cl0q.com/api/favicon?domain=github.com'
Access-Control-Allow-Origin: *, so you can call them from a browser. Everything is
served over HTTPS with HSTS.Search API
GET /search no key required
Parameters
| Param | Type | Default | Description |
|---|---|---|---|
q | string | — | Required. 2–512 characters. May contain operators. Longer values are truncated at 512. |
limit | int | 50 | Results per page. Clamped to 1–200. |
offset | int | 0 | Pagination offset. Clamped to 0–1000000. |
lang | string | — | Restrict to a detected page language (e.g. fr). Also selects language-aware
stemming and snippet highlighting. Unknown/empty means no filter and English snippets. |
safe | 0/1 | 1 |
SafeSearch (mature-content filter). On by default; pass safe=0 to disable. |
apex_only | 0/1 | 0 |
Return only apex domains, excluding subdomains. www. counts as apex. Useful
because large hosting platforms can otherwise flood results with subdomains. |
include_favicon | 0/1 | 0 |
Include the site's favicon bytes (base64) per result. Increases response size noticeably. |
include_org | 0/1 | 0 |
Include network/ASN and DNS detail per result. |
since | RFC 3339 | — | Only results crawled at or after this timestamp, e.g. 2026-01-01T00:00:00Z.
Invalid values are ignored rather than rejected. |
Example
curl 'https://cl0q.com/search?q=intitle:handbook+rust&limit=10&apex_only=1&lang=en'
Response
JSON with a result array and a real total (not an estimate), so you can paginate honestly.
Each result carries the indexed page metadata: domain, page title, h1, description, a
match-highlighted snippet, HTTP status, and the crawl timestamp. include_org=1 adds
network/ASN and DNS fields; include_favicon=1 adds icon bytes.
Query operators
Operators are parsed out of q; whatever remains is full-text matched. Prefix any
operator with - to negate it. Operators can be combined.
| Operator | Matches | Example |
|---|---|---|
site: | That domain and its subdomains (site:.gov = a whole TLD) | site:github.io static site |
inurl: | Domain contains (3+ characters) | inurl:docs api |
intitle: | Page title contains | intitle:changelog |
ip: | Resolved IPv4 of the host | ip:93.184.216.34 |
cname: | CNAME target | cname:cdn.example.net |
asn: | Network/ASN org, prefix match | asn:AS13335 |
org: | Network org, substring match | org:cloudflare |
favicon: / icon: | Favicon SHA-256 hash | favicon:<hex> |
Notes and limits
org:requires at least 3 characters. Shorter values are rejected with a400rather than silently running an unindexed scan — a deliberate choice to keep one query from degrading the service for everyone.- The infrastructure operators (
ip:,cname:,asn:,org:,favicon:) are feature-detected at startup. If one isn't available on this instance it returns a clear400saying so, instead of pretending to work. - A query consisting only of operators is valid; free text is optional.
Suggest API
GET /suggest no key required
| Param | Description |
|---|---|
q | Partial query. |
format | os returns the OpenSearch suggestions array
[query, [domains], [titles], [urls]]. Omit for the default JSON shape. |
safe | SafeSearch (mature-content filter), same as /search. On by default; pass safe=0 to disable. |
Built for typeahead: it's tuned for latency and rate-limited separately from
/search. cl0q also ships an OpenSearch descriptor, so
browsers can add cl0q as a search engine and use this endpoint for suggestions.
Favicon-hash API
GET /api/favicon API key required
Favicons are content-addressed by SHA-256, which makes them a pivot: sites sharing an icon are usually related — same operator, same framework default, same phishing kit, same rebrand. This is the same class of pivot Shodan/FOFA-style tooling uses, over cl0q's open-web index.
| Param | Description |
|---|---|
domain | Look up this domain's favicon hash, plus how many other domains serve the same icon and a sample of them. |
hash | Reverse pivot: hex SHA-256 → every indexed domain serving that exact icon. |
limit | Default 100, max 1000. |
# forward: domain -> hash + who else uses it
curl -H 'X-API-Key: cl0q_...' 'https://cl0q.com/api/favicon?domain=github.com'
# reverse: hash -> domains
curl -H 'X-API-Key: cl0q_...' 'https://cl0q.com/api/favicon?hash=<hex-sha256>&limit=50'
Pass exactly one of domain or hash. A malformed hash returns
400.
Subdomains API
GET /api/subdomains API key required
| Param | Description |
|---|---|
domain | Apex domain to enumerate. |
limit | Default 100, max 1000. |
Returns subdomains cl0q discovered by crawling the open web. This is passive — it reflects what was found by following public links and DNS, and is not a live brute-force of your zone.
Domain profile API
GET /api/domain/<host> API key required
Everything cl0q knows about one host — the machine-readable twin of the
/site/<host> page. Returns page metadata (title, description, language, word count),
response characteristics (status, content type, latency, outbound links), DNS (IPv4/IPv6, CNAME, MX)
and network attribution (ASN, org, region, country).
curl -H 'X-API-Key: cl0q_...' 'https://cl0q.com/api/domain/tautulli.com'
{
"domain": "tautulli.com",
"title": "Tautulli",
"status_code": 200,
"language": "en",
"server": "cloudflare",
"word_count": 380,
"latency_ms": 116,
"outbound_links": 11,
"ipv4": "104.21.80.1",
"ipv6": "2606:4700:3030::6815:6001",
"mx_records": "route1.mx.cloudflare.net.,...",
"asn": "13335",
"asn_org": "AS13335 Cloudflare, Inc.",
"region": "California",
"country": "US",
"favicon_sha256": "8d422d2b465d7199dd334ffa6c7e79b70a74c68ca48a8bcc217baf8fd6f8bb7e",
"first_seen": "2025-08-24T19:32:27Z",
"last_crawled": "2026-07-03T23:39:28Z"
}
The favicon_sha256 is the pivot point: feed it straight back into
favicon:<hash> or the favicon API to find every other site
serving the same icon. Returns 404 with {"error":…} if the host isn't in
the index.
Data API no key needed
Aggregate and facet data — the same numbers behind the explore dashboard. These read precomputed snapshots rather than querying the index live, so they're free, need no API key, and are only IP rate-limited. Refreshed hourly (stats) and daily (facets).
| Endpoint | Returns |
|---|---|
GET /api/stats | Index-wide totals (pages, domains, TLDs, favicons), 24h crawl velocity, and the top distributions by TLD, network, server, language and status. |
GET /api/facets | Discovery index: every drill-down key you can query, normalised to {kind: [{key, label, count}]}. Start here. |
GET /api/tld/<tld> | Per-TLD profile — page count, rank, share, notable sites, top hosting networks. e.g. /api/tld/io |
GET /api/country/<cc> | Per-country profile (ISO-3166 alpha-2, lower-case). "Hosted in" = the site's IP geolocates there. e.g. /api/country/de |
GET /api/tech/<server> | Per-server-software profile, plus the languages it serves. e.g. /api/tech/nginx |
GET /api/lang/<code> | Per-language profile, plus the servers behind it. e.g. /api/lang/de |
GET /api/cross/<kind>/<key> | Cross breakdown of one drill-down along the other dimensions — e.g. a network's top TLDs, countries, languages and server software. kind is asn, tld, country, lang or tech. e.g. /api/cross/asn/13335 |
# what can I drill into?
curl 'https://cl0q.com/api/facets'
# how big is the index right now?
curl 'https://cl0q.com/api/stats'
# which web servers dominate the German-language web?
curl 'https://cl0q.com/api/lang/de'
The facet endpoints cross-reference each other: /api/tech/nginx includes a
cross breakdown of the languages it serves, and /api/lang/de the servers
serving it. Counts are exact; the sites lists are a representative sample of the index,
not a ranking. Unknown or too-small keys return 404 — call /api/facets for
the valid set.
Browse API first 150 rows free
Every live domain behind a drill-down — all the sites on a network, a TLD, a country, a language, a server family, or sharing a favicon — as a filterable, paginated list. This is what powers the site lists on the explore pages.
GET /api/browse/<kind>/<key>?tld=&country=&lang=&tech=&asn=&apex=1&q=&limit=&cursor=
| Parameter | Meaning |
|---|---|
kind / key | asn/13335, tld/io, country/de, lang/ja,
tech/nginx, or favicon/<sha256>. Keys match the drill-down pages (see /api/facets). |
tld, country, lang, tech, asn | Filter on any of the other
dimensions (any combination). Use /api/cross/<kind>/<key> to see which values exist. |
apex=1 | Registrable domains only (no subdomains). |
q | Domain or title contains this text (2–60 characters). |
safe=0 | Include mature sites (SafeSearch is on by default). |
limit | Rows per page, 1–100 (default 50). |
cursor | The next value from the previous page. Opaque and signed — don't build it yourself. |
curl 'https://cl0q.com/api/browse/asn/13335?country=de&lang=de'
{"kind":"asn","key":"13335","mode":"index","items":[
{"d":"example.de","t":"Example GmbH","cc":"de","lang":"de","tech":"cloudflare",
"asn":"13335","org":"Cloudflare, Inc.","at":"2026-09-18"}, …],
"next":"12345678.50.9f2c…","served":50,"free_rows":150,"scanned":2500}
Lists are newest-first (favicon lists are A→Z). Filters are applied to a bounded window of
candidates per request, so a rare combination can return a short — even empty — page with a
next cursor: keep following next until it is null. Without an API key,
each list is open for its first 150 rows; past that the API answers 401 with
{"error":"signin_required"}. Send an API key (X-API-Key) to page through
everything. On the website, a free cl0q account does the same.
API keys & rate limits
Getting a key
curl -X POST https://cl0q.com/api/signup
curl -X POST https://cl0q.com/api/signup -d 'email=you@example.com' # optional
Returns the key once, in the response body. It is shown only once — cl0q stores only a hash and cannot show it to you again. The email is optional and used solely to contact you about the key; omit it and nothing identifying is stored.
Sending a key
Header X-API-Key: cl0q_... (preferred) or query param ?key=cl0q_....
Limits
| Endpoint | Unauthenticated | With a free key |
|---|---|---|
/search | 10 requests per 10s per IP | Gate bypassed |
/suggest | Per-minute per-IP limit | — |
/api/favicon, /api/subdomains, /api/domain | Not available | 30/min, 1000/day |
/api/stats, /api/facets, /api/{tld,country,tech,lang}, /api/cross |
Free — IP rate-limited only | Same |
/api/browse | First 150 rows per list; IP rate-limited | Every row |
/search gate exists to protect a small shared database from
scrapers, not to nag humans — normal interactive searching never hits it. If you're building
something real, take a free key.Paid tiers raise per_min/per_day on the same key. During
beta, everyone gets a free key immediately.
Other endpoints
| Endpoint | Description |
|---|---|
GET /api/city | ASN "skyline" — aggregate view of networks in the index. Cached. |
GET /api/city/asn?org= | Per-network detail windows. |
GET /api/city/locate?q= | Find which network a domain sits on. |
GET /site/<domain> | Human-readable per-domain page. |
GET /network/<asn> | Human-readable per-network hub page. |
GET /sitemaps/ | Sitemap index for the above. |
GET /opensearch.xml | OpenSearch descriptor — add cl0q to your browser. |
POST /api/watch | Saved-search alerts via Web Push. Opt-in; stores the query and push endpoint. |
POST /api/feedback | Send feedback. |
POST /api/report | Report content or request removal of a domain. |
How the index is built
Four stages, each feeding the next. Every stage is incremental and restartable — nothing depends on a full rebuild. The important thing to notice is what crosses each boundary: a layer only ever hands the next one a small, well-defined artefact.
1. Discovery
A breadth-first crawler walks outward from a curated seed list of link-rich, bot-friendly pages, recording every new registrable domain it sees. Because a fixed seed list plateaus quickly — it re-walks the same neighbourhood and stops finding anything — discovery also continuously re-samples domains already known to the index and mines their homepages for outbound links. The known web becomes a rotating frontier rather than a finished list.
2. Page crawl
Discovered domains are fetched and parsed into the index: title, h1, meta
description, keywords, canonical, Open Graph and Twitter card metadata, charset, detected
language, robots directives, outbound link count, image count, word count, HTTP status, response
latency, redirect count, and server header.
Two details matter for quality. Content-type gating means only text/html is
parsed, so binary responses never become garbage "pages". And anti-bot interstitial detection
means when a challenge page is served instead of real content, cl0q recognises it, refuses to
index the challenge text, and records why — rather than indexing thousands of identical
"Checking your browser…" pages.
Most fetches do not end up in the index. Every gate below exists to keep something out:
3. Enrichment
Separate passes resolve DNS (A/AAAA/MX/CNAME) for domains discovered link-only, and map IPs to
their network/ASN and country. These feed the ip:, cname:,
asn: and org: operators and the region facets. Favicons are fetched and
stored content-addressed by SHA-256, so an icon shared by 100,000 sites is stored once — which is
also what makes the favicon pivot cheap.
4. Serving
A read-only API serves the index. Nothing in the read path writes to the crawl tables, so search load and crawl load stay independent.
Federation
cl0q can accept crawl results from trusted independent nodes, so contributors can crawl on
their own hardware and contribute back without ever receiving database credentials. Submissions
are authenticated, re-validated server-side against central policy, and tagged with their source
so any node's contribution can be audited or removed wholesale. Interested in running one? Use
/api/feedback.
Ranking & matching
Matching is PostgreSQL full-text search over a weighted vector, not a bag of words. Fields are weighted by trustworthiness:
| Weight | Field | Rationale |
|---|---|---|
| A (highest) | title, h1 | What the page says it is. |
| B | description | Author-written summary. |
| C (lowest) | keywords | Historically spam-prone — deliberately weakest. |
Ranking then layers on an apex boost (an apex domain outranks a subdomain, all
else equal) and a network popularity signal derived from how much of the index
sits on a given network. Substring operators (inurl:, intitle:,
org:) are trigram-indexed, which is why they need 3 characters — a
shorter pattern has no trigram to look up. A query that is just a hostname (example.com)
lists that site first.
Search runs in two stages: a candidate-and-rank pass, then a single batched detail fetch for
the page you asked for. Hot queries are briefly cached. Result total is a true count,
not an estimate.
For site owners: the cl0q crawler
If cl0q's crawler visits you, here's exactly what it does and how to control it.
Identifying it
The crawler sends a user agent containing cl0q-crawler and a link to
cl0q.com/bot. It is browser-shaped so that naive filters don't reject it, but
it always self-identifies and is always contactable — it will never pretend to be an ordinary
browser to evade you.
Controlling it
robots.txtis respected. Disallow the crawler and it stops.- Rate limits are per-host and per-IP. Concurrency against any single site or address is capped, and one busy network can't monopolise a crawl batch.
429and503are honoured, includingRetry-After. Throttling is treated as "come back later", not as a dead page.- Repeated failures back off automatically via a per-group circuit breaker, so a struggling host isn't hammered.
- Removal: use
/api/reportto request removal of a domain.
Being crawled efficiently
The crawler sends If-None-Match / If-Modified-Since when it has
previously stored an ETag or Last-Modified for you. Serve validators and
a re-crawl costs you a 304 and a few bytes instead of a full page render. cl0q fetches
your homepage — it does not spider your whole site.
Errors
| Status | Meaning |
|---|---|
400 | Bad request — q missing or under 2 chars;
org: under 3 chars; malformed favicon hash; an operator not enabled on this instance. |
401 / 403 | Missing or invalid API key on a gated endpoint. /api/browse returns
401 with "signin_required" past its 150-row anonymous preview. |
405 | Wrong method (e.g. GET on /api/signup). |
429 | Rate limited. Back off, or use an API key. |
500 | Server error. These are reported internally; if one is
reproducible, please send it via /api/feedback. |
503 | Backend temporarily unavailable — returns a maintenance response rather than hanging. |