BetaNYC · Reinvent Albany · July 30, 2026

The assembly step

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

Presenter notes

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).

Concede this first

Three things you should not use this for

One record you already know

If you have the contract ID, open Checkbook. A browser tab beats a query every time. Do not let a tool add a step.

Anything legally operative

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.

A question the data cannot answer

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.

Presenter notes

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.

Act 2 · the assembly

One vendor, every district, one question

Querying FY2026 Schedule C discretionary awards

By hand How you do this today

  • Open the Schedule C PDF for FY2026
  • Read it end to end — the awards are not grouped by vendor
  • Transcribe every row naming this organization
  • Reconcile 41 separate sponsors by hand
  • An afternoon, and you cannot re-run it next year.

Schedule C What the query returns

  • 54 awards, FY2026
  • $6,455,750 to one organization
  • Funded separately by 41 distinct sponsors
  • Nearly all under NYC Cleanup, via DYCD✓ 54, well under the cap
  • No line item anywhere says this.

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.

Presenter notes

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.

Act 2 · allocation to disbursement

The budget code is the join. It is coarser than it looks.

Verified 2026-07-30

Community League of the Heights, FY2026

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 number that does not reconcile

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.

Presenter notes

🚩 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.

Act 3 · build the meeting around this

Money that was announced, then withdrawn

Verified rows · incomplete set

FY2026 transparency resolution rescissions

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

The scale, read from the database

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.

Presenter notes

🚨 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.

Our own failure, twice, on the record

We broke this slide, then broke it again inside the fix

First break · 07-21

Truncated at 50

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 fix · 07-21

"Just pass limit: 500"

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.

Second break · 07-27

$90,314,887 low

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.

Presenter notes

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.

Act 4 · a reproducible method

Mandated annually. Most recent is 2019–2020.

Verified 2026-07-21

Local Law 174 of 2016 — CTE report

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

Then the discipline that makes it credible

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.

Presenter notes

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.

Know these cold, and volunteer them

What the data does not cover

Schedule C awards

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.

Transparency resolutions

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.

Two silent traps that survive

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.

Council vote positions

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.

Presenter notes

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.

The act that exists because of your name

The reform graveyard, queryable

By hand

Days of work in Albany

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.

Verified 2026-07-27

The same lineage, one query

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.

Presenter notes

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-2023A3140-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.

Act 5 · optional, 2 minutes

The City Record, queryable

Run live — examples expire

Open solicitations, soonest first

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

Same source as the daily PDF

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.

Presenter notes

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.

Act 6 · part one of three

We asked for access. Then we found out we were the problem.

Jul 16–21

Blocked

Checkbook's documented API starts returning 403 to us. The site loads fine in a browser.

Jul 22

We wrote

A letter to the Comptroller's office. Three asks. No header spoofing, no working around the block.

Jul 24

They answered

Two days. A real technical response, their CIO looped in, and an offer of a call.

Jul 28

It was us

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.

Presenter notes

🚫 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.

Act 6 · part two of three

We published two findings. One of them was wrong.

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 were wrong

"The registration date is not exposed"

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.

We were right

"The non-profit classification is not published"

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.

Presenter notes

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.

Act 6 · part three of three

The limits exist. They are documented nowhere.

Two numbers, learned only in conversation

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.

Two smaller documentation defects

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.

Presenter notes

⚠️ 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.

Stated at the source, not in a footnote

What we could not verify

Whether the LL174 CTE report was produced at all

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.

Why 345 rescissions carry no member

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.

Everything the row cap still touches

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.

Whether the rate limit is published somewhere we did not look

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.

Presenter notes

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.

Credit where it is owed

What this is built on, including the parts that are not ours

The records

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.

The AI layer, specifically

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.

Look us up too

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.

Presenter notes

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.

Where to take it

Fork it and find what we got wrong

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.

Presenter notes

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.

BetaNYC
Presenter notes visible
← → move · N notes · G grid · F full

Jump to a slide

Querying