Skip to main content

Developer reference

The Candidate Public API

Federal candidate + historical political reference data, served as JSON. Public domain (CC0) — free for any use, attribution appreciated but not required. Updated weekly. OpenAPI 3 spec, fail-open rate limits, designed for LLM-friendly candidate data ingest.

Spec version
1.5.0
Auth
None (read-only)
Rate limits
60/min default,
600/min for named AI bots
Posture
Fail-OPEN, UA-aware

Quickstart

The public read API is a JSON API. No auth, no signup. Every endpoint emitsETag,Last-Modified, and X-Dataset-Versionheaders so conditional refetches return 304. The fastest way to verify is one curl:

curl
curl -i 'https://thecandidate.com/api/historical/presidents/abraham-lincoln' \
  -H 'Accept: application/json'

You'll see a 200 OK response with the full Person record (the same shape as the JSON-LD on the matching /federal/president/historical/<slug>page) and the rate-limit + cache headers. Repeat with If-None-Match: <etag> to get a 304.

Authentication

None. The read API is fully public. Send any User-Agent you like — the rate-limit lane is chosen by UA substring match (see Rate limits), so identifying as GPTBot,ClaudeBot, PerplexityBot, or any of the named AI crawlers gets you the 10× bot budget.

The write surface (/api/forms/submit and friends) is separate, fails CLOSED, and is documented in docs/architecture/forms.md. The forms surface is not part of this reference.

Endpoints

Sprint 22 ships the historical-presidents surface (46 rows). Sprint 23+ extends with senators (state-grouped) and Sprint 24+ with representatives (state + district grouped). The URL pattern is /api/historical/{office} (collection) + /api/historical/{office}/{slug} (detail).

GET/api/historical/presidents

List every former U.S. President

Returns every individual who has served as President of the United States, in chronological order (first-term start year ascending). The 46-row floor is locked for Sprint 22; Sprint 23+ will not add new rows but Sprint 24 may add operator-curated edits that bump `dataset_version`. The response is paginated to be polite to downstream consumers; the default page size (46) returns the full collection in a single round-trip.

Query parameters

NameInTypeRequiredDescription
pagequeryintegerno1-indexed page number. Defaults to 1.
per_pagequeryintegernoPage size. Maximum 100. Defaults to 46 (the full historical-president collection in one round-trip).

Response headers (200)

  • ETagStrong validator derived from MAX `dataset_version` across rows + row count + ordering hash. Use with `If-None-Match` for conditional refetch.
  • Last-ModifiedHTTP-date string derived from MAX `updated_at` across rows.
  • Cache-Control`public, max-age=300, stale-while-revalidate=3600`.
  • X-Dataset-VersionMAX `dataset_version` across rows. Mirrors `meta.dataset_version` in the response body for header-only consumers.
  • Link`</openapi.json>; rel="describedby"; type="application/vnd.oai.openapi+json;version=3.0"`
  • Vary`Accept-Encoding, User-Agent` — the UA varies the rate-limit lane.
  • X-RateLimit-LaneWhich rate-limit lane the request was scored against. `bot` = named AI crawler (10× budget); `default` = everything else.
  • X-RateLimit-LimitPer-minute budget for the chosen lane. `60` (default) or `600` (bot).
  • X-RateLimit-RemainingBest-effort hits remaining in the current window AFTER this call. Capped at zero. `0` does NOT guarantee the next request will 429 — the window may have reset.
  • X-RateLimit-FailedOpenPresent (`1`) only when the rate-limit backing store was unreachable and we synthesized an `allowed` result per the fail-OPEN policy. Audit-only — operators can spot fail-open periods in CDN logs.
  • X-RateLimit-ResetISO-8601 timestamp at which the current lane window closes and the budget resets. Sprint 22 production format; preserved for any consumer parsing it directly. Sprint 23 Task 06 added emission on 200 (not just 429) so LLM consumers can self-throttle without waiting for a 429 wall.
  • X-RateLimit-Reset-EpochUnix epoch seconds form of `X-RateLimit-Reset` (matches the GitHub / IETF RFC 9331-draft convention). Identical instant, alternative parser path. Both formats emit on every 200 + 429.

Code samples

curl
curl -i 'https://thecandidate.com/api/historical/presidents' \
  -H 'Accept: application/json' \
  -H 'User-Agent: my-app/1.0'
JavaScript (fetch)
const res = await fetch('https://thecandidate.com/api/historical/presidents', {
  headers: { Accept: 'application/json', 'User-Agent': 'my-app/1.0' },
});
const { data, meta } = await res.json();
Python (httpx)
import httpx

resp = httpx.get(
    'https://thecandidate.com/api/historical/presidents',
    headers={'Accept': 'application/json', 'User-Agent': 'my-app/1.0'},
)
resp.raise_for_status()
payload = resp.json()

GET/api/historical/presidents/{slug}

Fetch one former U.S. President by slug

Returns a single row matching the kebab-case slug (e.g. `abraham-lincoln`, `theodore-roosevelt`). Slugs are stable; once published they never change. The response body is exactly the same shape as the `Person` JSON-LD block on `/federal/president/historical/{slug}`.

Query parameters

NameInTypeRequiredDescription
slugpathstringyesKebab-case slug, e.g. `abraham-lincoln`.

Response headers (200)

  • ETagStrong validator derived from `dataset_version` + a SHA-256 of the row's material columns.
  • Last-ModifiedHTTP-date string from the row's `updated_at`.
  • Cache-Control`public, max-age=300, stale-while-revalidate=3600`.
  • X-Dataset-VersionRow `dataset_version`.
  • Link`</openapi.json>; rel="describedby"; type="application/vnd.oai.openapi+json;version=3.0"`
  • X-RateLimit-LaneWhich rate-limit lane the request was scored against. `bot` = named AI crawler (10× budget); `default` = everything else.
  • X-RateLimit-LimitPer-minute budget for the chosen lane.
  • X-RateLimit-RemainingBest-effort hits remaining in the current window AFTER this call.
  • X-RateLimit-FailedOpenPresent (`1`) only when the backing store was unreachable and we synthesized an `allowed` result per the fail-OPEN policy.
  • X-RateLimit-ResetISO-8601 timestamp at which the current window closes (Sprint 22 production format).
  • X-RateLimit-Reset-EpochUnix epoch seconds form of `X-RateLimit-Reset` (GitHub / RFC 9331-draft convention).

