Alaska campaign financial disclosure data from the Alaska Public Offices Commission.
This scrapes the CSV files from the APOC website once a day and uploads them to this repo's releases.
https://nickcrews.github.io/apoc-data
Who gave money to which candidates, and what they spent it on — click to filter, no SQL required. Pick a donor and every other chart, the totals, and the table of matching records narrow to them; drag across the timeline to pick a date range; flip between money in and money out.
It's a SQLRooms app running DuckDB-WASM: the data is downloaded into your browser and queried there, so nothing you do is sent anywhere. There's a SQL editor if you want one, and an "Ask an AI" panel that hands you a copy-pasteable prompt (schema and data URLs included) for questions the dashboard doesn't answer.
https://nickcrews.github.io/apoc-data/llms.txt
Hand that URL to any agent that can fetch a URL — Claude Code, Cursor, Codex, a
ChatGPT with browsing on — and it has everything it needs: the parquet URLs, the
full schema with row counts and date ranges, and the caveats that otherwise
produce a confident wrong answer (names spelled six different ways, amended
reports double-counting, report_year not being the year the money moved).
It doesn't repeat the schema — that's in
manifest.json,
which it links, and any reader that can run the query in it can equally run
DESCRIBE. What it carries is the part that lives nowhere else.
For tools that can't fetch a URL — ChatGPT and Gemini, mostly — the "Ask an AI" button on the site copies the same guidance as a prompt, with the full schema spelled out inline and your question filled in, and points you at the CSV zip to attach.
There's also an AGENTS.md for agents working on this repo rather than on the data.
You can download the daily-scraped CSVs from the GitHub releases (this is what most users want).
Browse from this repo's releases. The latest release is also hosted on the site, as both CSV and parquet. Every file gets its own tab there, with its size and row count, and the rows themselves: search them, or filter from the distributions in the column headers — eg income.
Every CSV in one archive is at https://nickcrews.github.io/apoc-data/apoc-csvs.zip. That's the one to grab if you're feeding the data to ChatGPT or Gemini, which can't fetch a URL and only see the files you attach to the chat.
Using uv's uvx:
uvx apoc-data release download # downloads all files from the latest release to ./downloads/ folder
uvx apoc-data release download "20260702-125614" --destination mydownloads/ # specify explicitly
uvx apoc-data asset download debt.csv --destination apoc_debt.csv # download a single file
uvx apoc-data release list # see what releases are available
uvx apoc-data asset list --json # see what files are in the latest release, as JSONOr, you can download these CSVs directly using the direct URLs from the releases page using curl, pandas, ibis, whatever!
curl -L https://github.com/NickCrews/apoc-data/releases/latest/download/candidate_registration.csv > candidate_registration.csv # get latest
curl -L https://github.com/NickCrews/apoc-data/releases/download/20240716-025636/candidate_registration.csv > candidate_registration.csv # or a different url pattern for specific releases
# query directly using duckdb
duckdb -c "SELECT count(*) FROM 'https://github.com/NickCrews/apoc-data/releases/latest/download/candidate_registration.csv'"
duckdb -c "SELECT count(*) FROM 'https://github.com/NickCrews/apoc-data/releases/download/20240716-025636/candidate_registration.csv'"The same data is also published as parquet on the GitHub Pages site, with the
strings parsed into real types: "$1,234.00" becomes a DECIMAL(18,2),
"7/16/2024" becomes a DATE, and income.csv goes from 369MB to about 23MB.
DuckDB reads them straight over HTTPS, and only fetches the columns and row
groups your query touches:
duckdb -c "SELECT filer_name, sum(amount) AS raised
FROM 'https://nickcrews.github.io/apoc-data/data/income.parquet'
WHERE report_year = 2024
GROUP BY 1 ORDER BY raised DESC LIMIT 10"data/manifest.json
lists every table with its row count, columns and types, and date range, plus
the release the data came from, whose published_at is when it was scraped.
Note that the GitHub release URLs above don't send CORS headers, so they can't be fetched directly from a browser (eg from duckdb-wasm or a web app). That's why the latest release is also mirrored to GitHub Pages, which does allow cross-origin requests — it's what makes the in-browser explorer possible. eg at https://shell.duckdb.org/:
- this works:
SELECT count(*) FROM 'https://nickcrews.github.io/apoc-data/campaign_form.csv' - this does not:
SELECT count(*) FROM 'https://github.com/NickCrews/apoc-data/releases/latest/download/campaign_form.csv'
We provide a python API too. uv add apoc-data and then
from apoc_data.releases import asset_download, release_download, release_list
release_list() # all releases, newest first, as `Release` objects
release_download(destination="downloads/") # all files from the latest release
asset_download("debt.csv", destination="apoc_debt.csv") # a single fileYou can also scrape fresh data directly from the APOC website
(requires the scrape extra for playwright):
uvx "apoc-data[scrape]" scrape --directory scraped/There is also a python API. Read the source code.
MIT, do as you wish with the data and code!
Create venv and install dev deps:
uv syncscrape:
uv run apoc-data scrape --directory downloads --no-headlessconvert the scraped CSVs to typed parquet (plus a manifest.json describing
them):
uv run apoc-data convert --source scraped/ --destination _site/data/The type of each column is detected rather than hardcoded, so new or renamed
APOC columns keep working. See src/apoc_data/convert/.
The explorer lives in web/ — Vite + React +
SQLRooms over DuckDB-WASM.
cd web
npm install
npm run data # convert ../scraped into web/public/data (once)
npm run devWithout local data, point it at the live site instead:
VITE_DATA_BASE_URL=https://nickcrews.github.io/apoc-data/data/ npm run devscripts/build_site.sh assembles everything that gets published, so the layout
is reproducible outside CI:
scripts/build_site.sh scraped _site_site/
index.html, assets/ the explorer app, which lists every file below
data/*.parquet typed parquet + manifest.json
*.csv, *.csv.zip the raw scrape, at the URLs it has always had
apoc-csvs.zip every CSV in one file, to upload somewhere
llms.txt the schema and caveats, for AI agents
llms.txt and the "Ask an AI" prompt are two renderings of one source,
web/src/aiGuide.ts — edit the caveats there, never in
llms.txt, and both change together. A copy of llms.txt is committed at the
repo root so its wording is reviewable in a diff; refresh it with
cd web && npx tsx ../scripts/gen-llms-txt.tsIt holds no row counts or timestamps, so it only changes when you change it.