Take the files.
Every figure on this site is read from a file, and every one of those files is published here at a URL that does not change. There is no key, no quota and no rate limit. If you want to check the arithmetic, disagree with a count, or build something else on top of it, this is the door — and checking the arithmetic is the point of publishing records at all.
Start at /api
GET /api returns an index of everything below: what each file is, how large it is, how many rows it holds, where it came from and what license it carries. It is the only URL you need to hard-code.
Each dataset is served at two addresses, and the difference matters:
/api/<name>.jsonis stable. It always resolves to the current file and revalidates on every request. Use this in a script./data/<name>.<hash>.jsonis immutable. The hash is of the contents, so that URL will serve the same bytes forever and can be cached for a year. Use this when you want to pin a version, or cite one.
A data refresh moves the second and never the first. Both are listed in the index, as url and immutable_url.
And as a spreadsheet. The JSON files are nested documents — every figure carries the production, page and sha256 it was read from, and that structure is the point of them. It is also the wrong shape if you work in Excel or Sheets, which is most people who want this. So the parts that genuinely are tables are published as CSV too: 6 tables, 11,688 rows, at /api/<name>.csv, listed under the file each one came out of. Every column is named in the index with a line saying what it means. They are RFC 4180 with CRLF endings and a UTF-8 byte-order mark, so Excel opens them correctly on a double-click instead of turning every dash into a question mark.
What is published
Sizes are uncompressed; everything here is served brotli-compressed and is a good deal smaller over the wire. Licenses differ per file and are named on each row in the index — the produced records are public records, the mapping is OpenStreetMap under ODbL, the boundaries are US Census and public domain, and the joins this project computed are CC-BY.
The documents themselves
The produced records ship exactly as the custodian handed them over — not recompressed, not linearized, not re-saved. The sha256 published beside each one is the chain back to that file, and optimizing a PDF for a few hundred kilobytes would break it.
GET /api/records.json is the manifest: one entry per document, with its path under /records/, its sha256, its byte length, which production it arrived in and how its text was extracted. GET /api/doctext.json is that extracted text, one array in the same order.
/records/ is not a browsable directory, and that is deliberate rather than an oversight: one production is withheld from this site because it concerns named individuals rather than the program, its files sit on the same disk, and a directory listing would publish them. The manifest is the index — it is what the site builds itself from, and it cannot list what it does not contain.
One difference between the originals and the extracted text is worth stating plainly, because a silent edit to a record would be worse than no publication at all: personal names are withheld from the extracted text in the 11 documents where they appeared, marked in place, and each affected page says so. The originals under /records/ are untouched and still verify against their published hash.
The two endpoints that are not files
POST /api/tip takes a correction, a records tip or a note. It accepts application/json or an ordinary form encoding, with message required and email and about optional — leave the address blank to stay anonymous. It answers JSON to a JSON request and a redirect to a page otherwise, because the form works without JavaScript and that is the default rather than the fallback.
There is no read side. Nothing sent through it is stored in a database; the notification is the record. If it cannot be delivered you are told so rather than thanked.
POST /api/where takes {"q": "an address"} and answers with the geocoded point, how many cameras and camera sites stand within 250 feet of it, and how far away the nearest one is. It is what the map’s lookup falls back to when a street is not in this window — and it is the only thing on this site that sends anything anywhere, which is why it happens when you choose it and never while you type.
The split is deliberate. The address goes to the US Census geocoder — public domain, no key, no billing, the same agency whose boundaries this site already draws — and a coordinate comes back. The cameras are counted here, against all 115,103 sites this project holds, so what is near an address is worked out on this side and asked of nobody. The cost of choosing Census over a commercial geocoder is that it does not forgive typos; the street list on the map is the forgiving half and answers first.
Rate-limited, cached, and nothing is stored. It sends no CORS headers, so another site cannot call it from your browser — though anyone can call it directly, as they can any endpoint a public page uses, and the honest protection is that the upstream is free: there is no bill here to run up.
Using it
Republish it, chart it, argue with it. Attribution is required by the licenses and is the only thing asked for. If you find an error, say so — a corrected figure is worth more to this project than an uncorrected one is to anybody.
Two things this data is not. It is not a register of deployed cameras: OpenStreetMap is a volunteer survey and records what somebody walked past, so every camera count here is a floor. And it is not the city’s publication — this is an independent project, not affiliated with, endorsed by, or operated by the City of Alpharetta, the Alpharetta Police Department, Flock Safety, or any government agency.