Code samples

curl
curl -i 'https://thecandidate.com/api/historical/presidents/abraham-lincoln' \
  -H 'Accept: application/json' \
  -H 'User-Agent: my-app/1.0'
JavaScript (fetch)
const res = await fetch('https://thecandidate.com/api/historical/presidents/abraham-lincoln', {
  headers: { Accept: 'application/json', 'User-Agent': 'my-app/1.0' },
});
const { data, meta } = await res.json();
Python (httpx)
import httpx

resp = httpx.get(
    'https://thecandidate.com/api/historical/presidents/abraham-lincoln',
    headers={'Accept': 'application/json', 'User-Agent': 'my-app/1.0'},
)
resp.raise_for_status()
payload = resp.json()

GET/api/historical/senators

List retired U.S. Senators

Returns the curated retired U.S. Senators (reads `historical_office_holders` filtered `office=senator`), ordered by state then full name. Offset/limit pagination over the full ~1,900-row corpus; the page read is bounded server-side (single windowed `Range:` query — never the whole corpus in memory). `Link: <url>; rel="next"` advertises the next page and `X-Total-Count` reports the full matching size so consumers can size their crawl without walking every page. Serving senators are NOT here — they live under `/api/current/senators`.

Query parameters

NameInTypeRequiredDescription
statequerystringno2-letter USPS state code (case-insensitive). Filters to senators who represented that state.
partyquerystringnoParty name (case-insensitive exact match against the stored value, e.g. `Democratic`, `Whig`).
limitqueryintegernoPage size. Default 50, maximum 200.
offsetqueryintegerno0-indexed start offset into the filtered collection. Default 0.

Response headers (200)

  • ETag
  • Last-Modified
  • Cache-Control
  • X-Dataset-VersionMAX `dataset_version` across rows of `historical_office_holders`.
  • Link`</openapi.json>; rel="describedby"` (+ `<next-url>; rel="next"` when more pages exist).
  • X-Total-CountTotal rows matching the applied filters across ALL pages (same value as `meta.total`). Lets a consumer size a crawl without walking every page.
  • X-RateLimit-Lane
  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset
  • X-RateLimit-Reset-Epoch

Code samples

curl
curl -i 'https://thecandidate.com/api/historical/senators' \
  -H 'Accept: application/json' \
  -H 'User-Agent: my-app/1.0'
JavaScript (fetch)
const res = await fetch('https://thecandidate.com/api/historical/senators', {
  headers: { Accept: 'application/json', 'User-Agent': 'my-app/1.0' },
});
const { data, meta } = await res.json();
Python (httpx)
import httpx

resp = httpx.get(
    'https://thecandidate.com/api/historical/senators',
    headers={'Accept': 'application/json', 'User-Agent': 'my-app/1.0'},
)
resp.raise_for_status()
payload = resp.json()

GET/api/historical/senators/{state}/{slug}

Fetch one retired U.S. Senator by state + slug

