FOIA ConsoleBorn Between 2 Generals Open the console →

The surface

A small JSON surface. The interesting part is the status codes.

A JSON API over a single SQLite file. What is worth reading here is not the shape of the payloads — it is which failures are given their own code and their own guidance, because those are the places the system had a choice about whether to be honest.

Endpoints

MethodPathScopePurpose
GET/api/agenciesreadAll agencies, each flagged sendable
GET/api/agencies/:idreadFull dispatch metadata, warnings, appeal route
POST/api/deadline/previewreadCompute a deadline without creating anything
POST/api/requestswriteCreate a draft — 422 if the body is subject to no records law
GET/api/requestsreadEvery request with its computed status
GET/api/requests/:idreadOne request, its events, tolls and signals
POST/api/requests/:id/sendsendSend — or 409 with the portal URL if not emailable
POST/api/requests/:id/mark-submittedwriteRecord a portal submission and start the clock
POST/api/requests/:id/eventswriteRecord acknowledgment, extension, denial, response
POST/api/requests/:id/tollswriteStart a toll — clarification is limited to one, per statute
POST/api/requests/:id/tolls/endwriteEnd the open toll; the clock restarts
GET/api/requests/:id/escalationreadThe right letter for where the request stands
GET/api/requests/:id/signalsreadOpen signals on one request
GET/api/signalsreadEvery open signal, worst first, plus the pressure summary
POST/api/signals/:id/confirmwriteA person decides the signal was what it looked like — determinationOn sets the effective date
POST/api/signals/:id/dismisswriteA person decides it was not
GET/api/dashboardreadEverything needing attention, worst first
POST/api/inboundshared secretAgency reply webhook — x-inbound-secret header
GET/healthnoneLiveness

Every route except /api/inbound and /health requires Authorization: Bearer <token> with the scope named. There is no anonymous access and no way to switch it off except on loopback. A missing or unknown token gets 401 with a WWW-Authenticate header and the exact command that mints one. A valid token with the wrong scope gets 403 naming both the scopes it has and the scope it needed.

The status codes that carry meaning

409

This agency does not accept email requestsReturned with the portal URL, the postal address where one is published, and the full request text. The request is marked awaiting-manual-submission. Nothing was sent, and the response does not pretend anything was.

422

This body is not subject to a public records lawThe Administrative Office of the U.S. Courts and U.S. Probation are excluded from FOIA by 5 U.S.C. § 551(1). The draft is not created; the response names the workaround — request the same records from a body that holds copies.

422

A second clarification toll§ 552(a)(6)(A)(ii)(I) allows one. The API refuses the second rather than quietly extending a deadline the agency is not entitled to.

429

Send rate limit reachedPer token, rolling hour, default thirty. Carries Retry-After, and is written to the audit trail.

202

Inbound, unmatchedA reply that carries no request token, or one whose token does not resolve, is acknowledged rather than errored, and is explicitly reported as matched: false with the reason. The webhook succeeded; there was simply nothing to attach the reply to.

Escalation

The right letter for where the request actually stands.

Not a template picker. The escalation endpoint reads the computed state of the request — pending, overdue, denied, responded — and returns the letter that state calls for, with the citations that state supports.

GET /api/requests/:id/escalation assesses the request first, then chooses. The assessment is the same pure function the deadline engine exposes, so the letter and the dashboard can never disagree about what day it is.

  • Overdue, federal. The overdue letter, asserting exhaustion under § 552(a)(6)(C)(i) and the search-fee prohibition under § 552(a)(4)(A)(viii), and asking for the unusual-circumstances notice § 552(a)(6)(B) requires if that is what is being claimed.
  • Overdue, state. The overdue notice, then the jurisdiction's own appeal or oversight route — which for several states is not an appeal at all but a court, an ombudsman, or a binding-opinion office.
  • Denied. The appeal, with the deadline for that specific agency and the arguments the record supports: foreseeable-harm under § 552(a)(8)(A) where exemptions are cited without identified harm, segregability under the closing sentence of § 552(b), and the agency's burden under § 552(a)(4)(B).
  • No statutory deadline. A follow-up, which is what exists when there is no deadline to assert. It is labelled as a practice follow-up, not a demand.

Letters are drafts

They are generated from the record and the statute, and they are yours to read before they go anywhere. Nothing on this site is legal advice; the citations were verified in August 2026 and statutes and agency regulations change. See limits.

The daily digest

src/digest.js renders the dashboard as text and can mail it. Run it on a schedule:

0 13 * * *  cd /srv/foia && node src/digest.js

Deadlines are the whole point of this system, and a deadline you have to remember to check is a deadline you will miss.

The console

A browser front end for the same API: it drafts the request, then hands it to a server you point it at. Connect it to your own host, or to 127.0.0.1 while you are testing.

How this works

0:52 · narrated · captioned · served from this site, and nothing is sent anywhere when you press play. Prefer to read it? The transcript is below.

Transcript
AtSectionWhat it says
0:02The shapeA small JSON interface over one SQLite file. What is worth reading is not the payloads, but the status codes, because those are where it chose to be honest.
0:13Refusing to pretendA portal-only agency returns a conflict, carrying the portal address and your full request text. Nothing was sent, and the response does not pretend.
0:22Refusing to createA body subject to no records law is refused outright. So is a second clarification toll, because the statute allows the agency exactly one of those.
0:31Refusing to floodA token over its hourly send limit is refused, with a retry header. That is a leaked-credential guard, and the attempt goes to the audit trail.
0:41EscalationAsk for the escalation letter and it reads the computed state of the request first, so the letter and the dashboard cannot disagree about the date.