Public search API
A free JSON API over an independent index of official government websites in the United States, Canada, and the United Kingdom, from national departments down to city councils. No key, no signup, CORS enabled.
Every endpoint is a GET request that returns JSON. Send no headers and no
credentials.
https://crawl.govglance.org
# Search for passport renewal pages
curl "https://crawl.govglance.org/api/v1/search?q=renew+passport&size=3"
The main endpoint. Full-text search across every indexed page, in all three countries unless you narrow it.
/api/v1/search| Parameter | Type | Description |
|---|---|---|
q | string required | Query text. Supports the operators below. Max 500 characters. |
page | integer | 1-indexed page number. Default 1. The result window is capped at 1000. |
size | integer | Results per page, 1–50. Default 10. |
country | string, repeatable | us, ca, uk. Defaults to all
of them. Comma-separated values work too
(country=us,ca), as do the ISO codes, where the United
Kingdom is gb. |
level | string, repeatable | Level of government. The values differ by country — see Countries. |
state | string, repeatable | State, province, or territory. Code or full name, so tx,
Texas and british columbia all work. See
Places. |
city | string, repeatable | City, town, or council, e.g. Austin. Case and punctuation are
ignored, and st louis finds St. Louis. Names repeat
across states, so pair it with state to mean one Springfield
rather than all of them. |
domain | string, repeatable | Restrict to specific domains, e.g. irs.gov. |
filetype | string | pdf, html, doc, docx |
language | string | Two-letter code, e.g. fr for French-language pages. |
facets | boolean | Include aggregation counts. Default true; set false for lower latency. |
Results will appear here.
Operators go inside q and combine freely with the query parameters.
| Operator | Example | Effect |
|---|---|---|
site: | site:irs.gov tax forms | Restrict to one domain |
country: | country:uk council tax | Restrict to one country |
filetype: | budget filetype:pdf | Restrict by document type |
level: | level:city parking permit | Restrict by level of government |
state: | state:tx drivers license | Restrict by state, province, or territory |
city: | city:austin zoning | Restrict by city or council |
intitle: | intitle:budget | Require a term in the page title |
inurl: | inurl:forms | Require a term in the URL |
"…" | "notice of proposed rulemaking" | Exact phrase |
-term | permits -parking | Exclude a term |
Trimmed to one result for readability.
{
"query": "renew passport",
"total": 3255,
"total_is_lower_bound": false,
"page": 1,
"size": 10,
"results": [
{
"url": "https://travel.state.gov/…/renew.html",
"title": "Renew my <mark>Passport</mark>",
"snippet": "You can renew by mail if your …",
"domain": "state.gov",
"organization": "US Department of State",
"country": "US",
"level": "federal",
"state": null,
"language": "en",
"city": null,
"content_type": "text/html",
"published_at": null,
"content_changed_at": "2026-07-30T02:11:04Z",
"updated_at": "2026-07-30T02:11:04Z",
"last_crawled": "2026-08-05T14:21:56Z",
"word_count": 807,
"score": 317.2
}
],
"facets": {
"countries": [{ "value": "US", "count": 2911 }],
"levels": [{ "value": "federal", "count": 836 }],
"states": [{ "value": "DC", "count": 644 }],
"cities": [{ "value": "Washington", "count": 612 }],
"domains": [{ "value": "uscis.gov", "count": 279 }],
"types": [{ "value": "text/html", "count": 3067 }]
},
"took_ms": 77.3
}
title and snippet are already HTML-escaped and
may contain <mark> around matched terms. Insert them as HTML;
escaping them a second time renders the escaping itself, so
apply/renew comes out as apply/renew. Every other
field — url, domain, organization and the rest —
is raw text and must be escaped by you before it reaches the DOM. For plain text, strip
the <mark> tags and HTML-decode.
When total_is_lower_bound is true,
total is a floor rather than an exact count.
The latest published or updated pages, ordered by date instead of relevance. Every filter from Search applies, so the same endpoint gives you "what has Austin published lately" and "what has the UK published lately".
/api/v1/recent/api/v1/recent.rssTwo independent signals say a page is current: the date the page declares, and the
date we watched its text change on a recrawl. sort chooses between them.
| Sort | Field | Meaning |
|---|---|---|
updated default | updated_at |
The later of the two below. The best general answer. |
published | published_at |
The date the page itself declares. About a quarter of pages carry one. |
changed | content_changed_at |
When we last saw the text change. Only pages crawled more than once. |
crawled | fetched_at |
When we last fetched it. This describes our schedule, not the site's. |
Pages with no known date are left out. A page we have crawled once that declares no date has no recency we can honestly report, and dating it to the moment we happened to reach it would turn the feed into a report on our own crawler. Dates in the future are dropped for the same reason: a page about next month's council meeting was not published next month.
| Parameter | Type | Description |
|---|---|---|
q | string | Optional here. Narrows the feed to matching pages. |
sort | string | One of the four above. Default updated. |
since | string | Lower bound. A relative window — 7d, 24h,
3w, 6m, 1y — or an ISO date like
2026-08-01. |
until | string | Upper bound, same formats. Defaults to now: a recency feed never shows the future. |
country, state, city,
level, domain, filetype,
language | Exactly as on Search. | |
page, size | integer | Default size 20, max 50. |
# Everything Austin, Texas has published in the last month
curl "https://crawl.govglance.org/api/v1/recent?city=Austin&state=TX&since=30d"
# UK councils only, as an RSS feed you can subscribe to
curl "https://crawl.govglance.org/api/v1/recent.rss?country=uk&level=local_authority"
# Pages about flooding that changed in the past week, anywhere
curl "https://crawl.govglance.org/api/v1/recent?q=flooding&sort=changed&since=7d"
The JSON response is the same shape as Search, plus
sort, sort_meaning, window and
place echoing what was actually applied. The RSS variant is
RSS 2.0 and takes the same parameters except until,
page and facets.
The states, provinces and cities you can filter on. Only places with at least one
crawled government domain appear, so this describes the index rather than the world.
Every city value returned is exactly what the city filter
expects.
/api/v1/places| Parameter | Type | Description |
|---|---|---|
country | string, repeatable | us, ca, uk. |
state | string | Narrow the city list to one state or province. |
q | string | City name prefix, for autocomplete. |
limit | integer | Cities to return, 1–1000. Default 200, busiest first. |
{
"states": [
{ "code": "TX", "name": "Texas", "country": "US",
"type": "state or territory", "cities_indexed": 431 }
],
"cities": [
{ "city": "Austin", "state": "TX",
"state_name": "Texas", "country": "US", "domains": 47 }
],
"cities_known": 9332
}
The jurisdiction a domain serves, not the address its registrant receives post at.
Those differ for about one US city domain in six — addisontx.gov is
registered from Dallas — so the served city is reconstructed from the organization
name and confirmed against the domain name before it is indexed. Where the evidence
is inconclusive the registry's own answer is kept, so a handful of small
jurisdictions still carry a neighbouring town's name.
The United Kingdom contributes no entries to states. No
published register records which nation or region a UK public body belongs to, and
inferring it from a council's name would be wrong often enough to be worse than
saying nothing. UK results are filtered by city, level and
domain instead.
Autocomplete drawn from indexed page titles. Useful for search-as-you-type.
/api/v1/suggest| Parameter | Type | Description |
|---|---|---|
q | string required | Prefix, 2–100 characters. |
size | integer | 1–20. Default 8. |
curl "https://crawl.govglance.org/api/v1/suggest?q=medicare"
{ "query": "medicare", "suggestions": ["Medicare Costs", "Medicare Enrollment"] }
The countries in scope and the levels of government each one contributes. Build country and level pickers from this rather than hard-coding the lists, which change as the crawl grows.
/api/v1/countriesLevels are deliberately not forced into a shared vocabulary: a UK council is not a US county, and collapsing both into "local" would discard the distinction you are usually filtering on.
| Country | Accepts | Levels |
|---|---|---|
| United States | us |
federal, interstate, state,
county, city, tribal,
special_district, school_district |
| Canada | ca |
federal, provincial,
crown_corporation, municipal,
indigenous |
| United Kingdom | uk, gb |
national, parliamentary, judicial,
devolved, health, police,
local_authority, public_body |
public_body covers the several thousand
.gov.uk registrations that the published register lists by name
only. Recording them as public bodies is accurate; guessing whether each is a
department or a council would not be.
curl "https://crawl.govglance.org/api/v1/countries"
{
"default": "all countries",
"countries": [
{ "code": "US", "name": "United States", "aliases": [],
"levels": ["federal", "state", …], "domains": 13084, "pages_indexed": 2841003 }
]
}
Browse the government domains that make up the index.
/api/v1/domains| Parameter | Type | Description |
|---|---|---|
country | string, repeatable | us, ca, uk. Defaults to all. |
level | string | Filter by level of government. |
state | string, repeatable | State, province, or territory. Code or full name. |
city | string, repeatable | City, town, or council. |
search | string | Match against domain or organization name. |
limit | integer | 1–500. Default 100. |
offset | integer | For pagination. |
Index and crawl coverage. Recomputed in the background every five minutes.
/api/v1/statsindexed_pages and domains_total are exact.
The frontier totals under crawl are planner estimates, flagged by
counts_are_estimates, because an exact count of a table this size is far
too expensive to serve. Also useful: GET /health returns per-dependency
status and a 503 when search is unavailable.
| Status | Meaning |
|---|---|
400 | Invalid parameter — empty query; unknown country, level, state or sort; an unreadable since; or a page beyond the 1000-result window. A city with no indexed government domain is also a 400, with suggestions, rather than an empty result set that would look like a working filter. |
429 | Rate limit exceeded. Includes a Retry-After header. |
503 | Search backend temporarily unavailable. Retry with backoff. |
Errors return {"detail": "…"}. The rate limit is applied per IP per minute.
Responses are cached briefly, so repeated identical queries are cheap and return
X-Cache: HIT. If you need a higher limit for research or public-interest work,
get in touch through govglance.org.