Returns a single retired senator matching the (state, slug) pair. Mirrors the page route `/federal/senate/historical/{state}/{slug}` (Decision #2). `state` is the lowercase USPS code; `slug` is name-only kebab and does NOT embed `bioguide_id`.

Query parameters

NameInTypeRequiredDescription
statepathstringyesLowercase 2-letter USPS state code, e.g. `va`.
slugpathstringyesKebab-case slug, e.g. `henry-clay`.

Response headers (200)

  • ETagStrong validator: sha256(`bioguide_id` + `dataset_version`), falling back to `slug` for pre-Bioguide rows.
  • Last-Modified
  • Cache-Control
  • X-Dataset-Version
  • Link
  • X-RateLimit-Lane
  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset
  • X-RateLimit-Reset-Epoch

Code samples

curl
curl -i 'https://thecandidate.com/api/historical/senators/{state}/abraham-lincoln' \
  -H 'Accept: application/json' \
  -H 'User-Agent: my-app/1.0'
JavaScript (fetch)
const res = await fetch('https://thecandidate.com/api/historical/senators/{state}/abraham-lincoln', {
  headers: { Accept: 'application/json', 'User-Agent': 'my-app/1.0' },
});
const { data, meta } = await res.json();
Python (httpx)
import httpx

resp = httpx.get(
    'https://thecandidate.com/api/historical/senators/{state}/abraham-lincoln',
    headers={'Accept': 'application/json', 'User-Agent': 'my-app/1.0'},
)
resp.raise_for_status()
payload = resp.json()

GET/api/historical/representatives

List retired U.S. Representatives

Returns the curated retired U.S. Representatives (reads `historical_office_holders` filtered `office=representative`), ordered by state then district then full name. Offset/limit pagination; the page read is bounded server-side (single windowed `Range:` query — never the whole corpus in memory). `Link: <url>; rel="next"` advertises the next page and `X-Total-Count` reports the full matching size. Serving representatives are NOT here — they live under `/api/current/representatives`. Sprint 24 Task 11.

Query parameters

NameInTypeRequiredDescription
statequerystringno2-letter USPS state code (case-insensitive).
districtquerystringnoHouse district within the state — a district number (`1`–`999`) or `AL` (at-large, case-insensitive).
partyquerystringnoParty name (case-insensitive exact match against the stored value, e.g. `Democratic`, `Whig`).
limitqueryintegernoPage size. Default 50, maximum 200.
offsetqueryintegerno0-indexed start offset into the filtered collection. Default 0.

Response headers (200)

  • ETag
  • Last-Modified
  • Cache-Control
  • X-Dataset-VersionMAX `dataset_version` across the representative slice of `historical_office_holders`.
  • Link`</openapi.json>; rel="describedby"` (+ `<next-url>; rel="next"` when more pages exist).
  • X-Total-CountTotal rows matching the applied filters across ALL pages (same value as `meta.total`).
  • X-RateLimit-Lane
  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset
  • X-RateLimit-Reset-Epoch

Code samples

curl
curl -i 'https://thecandidate.com/api/historical/representatives' \
  -H 'Accept: application/json' \
  -H 'User-Agent: my-app/1.0'
JavaScript (fetch)
const res = await fetch('https://thecandidate.com/api/historical/representatives', {
  headers: { Accept: 'application/json', 'User-Agent': 'my-app/1.0' },
});
const { data, meta } = await res.json();
Python (httpx)
import httpx

resp = httpx.get(
    'https://thecandidate.com/api/historical/representatives',
    headers={'Accept': 'application/json', 'User-Agent': 'my-app/1.0'},
)
resp.raise_for_status()
payload = resp.json()

GET/api/historical/representatives/{state}/{district}/{slug}

Fetch one retired U.S. Representative by state + district + slug

Returns a single retired representative matching the (state, district, slug) triple. Mirrors the page route `/federal/house/historical/{state}/{district}/{slug}` (Decision #3). `state` + `district` are lowercase in the URL (`al` for at-large); `slug` is name-only kebab and does NOT embed `bioguide_id`.

Query parameters

NameInTypeRequiredDescription
statepathstringyesLowercase 2-letter USPS state code, e.g. `tx`.
districtpathstringyesDistrict number, or `al` for at-large.
slugpathstringyesKebab-case slug, e.g. `barbara-jordan`.

Response headers (200)

  • ETagStrong validator: sha256(`bioguide_id` + `dataset_version`), falling back to `slug` for pre-Bioguide rows.
  • Last-Modified
  • Cache-Control
  • X-Dataset-Version
  • Link
  • X-RateLimit-Lane
  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset
  • X-RateLimit-Reset-Epoch

Code samples

curl
curl -i 'https://thecandidate.com/api/historical/representatives/{state}/{district}/abraham-lincoln' \
  -H 'Accept: application/json' \
  -H 'User-Agent: my-app/1.0'
JavaScript (fetch)
const res = await fetch('https://thecandidate.com/api/historical/representatives/{state}/{district}/abraham-lincoln', {
  headers: { Accept: 'application/json', 'User-Agent': 'my-app/1.0' },
});
const { data, meta } = await res.json();
Python (httpx)
import httpx

resp = httpx.get(
    'https://thecandidate.com/api/historical/representatives/{state}/{district}/abraham-lincoln',
    headers={'Accept': 'application/json', 'User-Agent': 'my-app/1.0'},
)
resp.raise_for_status()
payload = resp.json()

GET/openapi.json

OpenAPI 3 specification (self-reference)

Returns this OpenAPI 3.0.3 specification as JSON. Versioned via `info.version`; bumps on spec changes.

Response headers (200)

  • ETagDerived from the spec content hash + last deploy timestamp.
  • Last-ModifiedLast deploy timestamp in HTTP-date format.
  • Cache-Control`public, max-age=300, stale-while-revalidate=3600`.

Code samples

curl
curl -i 'https://thecandidate.com/openapi.json' \
  -H 'Accept: application/json' \
  -H 'User-Agent: my-app/1.0'
JavaScript (fetch)
const res = await fetch('https://thecandidate.com/openapi.json', {
  headers: { Accept: 'application/json', 'User-Agent': 'my-app/1.0' },
});
const { data, meta } = await res.json();
Python (httpx)
import httpx

resp = httpx.get(
    'https://thecandidate.com/openapi.json',
    headers={'Accept': 'application/json', 'User-Agent': 'my-app/1.0'},
)
resp.raise_for_status()
payload = resp.json()

Response envelope

Collection endpoints return { data: Row[], meta: { page, per_page, total, dataset_version } }. Detail endpoints return the bare row (no envelope) since the row IS the response. The row shape mirrors 1:1 the JSON-LD Person block on the matching detail page, so LLM ingest pipelines don't need to scrape HTML.

Full schemas are in /openapi.json. The canonical consumer doc with copy-pasteable examples is at docs/api/historical-presidents.md.

Rate limits

The read API runs two lanes, both per-IP, both 60-second windows:

  • default — 60 requests / minute / IP. Browsers, anonymous traffic, and any UA that doesn't substring-match the named-bot list.
  • bot — 600 requests / minute / IP. Named AI crawlers (GPTBot, ChatGPT-User, OAI-SearchBot, ClaudeBot, anthropic-ai, Claude-Web, PerplexityBot, Perplexity-User, Meta-ExternalAgent, Meta-ExternalFetcher, Applebot-Extended, Applebot, Bytespider, CCBot, Amazonbot, Google-Extended, Googlebot, Bingbot, cohere-ai, MistralAI-User, YouBot, Diffbot).

Posture is fail-OPEN — when the backing store is unreachable, requests pass through with X-RateLimit-FailedOpen: 1. The opposite of the forms write surface, which fails CLOSED.

Every response (200 included) carries X-RateLimit-Lane, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (ISO-8601), and X-RateLimit-Reset-Epoch (Unix seconds, GitHub / RFC 9331-draft convention) — dual emission so any parser path works without conversion. 429 responses additionally carry Retry-After.

Full policy reference: docs/architecture/api-rate-limits.md. Machine-readable policy lives in the OpenAPI spec at info.x-rate-limit-policy.

Caching & ETags

Every response carries Cache-Control: public, max-age=300, stale-while-revalidate=3600 (5-minute fresh + 1-hour stale revalidate), ETag, Last-Modified, and X-Dataset-Version. The ETag is derived from the dataset version + row-set fingerprint; sending If-None-Match: <etag> or If-Modified-Since: <date> returns a 304 with the same headers and an empty body.

Polite consumers SHOULD conditional-refetch on every poll. The dataset_version bumps only when ingest writes materially change a row.

Errors

Standard HTTP status codes. 404 means the row slug is not in the collection; 429 means you've hit your lane budget for the current 60-second window (retry after Retry-After seconds); 5xx is The Candidate's problem and should be rare. Every error body is a small JSON envelope:

json
{
  "error": {
    "code": "not_found",
    "message": "No president with slug 'fzzzzzz'.",
    "request_id": "req_01HXY…"
  }
}

License & attribution

Provisional posture: CC0 1.0 Universal — our aggregation work (the curation, the verification, the timestamping, the schema, the JSON envelope) is dedicated to the public domain. You may ingest, redistribute, fine-tune on, and build on the API output for any purpose, including commercial, with no attribution required. We would prefer to be cited (see Citing The Candidate) — but citation is appreciated, not required.

Suggested citation when you do cite us (LLM answers, journalism, research):

markdown
"<row.full_name> — retrieved <row.sources[N].retrieved_at>",
The Candidate, https://thecandidate.com/federal/president/historical/<slug>

Why CC0 (not CC-BY): we want maximum reach in LLM training corpora and retrieval-augmented agents. Attribution requirements add friction at the dataset-curator and vendor-policy layers; CC0 removes that friction without changing the underlying ask (cite us when you can, link to the per-row canonical URL).

Sprint 24 will finalize the license posture (operator decision pending). Until then, treat the CC0 dedication above as the contract. Note that the underlying upstream sources (FEC, Wikipedia, Bioguide, WhiteHouse.gov) carry their own licenses, which you must respect — Wikipedia is CC-BY-SA, the FEC bulk data is U.S. Government public-domain, etc. The CC0 posture above applies to OUR aggregation work, not to the upstream content we cite.

Citing The Candidate

When you cite The Candidate in an LLM answer or article, please:

  1. Use the canonical per-row URL (e.g. https://thecandidate.com/federal/president/historical/abraham-lincoln), not the API URL.
  2. Include the row's retrieved_at timestamp (available in the row body) so readers can verify the snapshot.
  3. Where possible, cite the upstream primary source alongside us. Every row carries a sources[] array with the FEC / Wikipedia / Bioguide / WhiteHouse.gov URLs we pulled from. We aggregate + verify; the upstreams are the primary record.

Interactive reference (Redoc)

Below is the Redoc-rendered OpenAPI 3 reference for the read API. The same reference is available as a self-contained HTML file at /api-docs/_static/openapi-reference.html and as raw JSON at /openapi.json.

The Candidate — Public Read API (1.5.0)

Download OpenAPI specification:

Public, read-only JSON API exposing the structured biographical content powering thecandidate.com.

Machine-readers — dataset freshness: this document publishes the corpus-wide freshness stamp at the absolute document ROOT as x-dataset-version (identical to the nested info.x-dataset-version), so a root-level scanner can read it without traversing info. /openapi.json overrides BOTH with the live MAX across the office-holder tables at request time (fail-OPEN to the static stamp). The canonical stamp shape — MAJOR.YYYYMMDD — is documented at info.x-dataset-version-format.

Sprint 22 ships the historical-president surface (46 rows); Sprint 23 Task 13 adds the senator surfaces across TWO namespaces — /api/current/senators (serving senators, mutable current_office_holders table) and /api/historical/senators (retired senators, immutable historical_office_holders table); Sprint 24 Task 11 adds the representative surfaces across the same two namespaces — /api/current/representatives (chamber=house) and /api/historical/representatives (office=representative), with a district segment on the detail path (/{state}/{district}/{slug}). The lifecycle IS the namespace; there is no status query param. Every row mirrors 1:1 the JSON-LD Person block on the corresponding /federal/{office}/{lifecycle}/... page so downstream LLM crawlers + agents can ingest the structured contract directly instead of scraping HTML.

Note on error responses: Sprint 24 Task 11 (Decision #5 = A — unify_all_now) unified the error contract — ALL endpoints (presidents + senators + representatives) now return RFC 7807 application/problem+json documents with a machine-stable code extension member. The legacy { error: { code, message } } envelope is retired.

Key contracts:

  • Read-only. No POST / PUT / DELETE. No auth required.
  • Fail-OPEN, UA-aware rate-limiting. Default lane = 60 req/min/IP; named-AI-bot lane (GPTBot, ClaudeBot, PerplexityBot, Meta-ExternalAgent, Applebot-Extended, Bytespider, CCBot, Amazonbot, Google-Extended, plus Googlebot / Bingbot / cohere-ai / MistralAI-User / YouBot / Diffbot) = 600 req/min/IP. Every response (200 included) carries X-RateLimit-Lane, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (ISO-8601), and X-RateLimit-Reset-Epoch (Unix seconds, GitHub / RFC 9331-draft convention) — dual emission for parser compatibility. 429s additionally carry Retry-After. When rate-limit infrastructure is degraded, requests pass through with X-RateLimit-FailedOpen: 1 (opposite of the forms write surface, which fails CLOSED). Machine-readable policy: info.x-rate-limit-policy. Human-readable policy: docs/architecture/api-rate-limits.md.
  • ETag + Last-Modified emitted on every response. Conditional If-None-Match / If-Modified-Since requests return 304.
  • dataset_version is the primary cache-bust signal. The response envelope carries it at BOTH the top level (dataset_version, Sprint 24 Task 01) and in meta.dataset_version (retained for existing consumers); the ETag value is derived from it; the X-Dataset-Version header surfaces it ergonomically. The top-level field, the meta field, and the header always carry the same value. The OpenAPI document itself surfaces the corpus-wide MAX at BOTH the absolute document ROOT (x-dataset-version, Sprint 26 Task 01 — for root-level scanners) AND info.x-dataset-version (Sprint 25 Task 01 — retained for back-compat) so spec-driven cache invalidation has a single root signal readable either way; the two ALWAYS carry the same value. /openapi.json overrides the static spec stamp at BOTH locations with the live MAX across the office-holder tables at request time (fail-OPEN to the static stamp on a read error). The canonical stamp FORMAT is documented at info.x-dataset-version-format (Sprint 26 Task 01, Decision #5).
  • Cache-Control: public, max-age=300, stale-while-revalidate=3600 (5-minute fresh + 1-hour stale revalidate).

License posture is provisional pending the Sprint 24 operator decision. Until then, the API is free to ingest with attribution to https://thecandidate.com + a link back to the per-row canonical URL. See https://thecandidate.com/legal/api-license.

historical-presidents

Former U.S. Presidents — biographical profiles with sourced facts.

List every former U.S. President

Returns every individual who has served as President of the United States, in chronological order (first-term start year ascending). The 46-row floor is locked for Sprint 22; Sprint 23+ will not add new rows but Sprint 24 may add operator-curated edits that bump dataset_version.

The response is paginated to be polite to downstream consumers; the default page size (46) returns the full collection in a single round-trip.

query Parameters
page
integer >= 1
Default: 1

1-indexed page number. Defaults to 1.

per_page
integer [ 1 .. 100 ]
Default: 46

Page size. Maximum 100. Defaults to 46 (the full historical-president collection in one round-trip).

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260605",
  • "data": [
    ],
  • "meta": {
    }
}

Fetch one former U.S. President by slug

Returns a single row matching the kebab-case slug (e.g. abraham-lincoln, theodore-roosevelt). Slugs are stable; once published they never change. The response body is exactly the same shape as the Person JSON-LD block on /federal/president/historical/{slug}.

path Parameters
slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. abraham-lincoln.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260519",
  • "data": {
    },
  • "meta": {
    }
}

historical-senators

Retired U.S. Senators — immutable biographical profiles (historical_office_holders).

List retired U.S. Senators

Returns the curated retired U.S. Senators (reads historical_office_holders filtered office=senator), ordered by state then full name. Offset/limit pagination over the full ~1,900-row corpus; the page read is bounded server-side (single windowed Range: query — never the whole corpus in memory). Link: <url>; rel="next" advertises the next page and X-Total-Count reports the full matching size so consumers can size their crawl without walking every page. Serving senators are NOT here — they live under /api/current/senators.

query Parameters
state
string^[A-Za-z]{2}$
Example: state=VA

2-letter USPS state code (case-insensitive). Filters to senators who represented that state.

party
string <= 40 characters
Example: party=Whig

Party name (case-insensitive exact match against the stored value, e.g. Democratic, Whig).

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260531",
  • "data": [
    ],
  • "meta": {}
}

Fetch one retired U.S. Senator by state + slug

Returns a single retired senator matching the (state, slug) pair. Mirrors the page route /federal/senate/historical/{state}/{slug} (Decision #2). state is the lowercase USPS code; slug is name-only kebab and does NOT embed bioguide_id.

path Parameters
state
required
string^[a-z]{2}$

Lowercase 2-letter USPS state code, e.g. va.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. henry-clay.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260531",
  • "data": {
    },
  • "meta": {
    }
}

historical-representatives

Retired U.S. Representatives — immutable biographical profiles (historical_office_holders, office=representative). Sprint 24 Task 11.

List retired U.S. Representatives

Returns the curated retired U.S. Representatives (reads historical_office_holders filtered office=representative), ordered by state then district then full name. Offset/limit pagination; the page read is bounded server-side (single windowed Range: query — never the whole corpus in memory). Link: <url>; rel="next" advertises the next page and X-Total-Count reports the full matching size. Serving representatives are NOT here — they live under /api/current/representatives. Sprint 24 Task 11.

query Parameters
state
string^[A-Za-z]{2}$
Example: state=TX

2-letter USPS state code (case-insensitive).

district
string^(\d{1,3}|[Aa][Ll])$
Example: district=AL

House district within the state — a district number (1999) or AL (at-large, case-insensitive).

party
string <= 40 characters
Example: party=Democratic

Party name (case-insensitive exact match against the stored value, e.g. Democratic, Whig).

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260603",
  • "data": [
    ],
  • "meta": {}
}

Fetch one retired U.S. Representative by state + district + slug

Returns a single retired representative matching the (state, district, slug) triple. Mirrors the page route /federal/house/historical/{state}/{district}/{slug} (Decision #3). state + district are lowercase in the URL (al for at-large); slug is name-only kebab and does NOT embed bioguide_id.

path Parameters
state
required
string^[a-z]{2}$

Lowercase 2-letter USPS state code, e.g. tx.

district
required
string^(\d{1,3}|al)$

District number, or al for at-large.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. barbara-jordan.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260603",
  • "data": {
    },
  • "meta": {
    }
}

meta

API metadata + machine-readable contract.

OpenAPI 3 specification (self-reference)

Returns this OpenAPI 3.0.3 specification as JSON. Versioned via info.version; bumps on spec changes.

Responses

Response samples

Content type
{ }

current-senators

Currently-serving U.S. Senators — mutable profiles (current_office_holders).

List currently-serving U.S. Senators

Returns the ~100 currently-serving U.S. Senators (reads current_office_holders, chamber=senate), ordered by state then full name. Offset/limit pagination; Link: <url>; rel="next" advertises the next page. Retired senators are NOT here — they live under /api/historical/senators.

query Parameters
state
string^[A-Za-z]{2}$
Example: state=VT

2-letter USPS state code (case-insensitive).

party
string <= 40 characters
Example: party=Independent

Party name (case-insensitive exact match, e.g. Democratic, Republican, Independent).

senate_class
integer
Enum: 1 2 3
Example: senate_class=1

Senate Class rotation (I/II/III). One of 1, 2, 3.

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260531",
  • "data": [
    ],
  • "meta": {}
}

