// FEDERATION · MIRROR API

Federation API v1

A read-only machine-readable mirror surface so other accountability platforms can replicate Goblin House's investigative archive. The site itself is a single point of failure; the federation API makes it possible to keep the corpus intact even if goblinhouse.net goes dark.

What's exposed

  • GET /api/v1/federation — capability index
  • GET /api/v1/federation/entities?since=<id>&limit=<n> — paginated entity catalog
  • GET /api/v1/federation/sources?since=<id>&limit=<n> — paginated source list with content hashes for verifiability
  • GET /api/v1/federation/facts?since=<id>&limit=<n> — paginated fact list
  • GET /api/v1/federation/changes?since=<iso8601>&limit=<n> — unified changelog feed for incremental sync
  • GET /attestation — daily Merkle-root attestation page

How it works

All endpoints are public, no auth — the underlying data is already public on the HTML routes; this is a clean machine-readable mirror.

List endpoints use forward-only cursor pagination via a since parameter (numeric id or ISO date). The response includes a next link to keep walking.

Every list response carries an ETag. Clients sending If-None-Match get 304 Not Modified when nothing has changed since their last sync. Use this to throttle re-fetches.

Rate-limited at 120 requests/minute per IP via in-memory token bucket. Bursting is fine; sustained abuse degrades to 429 Retry-After.

Verifiability

Every source row carries a content_hash computed as sha256(coalesce(url, '<no-url>') || '\n' || raw_content).

Daily Merkle-root attestations roll up all source hashes into a single signed root — see /attestation for the latest and the per-day archive. A mirror can verify the corpus by recomputing the Merkle tree from its mirrored sources and comparing the root.

Open data — direct downloads

No key required. These public JSON surfaces are the fastest way to pull structured slices of the corpus:

  • GET /api/entities.json — the tracked-entity catalog (name, slug, type, role)
  • GET /api/v2/health — federation API capability + status
  • GET /api/v2/dossier/<slug> — a single entity's full dossier (facts, sources, connections)

For incremental full-corpus sync use the cursor-paginated /api/v1/federation/* endpoints above. This dataset is indexed by Google Dataset Search via the Dataset schema on this page.

License + citation

Goblin House is an open public-records investigative platform. All data is sourced from government databases, SEC filings, FEC records, court filings, and other public registries.

Mirror and republish freely; please cite Goblin House when you publish derived findings. Send pull requests, corrections, or methodology feedback to [email protected].

Sample call

curl -H 'Accept: application/json' \
  -H 'If-None-Match: "<prev-etag>"' \
  'https://goblinhouse.net/api/v1/federation/entities?since=0&limit=100'

Response shape:

{
  "ok": true,
  "endpoint": "entities",
  "since": 0,
  "limit": 100,
  "count": 100,
  "next": "/api/v1/federation/entities?since=100&limit=100",
  "rows": [
    {
      "id": 1,
      "name": "Donald Trump",
      "slug": "donald-trump",
      "type": "elected_official",
      "updated_at": "2026-05-25T18:32:41.182Z"
    },
    /* … */
  ],
  "meta": {
    "max_updated_at": "2026-05-25T18:32:41.182Z",
    "row_count": 100
  }
}

Headers: response carries ETag (revalidate on next pull); send back as If-None-Match to get 304 when nothing has changed. Cache-Control: public, max-age=300.

Mirror workflow: poll /api/v1/federation/changes?since=<your-last-iso8601> on a cadence (every 15 min is generous; every 6h is plenty for most mirrors), pull each changed entity/source/fact by id, and verify each source's hash against the daily Merkle root.