You can already find this data. What costs you a week is putting it together. This is that week, in about twenty seconds, with every claim resolving to a record.
Seven open-source data connectors · Free · MIT licensed · Every figure dated and sourced
Do not explain the domain to this room. They know what Schedule C is and have FOILed for worse. The pitch is never "here is discretionary funding" — it is "here is discretionary funding, queryable in seconds, by an agent that cites its source and refuses to guess."
Expect to be interrupted with hard questions about provenance and coverage. That is the good outcome. Every slide has notes written so you can answer them.
Runs ~26 minutes. If short on time cut Act 5 (slide 7), never Act 6 (slides 10–12).
If you have the contract ID, open Checkbook. A browser tab beats a query every time. Do not let a tool add a step.
A FOIL request, a filing, a number going in front of a judge. Go to the source document. This is a research accelerator, not a system of record.
Campaign finance to legislative district. 18.3 million rows, refreshed daily, attributable to a district on 0.16% of them. No tool fixes a missing key.
What is left is the part that actually costs you: assembling something nobody has assembled yet, across systems that were never designed to be joined.
Open by conceding, and mean it. This room has watched a decade of civic tech oversell. The fastest way to lose them is to claim the tool is good at everything.
The third card is the strongest and it is our own finding — Crosswalk F in the repo. 18.3M campaign finance rows, district-attributable on 0.16%. An honest "this join does not exist" buys more here than any capability claim.
Then pivot on the last line. Assembly is the thing they actually pay for in staff hours.
Individual awards run $7,750 to $280,000. 54 rows sits well below the tool's row cap, which is the only reason this figure survived — see the next slide but one.
Organization is the Association of Community Employment Programs for the Homeless. Re-verified 2026-07-27. Press R to replay.
Precision on "41 sponsors": that is 39 council members plus the Brooklyn and Queens delegations, which are sponsors in their own right. Do not say "41 council members" — verified against the database 2026-07-27.
Left panel is "by hand," not a web search. This room can already find the PDF. What they lack is the assembly, so that is what the comparison has to be about.
Plant the cap here so slide 6 lands: 54 is safely under it. Act 3's numbers were not.
32 payment records · $1,040,448.79 across DYCD, DOE, SBS, DOT and Small Business Services.
Three checks under budget code 3625 (TAX LEVY ELECTED OFFICIALS):
$107,244.16, $33,000.00, $2,560.00.
"Check Checkbook for FY2026 spending paid to CLOTH."
Run this query
The allocation side shows CLOTH receiving $30,000 from Council Member De La Rosa. The disbursement side shows $142,804.16 under Tax Levy Elected Officials.
Those do not match, and that is not an error. Code 3625 aggregates discretionary
spending from every elected official to that vendor. Other members funded CLOTH too.
So the join is real — allocation in one system, an actual check in another. And getting from there to whose allocation that check discharges is a further step the budget code does not give you. Published, current, and still not joinable to the unit governance actually uses.
🚩 Do not say $107,244.16 is De La Rosa's $30,000 arriving. It is not, and this room will catch it. Say the gap out loud before anyone asks.
The interesting output of this pattern is the mismatch, not the match — allocations with no matching spending, or spending exceeding allocation. Just be precise about which mismatches are findings and which are artifacts of a coarse budget code.
Checkbook figures re-verified live 2026-07-30 from the office IP. The $30,000 award figure is from 2026-07-21 and has not been re-run.
Adopted budgets get amended all year by transparency resolution. Rescissions carry negative amounts.
A transfer appears as a rescind and a designate on the same EIN — so the tool can distinguish "moved" from "taken away."
"Show FY2026 Council rescissions — money de-designated after adoption."
Run this query
1,291 rescissions in FY2026, totaling $212,600,875, across Resolutions 1 through 10.
345 of them carry no council member at all — $176,596,955, or 83% of the money by value. Including −$213,000 to DYCD for Adult Literacy Forward, a program whose name tells you who it serves.
Read from budget.db 2026-07-27, not from the tool. The tool cannot return this.
Ask the room what they would want to know next. That is the demo doing its job — not answering the question, but getting you to the question in ten seconds instead of a week.
🚨 Read these figures off this slide, not off a live query. The tool caps at 500 rows and will report $122,285,988 — $90M low. The next slide is why.
⚠️ Do not assert the missing attribution is a transparency failure. We have not established whether it is a data-extraction artifact or genuinely absent from the source resolution. Offer to check the source PDF via the Legistar crosswalk. This is 345 rows worth $176.6M, not a handful — the scale makes it a better question and a worse thing to guess about.
Volunteer the coverage caveat before they ask: transparency resolutions cover FY2010–FY2024 and FY2026. FY2025 is not in this dataset. For FY2010–FY2013 the org and member text is low-confidence garbled PDF — join on EIN, not on name.
The rescission query ran at its default limit of 50, truncated part-way through the alphabet, and we wrote up eight rows as though they were the set.
Every row verified. The set did not.
The correction told presenters to re-run at an explicit high limit and read the real total off the result.
That remediation is itself the cap.
cap() clamps any limit to 500, so limit: 5000 returns the identical
500 rows. The header reads 500 row(s): — exactly what a complete result of 500 prints.
Reported $122,285,988. Actual $212,600,875.
We wrote the rule and then broke it inside the fix for the last time we broke it.
This material already said "treat exactly N returned against a limit of N as truncation until proven
otherwise." The limit: 500 remediation violates precisely that.
This slide is why they should trust the rest of the deck. Presenting a truncated list as complete, to the organization whose job is catching exactly that, is the one unforced error available in this material. So show them we made it twice and caught it twice.
The general lesson lands harder than the specific one: the dangerous failure is not an error, it is a plausible answer to a question you did not ask.
The same cap() governs four tools, verified in src/db.ts 2026-07-27: search_awards, search_transparency_resolutions, search_capital_projects, get_terms_conditions. Treat any aggregate at or near 500 rows as truncated until checked against the database. Filed as New-York-City-Budget#42.
Worse detail if they want it: resolution is stored as TEXT, so the sort is "1", "10", "2" — the truncation is not chronological and resolutions 3 through 9 vanish entirely.
Two independent catalog searches return six CTE report datasets spanning 2015–16 through 2019–2020. Most recent published 2021-09-22. Nothing newer.
"LL174 requires an annual CTE report. What is the most recent on the portal?"
Run this query
What we established is that the dataset is not on the portal. That is not the same as the report was never produced.
It may well have gone to the Council as a PDF and never been published as open data. Different findings, different remedies — and the second one has a named owner: § 23-507 puts an open data coordinator at every agency.
This is a method, not a finding. Any reporting mandate in the Administrative Code can be checked against the portal the same way. That is a survey your team could run.
Do not skip the right-hand card. "The law says annual, the portal's most recent is 2019–2020, it is 2026" is the line that lands — and the bounded negative immediately after is what makes it survive scrutiny.
Second live example if you want one: NYC's Broadband Adoption by State Senate District dataset (9bjg-n96a) has 26 rows covering SD 10–34 plus 36, all created 2020-06-19. It does not contain District 59 — the file predates current district lines. A digital-equity dataset that cannot answer for a third of the city's senate districts.
This is the act to cut if you are short on time, but it is also the most reusable one for their own staff.
FY2015–FY2027 at EIN level. FY2009–FY2014 is initiatives-only, no per-organization rows and no EINs, deliberately excluded from the award tools. FY2008 is unparsed — blocked source document.
FY2010–FY2024 and FY2026. FY2025 is absent. For FY2010–FY2013 the organization and member text is low-confidence garbled PDF; the financial columns are reliable. Join on EIN, not on name.
council_member matches as a substring and can merge two members —
"Powers" returns Brooks-Powers and Keith Powers summed. Fiscal sponsors merge grantees:
EIN 13-2612524 is a passthrough for dozens of unrelated programs.
The local archive holds 159,666 roll-call attendance entries (1999–2026) and no aye/nay positions at all. The tool now refuses and explains rather than returning an empty list. Indexing was deferred deliberately so the naming gets decided first.
Stating a coverage gap before you are asked buys more credibility with this room than any figure could.
Do not read the grid. Pick the two that match what they asked about.
The council_member substring merge is the sharpest one: FY2026 "Powers" returns 156 awards, $3,405,000 across two different members, and a merged total looks entirely reasonable. In FY2027 only Brooks-Powers remains, so the same query is clean. The fiscal year determines whether it bites. Unfixed — budget repo issue #38.
The vote-positions card is the honest example of "no data" versus "different data than the name implies." Good answer if they ask why a fix is partial.
Search nysenate.gov one session at a time. Every year is a separate search.
Match on title, because print numbers change every session:
A8139 becomes A6323 becomes A5485.
Then notice it moved between three sponsors over fourteen years. Nothing upstream links a bill to its own past lives.
A8139-2011 Hanna · A6323-2013 Nojay ·
A5485-2015 Nojay · A5768-2017 Errigo
Same title, same summary. Four sessions, three sponsors, none became law. In 2016 someone struck the enacting clause.
"Trace this bill across every session it was introduced in."
Run this query
Every one of those bills is public. The pattern is indexed nowhere. "How many times has this been introduced and killed" is a days-long research task in Albany and a ten-second question here.
Keep this to two minutes. It exists because of their name — Reinvent Albany tracks which reforms die repeatedly, and this is the only act in the deck built for one organization.
The line to land: "Same bill, same summary, four sessions, three different sponsors, fourteen years. In 2016 someone struck the enacting clause. None of it became law, and no single page anywhere tells you that."
⚠️ Do not say A3140-2025 "died." Linda Rosenthal's FOIL and Open Meetings review bill (A7933-2023 → A3140-2025) was re-referred to Governmental Operations on 2026-01-07 and is live this session. The dead lineage is the real-property-tax one. Getting that backwards in front of this room would be bad.
⚠️ Statuses move. This is bills in committee; one can be reported out between now and the meeting. Re-verify.
⚠️ Coverage gaps, volunteer them. The local corpus holds 231,194 bills and 1,992 members — but floor transcripts and hearing transcripts are both 0 rows, and NYS law sections number only 137. The transcript tools reach the live API but have no local corpus. Do not demo transcript search. Verified against corpus.db 2026-07-27.
Read the refusal aloud if it comes up. Passing query= instead of term= returns: "Unrecognized parameters are rejected rather than ignored, because silently dropping a filter returns real data that answers a different question." That sentence is the entire thesis of the meeting, written by the tool.
Returns live notices with agency, PIN/EPIN, selection method, hour-precise due date, and a named contact with a phone number.
"What NYC procurement solicitations are currently open, and which agencies posted them?"
Run this query
Without reading the daily PDF.
This act is only worth running live. The whole point is that the notices are current, and reading out a solicitation that closed last week undercuts it entirely.
⏳ The three examples in the July 21 script have expired. FDNY 05726B0010 closed 2026-07-21 14:00. Do not quote them. Run the prompt fresh and read whatever comes back.
This is the act to cut if you are short on time. Never cut Act 6.
If it runs long: get_open_solicitations is very verbose — City Record notices embed raw HTML. Always bound the query.
Checkbook's documented API starts returning 403 to us. The site loads fine in a browser.
A letter to the Comptroller's office. Three asks. No header spoofing, no working around the block.
Two days. A real technical response, their CIO looped in, and an offer of a call.
Access working. The residual block was ours — we were exceeding a rate limit.
We were making roughly forty requests against their origin for every one logical API call, because our client followed redirects. Their fix had already worked. What we were still measuring was our own client getting itself blocked. Fixed on our side in v1.6.0: rate pacing, backoff, no redirect following.
🚫 Do not run part one as a live query. The July 21 version of this act demoed the 403. Access was restored 2026-07-28 and re-verified 2026-07-30 — the query now simply works.
Why this is the right material for this room: they spend their working lives being told a data problem is somebody else's fault. An organization that says "we asked, they fixed it, and the rest was us" establishes that its findings are worth trusting when they do point outward. The correction is the credential.
If the room is technical, tell the near-miss: on 2026-07-27 we nearly reported "your fix did not work" to a CIO who had just done real work for us. The test had egressed through a VPN on a datacenter IP, not the office address they investigated. Chrome from the same address loaded fine, which caught it. One address, two clients, minutes apart: browser 200, command line 403.
Our 2026-07-09 audit made two claims about public data we could not reach. The office answered both, and corrected us on one.
We reported that a prime-expense registration date was not available as a filter or a response column.
It is. The field was there and we missed it.
Not exposed in the API or on the open data portal.
Confirmed by the office. They said they will consider it.
That correction is on this slide for a reason. It is the same standard we are asking you to hold every other number in this deck to, applied to us, by someone else, in public.
Say "the field was there and we missed it" in exactly those words. This is the slide that pays for the whole deck with this audience.
⚠️ Scope, per the 2026-07-27 decision. This slide reports on our error. It does not critique the Comptroller's dashboard — that critique was deliberately removed and is not reinstated by our now having more information. Do not evaluate another office's product in front of a third party.
Also confirmed by the office, if asked: the Checkbook 2.0 migration dropped no documented API fields or domains, and external applications can keep using the API without going through the web interface. They also fixed two drifted NYC Open Data catalog entries we had flagged.
This is the worked example of the rule in our own good-government README: an absence you searched for is weaker evidence than a presence you found. We published an absence and it did not hold.
1 request per second. 20,000 records per call.
Neither appears on the API landing page, in the global parameters table, or on any domain page. We learned both by violating one, getting blocked, and being well-connected enough to get someone on the phone. The office says other users are hitting the same wall for the same reason.
The landing page says 20,000 records per call; every domain parameter table says fewer than 1,000 and caps the field at four characters, which cannot express 20000. We tested — 20,000 is correct and the tables are stale.
The landing page says records_from. The parameter tables say record_from.
That is the part worth knowing. Not that the limits exist — they are reasonable limits. It is that the only way we learned them was to violate one and get blocked. A researcher hitting the same wall just experiences an outage and concludes the data is unreliable.
We have passed all of it back to them. That is where it sits.
⚠️ This slide carries NO ask, per the 2026-07-27 scope decision. Report the finding and stop. Do not invite the room to co-sign anything and do not suggest their name would carry it further. That framing was removed deliberately and is not coming back. If someone volunteers to weigh in, that is their initiative and a fine outcome — you do not solicit it.
Get the letter's status right. Sent 2026-07-22, answered 2026-07-24. Do not describe it as pending, unsent, or unanswered. Richard Lundy (Assistant Comptroller, IT Operations/CIO) is the technical route, Matt Rubin (Chief of Staff) is the relationship owner.
We replied 2026-07-29 confirming access works, owning the rate-limit fault, and asking whether the limit is scoped per IP or per client. That question is outstanding — and it is ours to pursue.
We checked the portal's contents and nothing else. "Not on the portal" is the whole claim. It may have gone to the Council as a PDF.
Extraction artifact or genuinely absent from the source resolution — we do not know. Do not assert it is a transparency failure. We can check the source PDF via the Legistar crosswalk. $176.6M turns on the answer.
The same 500-row clamp governs four tools. We corrected Act 3 against the database, but any other aggregate in this material at or near 500 rows should be assumed truncated until someone checks it the same way.
We checked the landing page, the global parameters table, and every domain page, and the office confirmed verbally it is undocumented. That is our search plus their word, not proof.
This slide is not a disclaimer, it is the product demonstrating what it is for. An audience that does oversight for a living will notice the difference between "this dataset is not on the portal" and "the city stopped producing this report" immediately.
Overstating once costs more than the finding was worth. Say that out loud if it helps.
The De La Rosa allocation figure on slide 4 is also un-re-verified since 2026-07-21 — only the Checkbook side was re-run on 07-30.
NYC Council Legistar · the Comptroller's Checkbook NYC · The City Record · the City Charter, Administrative Code and Rules · NYC Open Data · NYS Senate OpenLegislation · NYC 311.
All public. None of it ours.
An assistant queries these connectors and reads back what they return. It does not extract the budget data — that is deterministic PDF parsing, reconciled against printed totals, because reconciling public money has to be reproducible.
The AI is the interface, not the arithmetic.
BetaNYC received $25,000 in FY2026 Council discretionary funding under the Digital Inclusion and Literacy Initiative, via the Fund for the City of New York.
You can verify that in fifteen seconds, with no account.
Every bug described in Act 6 is a public issue with our name on it, including the one where we were the problem.
The third card is the strongest one in the deck and it costs nothing. A deck that teaches people to query discretionary funding should disclose its own award and invite them to check it.
If asked about the AI-use position: it matches the statements carried in the public repos. Vagueness here would be self-defeating in a deck about grounding.
The connectors are public repos, the guides are CC BY-SA, and most of it needs no API key at all. We would rather you check it than take our word for any of it.
If there is a query your team runs constantly by hand, that is exactly the user journey we want to add.
The ask is the last line. Get one concrete recurring query out of the room before you leave. That is worth more than any follow-up meeting.
Leave-behind: the published script for this meeting is at guides/good-government/reinvent-albany.md in the repo, with every figure's provenance in the presenter notes at the bottom.