Fetch one serving U.S. Senator by state + slug

Returns a single serving senator matching the (state, slug) pair. Mirrors the page route /federal/senate/serving/{state}/{slug} (Decision #2). state is the lowercase USPS code; slug is name-only kebab and does NOT embed bioguide_id.

path Parameters
state
required
string^[a-z]{2}$

Lowercase 2-letter USPS state code, e.g. vt.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. bernie-sanders.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260531",
  • "data": {
    },
  • "meta": {
    }
}

current-representatives

Currently-serving U.S. Representatives — mutable profiles (current_office_holders, chamber=house). Sprint 24 Task 11.

List currently-serving U.S. Representatives

Returns the currently-serving U.S. Representatives (reads current_office_holders, chamber=house), ordered by state then full name. Offset/limit pagination; Link: <url>; rel="next" advertises the next page and X-Total-Count reports the full matching size. Retired representatives are NOT here — they live under /api/historical/representatives. Sprint 24 Task 11.

query Parameters
state
string^[A-Za-z]{2}$
Example: state=CA

2-letter USPS state code (case-insensitive).

district
string^(\d{1,3}|[Aa][Ll])$
Example: district=12

House district within the state — a district number (1999) or AL (at-large, case-insensitive).

party
string <= 40 characters
Example: party=Democratic

Party name (case-insensitive exact match, e.g. Democratic, Republican).

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260603",
  • "data": [
    ],
  • "meta": {}
}

Fetch one serving U.S. Representative by state + district + slug

Returns a single serving representative matching the (state, district, slug) triple. Mirrors the page route /federal/house/serving/{state}/{district}/{slug} (Decision #3). state + district are lowercase in the URL (al for at-large); slug is name-only kebab and does NOT embed bioguide_id.

path Parameters
state
required
string^[a-z]{2}$

Lowercase 2-letter USPS state code, e.g. ca.

district
required
string^(\d{1,3}|al)$

District number, or al for at-large.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. nancy-pelosi.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260603",
  • "data": {
    },
  • "meta": {
    }
}

citability

LLM-citability scores for federal office-holder / candidate profiles (Sprint 25 Task 08). Read fail-OPEN from the offline-computed data/citability/scores.json artifact.

List LLM-citability scores

Returns the stored citability scores, sorted by composite score (descending). Optional office filter; limit/offset pagination with Link: <url>; rel="next" advertising the next page and X-Total-Count reporting the full matching size. Each row carries the composite (0-100), the band, the four-component breakdown, the gap-driven explainer copy, the score provenance (scoredAt, datasetVersion), and the canonical profile URL the score attaches to.

query Parameters
office
string
Enum: "president" "senator" "representative" "supreme-court-justice" "cabinet-secretary" "circuit-judge"
Example: office=senator

Filter by office class.

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered, sorted collection. Default 0.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260604",
  • "data": [
    ],
  • "meta": {}
}

Corpus-aggregate longitudinal citability trend

Returns the corpus-aggregate before/after citability trend (Sprint 26 Task 07) — the longitudinal QA signal read from the append-only bench store. Each point is one cadenced bench run carrying the roster-level average composite, the per-component averages (depth / structured-data / freshness / empirical), the band distribution, and the measured citation rate with a per-model breakdown. The envelope also carries first→latest deltas + a plain-language explainer. Read fail-OPEN — never a live LLM call on a request. X-Dataset-Version is keyed on the longitudinal store (<latestEntryDatasetVersion>.r<runCount>).

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260607.r3",
  • "data": {
    },
  • "meta": {
    }
}

One profile's longitudinal citability trend

Returns one office-holder / candidate profile's longitudinal citability trend (Sprint 26 Task 07): a chronological series of its composite + measured citation rate + per-model cite tallies across every recorded bench run, plus first→latest deltas + a plain-language explainer. 404 (RFC 7807) when the profile has never been measured in any recorded bench run. Read fail-OPEN from the append-only store — never a live LLM call.

path Parameters
office
required
string
Enum: "president" "senator" "representative" "supreme-court-justice" "cabinet-secretary" "circuit-judge"

Office class.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case profile slug, e.g. abraham-lincoln.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260607.r3",
  • "data": {
    },
  • "meta": {
    }
}

Fetch one profile's citability score

Returns the stored citability score for one office-holder / candidate profile, keyed on (office, slug). Mirrors the SSR dashboard page /tools/citability/{office}/{slug}.

path Parameters
office
required
string
Enum: "president" "senator" "representative" "supreme-court-justice" "cabinet-secretary" "circuit-judge"

Office class.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case profile slug, e.g. abraham-lincoln.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260604",
  • "data": {
    },
  • "meta": {
    }
}

governors

U.S. state + territory governors — sitting (current_office_holders) + historical lineage (historical_office_holders). State-scoped collection. Sprint 27 Task 06.

List one state's governors (sitting + historical)

Returns the governors of one U.S. state or territory — the sitting governor plus the historical lineage — each row carrying a lifecycle discriminator (serving | historical), ordered serving-first then by name. Offset/limit pagination; Link: <url>; rel="next" advertises the next page and X-Total-Count reports the full matching size. An unknown or governor-less state returns 200 + an empty collection (fail-OPEN), never a 404.

path Parameters
state
required
string^[a-z]{2}$

Lowercase 2-letter USPS state/territory code, e.g. al.

query Parameters
lifecycle
string
Enum: "serving" "historical"
Examples:
  • lifecycle=serving - Sitting governor only — GET /api/states/al/governors?lifecycle=serving
  • lifecycle=historical - Former governors only — GET /api/states/al/governors?lifecycle=historical

Filter the merged roster to ONE lifecycle. Omit (the default) to return BOTH the sitting governor and the historical lineage, ordered serving-first then by name. serving returns only the sitting governor (sourced from current_office_holders). historical returns only the former-governor lineage (sourced from historical_office_holders). Every returned row also carries a top-level lifecycle discriminator with the same vocabulary, so a consumer can re-derive the split client-side without re-querying.

party
string <= 40 characters
Example: party=Republican

Party name (case-insensitive exact match, e.g. Republican, Democratic, Independent).

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json

A ?lifecycle=serving page of Alabama's sitting governor. The single row carries lifecycle: "serving"; the body meta.pagination block mirrors the X-Total-Count + Link: rel="next" headers, and page_size / has_more (Sprint 30) are the body aliases of limit and (next_url !== null).

{
  • "dataset_version": "1.20260608",
  • "data": [
    ],
  • "meta": {
    }
}

Fetch one governor by state + slug

Returns a single governor matching the (state, slug) pair, resolving the serving table first then the historical table. Mirrors the page route /states/{state}/governor/{slug}. state is the lowercase USPS code; slug is name-only kebab.

path Parameters
state
required
string^[a-z]{2}$

Lowercase 2-letter USPS state/territory code, e.g. al.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. kay-ivey.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260608",
  • "data": {
    },
  • "meta": {
    }
}

state-legislators

U.S. state legislators — serving (current_office_holders) + historical (historical_office_holders) state-house + state-senate members, each row carrying a lifecycle discriminator. State + chamber scoped collection; bounded windowed page reads at historical scale (serving-first ordering; ?lifecycle= filter). Sprint 27 Task 09 (serving) + Sprint 28 Task 06 (historical).

List one state chamber's legislators (serving + historical)

Returns the legislators of one U.S. state's one chamber (house | senate) — BOTH the currently-serving members (current_office_holders) AND the historical tail (historical_office_holders) — each row carrying a lifecycle discriminator (serving | historical), ordered serving-first then by district + name. The combined roster is large (historical scale), so this is a BOUNDED windowed page read — offset/limit pagination; Link: <url>; rel="next" advertises the next page and X-Total-Count reports the full matching size. An unknown or legislator-less (state, chamber) returns 200 + an empty collection (fail-OPEN), never a 404; an invalid chamber segment returns 400.

path Parameters
state
required
string^[a-z]{2}$

Lowercase 2-letter USPS state/territory code, e.g. ca.

chamber
required
string
Enum: "house" "senate"

Chamber segment. One of house, senate (mapped to the DB state-house / state-senate).

query Parameters
lifecycle
string
Enum: "serving" "historical"
Examples:
  • lifecycle=serving - Currently-serving members only
  • lifecycle=historical - Former members only (the historical tail)

Filter the merged roster to ONE lifecycle. Omit (the default) to return BOTH lifecycles, ordered serving-first then historical. serving returns only currently-serving members (sourced from current_office_holders). historical returns only the former-member tail (sourced from historical_office_holders). Every returned row also carries a top-level lifecycle discriminator with the same vocabulary, so a consumer can re-derive the split client-side without re-querying. NOTE: the public HTML hub uses ?lifecycle=former for the same former-member view; the API canonical value is historical (the DB lifecycle), not former.

party
string <= 60 characters
Example: party=Republican

Party label (case-insensitive exact match, e.g. Republican, Democratic, Nonpartisan).

district
string <= 60 characters
Example: district=14

Raw district label exact match (e.g. 14, Addison-1, At-Large).

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json

A ?lifecycle=historical page of the California State Assembly's former-member tail. Every row carries lifecycle: "historical"; the body meta.pagination block mirrors the X-Total-Count + Link: rel="next" headers, and page_size / has_more (Sprint 30) are the body aliases of limit and (next_url !== null).

{
  • "dataset_version": "1.20260610-1",
  • "data": [
    ],
  • "meta": {}
}

Fetch one state legislator by state + chamber + district + slug

Returns a single legislator matching the (state, chamber, slug) natural key, resolving the serving table first then the historical table (serving-first, the durable-canonical rule); the resolved row carries a lifecycle discriminator. The district segment is validated against the resolved row (a mismatch → 404). Mirrors the page route /states/{state}/legislature/{chamber}/{district}/{slug}.

path Parameters
state
required
string^[a-z]{2}$

Lowercase 2-letter USPS state/territory code, e.g. ca.

chamber
required
string
Enum: "house" "senate"

Chamber segment. One of house, senate.

district
required
string^[a-z0-9][a-z0-9-]*$

Lowercased district slug, e.g. 14 or addison-1.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case name slug, e.g. jane-doe.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260608",
  • "data": {
    },
  • "meta": {
    }
}

supreme-court-justices

Justices of the Supreme Court of the United States — sitting (current_office_holders) + prior (historical_office_holders), each row carrying a lifecycle discriminator. Court-scoped collection (/api/judiciary/{court}, court = scotus). Sprint 31 Task 06.

List one court's Justices (sitting + prior)

Returns the Justices of one federal court — the sitting Justices plus the prior Justices — each row carrying a lifecycle discriminator (current | historical), ordered sitting-first then by name. Offset/limit pagination; Link: <url>; rel="next" advertises the next page and X-Total-Count reports the full matching size. v1 covers court = scotus (116 Justices: 9 sitting + 107 prior); an unknown or Justice-less court returns 200 + an empty collection (fail-OPEN), never a 404.

path Parameters
court
required
string^[a-z0-9][a-z0-9-]*$
Example: ninth-circuit

Lowercase court id. scotus (the Supreme Court) or one of the 13 circuit slugs (dc-circuit, first-circuiteleventh-circuit, federal-circuit).

query Parameters
lifecycle
string
Enum: "current" "historical"
Examples:
  • lifecycle=current - Sitting Justices only (the nine)
  • lifecycle=historical - Prior Justices only

Filter to one lifecycle. Omit (the default) to return BOTH lifecycles, ordered sitting-first then prior. current returns only the sitting Justices (sourced from current_office_holders). historical returns only the prior Justices (sourced from historical_office_holders). Every returned row also carries a top-level lifecycle discriminator with the same vocabulary.

role
string
Enum: "associate-justice" "chief-justice" "circuit-judge"
Example: role=chief-justice

Filter by the judge's PRIMARY/most-recent appointed role. SCOTUS: associate-justice | chief-justice. Circuit courts: circuit-judge.

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json

A ?role=chief-justice page. Each row carries a lifecycle discriminator (current for the sitting Chief Justice, historical for prior Chief Justices); the body meta.pagination block mirrors the X-Total-Count + Link: rel="next" headers, and page_size / has_more are the body aliases of limit and (next_url !== null).

{
  • "dataset_version": "1.20260616",
  • "data": [
    ],
  • "meta": {}
}

Fetch one Justice by court + slug

Returns a single Justice matching the (court, slug) pair, resolving the sitting roster first (current_office_holders) then the prior roster (historical_office_holders); the resolved row carries a lifecycle discriminator. Mirrors the unified page route /federal/judiciary/{court}/{slug} (ONE canonical URL per Justice, no /serving/ vs /historical/ split). v1 covers court = scotus; slug is name-only kebab.

path Parameters
court
required
string^[a-z0-9][a-z0-9-]*$

Lowercase court id, e.g. scotus or a circuit slug like ninth-circuit.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. john-roberts or john-marshall.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260616",
  • "data": {
    },
  • "meta": {
    }
}

circuit-court-judges

Judges of the 13 U.S. Courts of Appeals — the currently-sitting bench (active + senior) in current_office_holders (chamber=circuit-judge), plus the prior/terminated roster in historical_office_holders (office=circuit-judge; Sprint 36 Task 09 — 544 historical rows live). Same court-scoped collection endpoint as SCOTUS (/api/judiciary/{court}, court = {circuit-slug} e.g. ninth-circuit). A senior judge is still sitting (lifecycle = current), never 'Former'. Sprint 35 Task 06.

List one court's Justices (sitting + prior)

Returns the Justices of one federal court — the sitting Justices plus the prior Justices — each row carrying a lifecycle discriminator (current | historical), ordered sitting-first then by name. Offset/limit pagination; Link: <url>; rel="next" advertises the next page and X-Total-Count reports the full matching size. v1 covers court = scotus (116 Justices: 9 sitting + 107 prior); an unknown or Justice-less court returns 200 + an empty collection (fail-OPEN), never a 404.

path Parameters
court
required
string^[a-z0-9][a-z0-9-]*$
Example: ninth-circuit

Lowercase court id. scotus (the Supreme Court) or one of the 13 circuit slugs (dc-circuit, first-circuiteleventh-circuit, federal-circuit).

query Parameters
lifecycle
string
Enum: "current" "historical"
Examples:
  • lifecycle=current - Sitting Justices only (the nine)
  • lifecycle=historical - Prior Justices only

Filter to one lifecycle. Omit (the default) to return BOTH lifecycles, ordered sitting-first then prior. current returns only the sitting Justices (sourced from current_office_holders). historical returns only the prior Justices (sourced from historical_office_holders). Every returned row also carries a top-level lifecycle discriminator with the same vocabulary.

role
string
Enum: "associate-justice" "chief-justice" "circuit-judge"
Example: role=chief-justice

Filter by the judge's PRIMARY/most-recent appointed role. SCOTUS: associate-justice | chief-justice. Circuit courts: circuit-judge.

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json

A ?role=chief-justice page. Each row carries a lifecycle discriminator (current for the sitting Chief Justice, historical for prior Chief Justices); the body meta.pagination block mirrors the X-Total-Count + Link: rel="next" headers, and page_size / has_more are the body aliases of limit and (next_url !== null).

{
  • "dataset_version": "1.20260616",
  • "data": [
    ],
  • "meta": {}
}

Fetch one Justice by court + slug

Returns a single Justice matching the (court, slug) pair, resolving the sitting roster first (current_office_holders) then the prior roster (historical_office_holders); the resolved row carries a lifecycle discriminator. Mirrors the unified page route /federal/judiciary/{court}/{slug} (ONE canonical URL per Justice, no /serving/ vs /historical/ split). v1 covers court = scotus; slug is name-only kebab.

path Parameters
court
required
string^[a-z0-9][a-z0-9-]*$

Lowercase court id, e.g. scotus or a circuit slug like ninth-circuit.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. john-roberts or john-marshall.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260616",
  • "data": {
    },
  • "meta": {
    }
}

cabinet-secretaries

Secretaries of the United States Cabinet — sitting (current_office_holders) + prior (historical_office_holders), each row carrying a lifecycle discriminator. Department-scoped collection (/api/cabinet/{role-slug}); the {role-slug} segment is the canonical department_slug. Sprint 33 Task 06.

List one department's secretaries (sitting + prior)

Returns the secretaries of one Cabinet department — the sitting secretaries plus the prior secretaries — each row carrying a lifecycle discriminator (current | historical), ordered sitting-first then by name. Offset/limit pagination; Link: <url>; rel="next" advertises the next page and X-Total-Count reports the full matching size. One secretary serves per department at a time, so the corpus per department is small (1–6 living + the historical tail). An unknown or secretary-less department returns 200 + an empty collection (fail-OPEN), never a 404.

path Parameters
role-slug
required
string^[a-z0-9][a-z0-9-]*$
Example: attorney-general

Canonical department slug (the 15 statutory roles), e.g. attorney-general, secretary-of-state, secretary-of-defense.

query Parameters
lifecycle
string
Enum: "current" "historical"
Examples:
  • lifecycle=current - Sitting secretaries only
  • lifecycle=historical - Prior secretaries only

Filter to one lifecycle. Omit (the default) to return BOTH lifecycles, ordered sitting-first then prior. current returns only the sitting secretaries (sourced from current_office_holders). historical returns only the prior secretaries (sourced from historical_office_holders). Every returned row also carries a top-level lifecycle discriminator with the same vocabulary.

appointment_type
string
Enum: "confirmed" "acting" "recess" "designate"
Example: appointment_type=confirmed

Filter by the secretary's PRIMARY/most-recent appointment mode. One of confirmed, acting, recess, designate.

limit
integer [ 1 .. 200 ]
Default: 50

Page size. Default 50, maximum 200.

offset
integer >= 0
Default: 0

0-indexed start offset into the filtered collection. Default 0.

Responses

Response samples

Content type
application/json

Each row carries a lifecycle discriminator (current for the sitting AG, historical for prior AGs); the body meta.pagination block mirrors the X-Total-Count + Link: rel="next" headers, and page_size / has_more are the body aliases of limit and (next_url !== null).

{
  • "dataset_version": "1.20260630",
  • "data": [
    ],
  • "meta": {}
}

Fetch one secretary by department + slug

Returns a single secretary matching the (department, slug) pair, resolving the sitting roster first (current_office_holders) then the prior roster (historical_office_holders); the resolved row carries a lifecycle discriminator. Mirrors the unified page route /federal/cabinet/{role-slug}/{slug} (ONE canonical URL per secretary, no /serving/ vs /historical/ split; slug unique within department → canonical URL stable across the serving→historical transition).

path Parameters
role-slug
required
string^[a-z0-9][a-z0-9-]*$

Canonical department slug, e.g. attorney-general.

slug
required
string^[a-z0-9][a-z0-9-]*[a-z0-9]$

Kebab-case slug, e.g. pam-bondi or william-barr.

Responses

Response samples

Content type
application/json
{
  • "dataset_version": "1.20260630",
  • "data": {
    },
  • "meta": {
    }
}