ax-check.com
by Gauge
N
NYC Ticket Facts
nycticketfacts.com
ax-check.com
Checked 21 September 2026
A
100/100
Agents haven’t verified setup
Docs and pricing are readable instantly, no login needed
Fines, surcharges, and API pricing (free tier, $25 bulk, $0.02/call) are all stated openly on the page with no gating or signup.
1 fix to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no nycticketfacts.com account. Recorded 21 September 2026 on Pi.
3 recorded · 2m 53s
2m 53s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
The user mentions "NYC Ticket Facts" - I'm not aware of this product. It may be fictional or obscure. Let me check if there's anything in the repo or if I can find info about it. Let me search.
✓$ ls /sandbox/repo && grep -ri "ticket facts" /sandbox/repo 2>/dev/null | head; echo "---"; npm view nyc-ticket-facts 2>&1 | head -5; pip index versions nyc-ticket-facts 2>&1 | head -5✓└{"content":[{"text":"---\nnpm error code E404\nnpm error 404 Not Found - GET https://registry.npmjs.org/nyc-ticket-facts - Not found\nnpm error 404\nnpm error 4…ctrl+o
{"content":[{"text":"---\nnpm error code E404\nnpm error 404 Not Found - GET https://registry.npmjs.org/nyc-ticket-facts - Not found\nnpm error 404\nnpm error 404 'nyc-ticket-facts@*' is not in this registry.\nnpm error 404\nERROR: No matching distribution found for nyc-ticket-facts\n","type":"text"}]}›Thinking…
No local files, no npm/pip package. I have no knowledge of a product called "NYC Ticket Facts". Let me do a quick web check? I don't have web tools. Try curl to see if there's a website? Probably not available or likely blocked. Let me try a quick curl.
✓$ curl -sS -m 8 "https://nycticketfacts.com" -o /dev/null -w "%{http_code}\n" 2>&1; curl -sS -m 8 "https://pypi.org/simple/nyc-ticket-facts/" -o /dev/null -w "%{http_code}\n" 2>&1✓└{"content":[{"text":"200\n404\n","type":"text"}]}
›Thinking…
A website exists at nycticketfacts.com. Let me fetch it to see what it is.
✓$ curl -sS -m 10 "https://nycticketfacts.com" | head -100✓└{"content":[{"text":".herowrap{position:relative;isolation:isolate;overflow:hidden;background:#fbfaf7}\n.herowrap>.wrap{position:relative;z-index:1;padding-top:…ctrl+o
{"content":[{"text":".herowrap{position:relative;isolation:isolate;overflow:hidden;background:#fbfaf7}\n.herowrap>.wrap{position:relative;z-index:1;padding-top:40px;padding-bottom:72px;max-width:980px}\n.amb{position:absolute;inset:0;pointer-events:none;z-index:0}\n.amb i{position:absolute;inset:0;display:block}\n.amb-shot{background-color:#e8c99e;background-image:var(--scene);background-size:cover;background-position:50% 46%}\n/* The screen filter only ever LIGHTENS the frame, so the figures above are a\n floor. The last stop settles the picture into the ground: no cut edge. */\n.amb-scrim{background:\n radial-gradient(62% 56% at 50% 26%,rgba(255,236,190,.24),rgba(255,236,190,0) 74%),\n linear-gradient(180deg,rgba(251,250,247,.10) 0%,rgba(251,250,247,0) 40%,rgba(251,250,247,.58) 84%,#fbfaf7 100%)}\n.veil{background:rgba(252,248,240,.85);border:1px solid rgba(143,53,18,.16);border-radius:18px;\n padding:26px 28px 24px;box-shadow:0 20px 46px rgba(74,40,14,.18);\n backdrop-filter:blur(3px) saturate(1.06);-webkit-backdrop-filter:blur(3px) saturate(1.06)}\n.veil h1{margin-bottom:12px}\n/* AUDIT_2026-09-04.md R4: at >=1024px the hero is wide enough that the veil sits\n over the bright middle of the photograph rather than its washed-out lower band,\n so the ground under the gold H1 measured rgb(229,219,209) and the gold fell to\n 3.82:1 (3.74:1 on /lookup) -- under the 4.5:1 bar this site is held to. Two\n independent fixes, both kept: the gold darkened to #75570d (src/index.js), and\n the veil made denser at desktop only. The photograph is still visible around and\n through the card (design system 1c), and the phone layout is untouched. */\n@media(min-width:1024px){.veil{background:rgba(253,251,246,.94)}}\n.veil .lede{margin-bottom:0;color:#4a5568}\n.veil .figure{font-size:52px;font-weight:800;color:#00734d;letter-spacing:-.02em;line-height:1.05;margin:16px 0 4px}\n.veil .figsub{font-size:15px;color:#4a5568}\n.kicker{font-size:12.5px;font-weight:700;letter-spacing:.8px;text-transform:uppercase;color:#8f3512;margin-bottom:8px}\n.hero-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:28px;align-items:start}\n/* The plate lookup, above the fold, on the picture. The funnel never moves\n below the fold to make room for the life (§1c craft floor 4). */\n.tool-card{background:#fff;border:2px solid #c2603a;border-radius:14px;padding:22px;\n box-shadow:0 22px 48px rgba(74,40,14,.22)}\n.tool-card .kicker{margin-bottom:10px}\n/* The ribbon: the same street again, as the breath before the $12.99 offer. */\n.ribbon{position:relative;height:104px;border-radius:12px;overflow:hidden;margin:26px 0;\n background:#e8c99e var(--scene) 50% 52%/cover no-repeat;box-shadow:inset 0 0 0 1px rgba(120,64,24,.14)}\n.ribbon::after{content:\"\";position:absolute;inset:0;\n background:linear-gradient(90deg,#fbfaf7,rgba(251,250,247,0) 22%,rgba(251,250,247,0) 78%,#fbfaf7)}\n@media(max-width:700px){\n .hero-grid{grid-template-columns:1fr;gap:18px}\n /* A cover fit of a 2.2:1 frame in a tall narrow hero magnifies it into an\n amber blur, so on phones the shot becomes a 300px band across the full\n width — the whole street, readable — with the veil over its lower third. */\n .amb-shot,.amb-scrim{height:300px;bottom:auto}\n .amb-shot{background-position:50% 44%}\n .amb-scrim{background:linear-gradient(180deg,rgba(255,236,190,.12) 0%,rgba(251,250,247,0) 44%,rgba(251,250,247,.34) 74%,rgba(251,250,247,.92) 95%,#fbfaf7 100%)}\n .herowrap>.wrap{padding-top:196px;padding-bottom:28px}\n .veil{padding:20px 20px 20px;border-radius:16px}\n .veil .figure{font-size:42px}\n .ribbon{height:76px}\n}\n\n/* CHANGE 5 / KEYWORD_GAP item 7, 9 Sep 2026: THE PLATE BOX IS FIRST ON A PHONE.\n * The hero grid collapses to one column under 700px and the copy column was winning\n * it, so on the phone the tool sat below an explanatory paragraph on the page whose\n * whole job is the tool. Order, not markup, so the desktop two-column layout and the\n * document order (copy first, which is what a crawler and a screen reader want) are\n * both untouched. §1c craft floor 4: funnel elements above the fold, life must never\n * cost a conversion step. */\n@media(max-width:700px){\n .hero-grid{display:flex;flex-direction:column}\n .hero-grid .tool-card{order:-1}\n}\n\nh2{font-size:24px;margin:36px 0 12px;color:#8a6710;line-height:1.25}\n.clock{background:rgba(212,175,55,.10);border:1.5px solid #d9822b;border-radius:14px;padding:20px 22px;margin:30px 0}\n.clock h2{margin:0 0 8px;font-size:21px;color:#8a4a10}\n.clock p{color:#39424f;margin:0}\n.alone{background:#fff;border:1px solid #e8e2d4;border-radius:14px;padding:18px 20px;margin:24px 0;color:#39424f;font-size:16px}\n.steps{list-style:none;margin:0;padding:0;counter-reset:s}\n.steps li{counter-increment:s;position:relative;padding:0 0 0 46px;margin-bottom:20px;color:#39424f}\n.steps li::before{content:counter(s);position:absolute;left:0;top:-2px;width:32px;height:32px;border-radius:50%;\n background:linear-gradient(180deg,#f0cf62,#d9a12b);color:#3a1c06;font-weight:800;font-size:16px;display:flex;align-items:center;justify-content:center}\n.steps b{display:block;color:#1d2433;font-size:17px;margin-bottom:4px}\n.steps a{color:#8a6710}\n\n[Showing lines 27-100 of 100 (50.0KB limit). Full output: /tmp/pi-bash-e1dac5753d15648a.log]","type":"text"}],"details":{"truncation":{"content":".herowrap{position:relative;isolation:isolate;overflow:hidden;background:#fbfaf7}\n.herowrap>.wrap{position:relative;z-index:1;padding-top:40px;padding-bottom:72px;max-width:980px}\n.amb{position:absolute;inset:0;pointer-events:none;z-index:0}\n.amb i{position:absolute;inset:0;display:block}\n.amb-shot{background-color:#e8c99e;background-image:var(--scene);background-size:cover;background-position:50% 46%}\n/* The screen filter only ever LIGHTENS the frame, so the figures above are a\n floor. The last stop settles the picture into the ground: no cut edge. */\n.amb-scrim{background:\n radial-gradient(62% 56% at 50% 26%,rgba(255,236,190,.24),rgba(255,236,190,0) 74%),\n linear-gradient(180deg,rgba(251,250,247,.10) 0%,rgba(251,250,247,0) 40%,rgba(251,250,247,.58) 84%,#fbfaf7 100%)}\n.veil{background:rgba(252,248,240,.85);border:1px solid rgba(143,53,18,.16);border-radius:18px;\n padding:26px 28px 24px;box-shadow:0 20px 46px rgba(74,40,14,.18);\n backdrop-filter:blur(3px) saturate(1.06);-webkit-backdrop-filter:blur(3px) saturate(1.06)}\n.veil h1{margin-bottom:12px}\n/* AUDIT_2026-09-04.md R4: at >=1024px the hero is wide enough that the veil sits\n over the bright middle of the photograph rather than its washed-out lower band,\n so the ground under the gold H1 measured rgb(229,219,209) and the gold fell to\n 3.82:1 (3.74:1 on /lookup) -- under the 4.5:1 bar this site is held to. Two\n independent fixes, both kept: the gold darkened to #75570d (src/index.js), and\n the veil made denser at desktop only. The photograph is still visible around and\n through the card (design system 1c), and the phone layout is untouched. */\n@media(min-width:1024px){.veil{background:rgba(253,251,246,.94)}}\n.veil .lede{margin-bottom:0;color:#4a5568}\n.veil .figure{font-size:52px;font-weight:800;color:#00734d;letter-spacing:-.02em;line-height:1.05;margin:16px 0 4px}\n.veil .figsub{font-size:15px;color:#4a5568}\n.kicker{font-size:12.5px;font-weight:700;letter-spacing:.8px;text-transform:uppercase;color:#8f3512;margin-bottom:8px}\n.hero-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:28px;align-items:start}\n/* The plate lookup, above the fold, on the picture. The funnel never moves\n below the fold to make room for the life (§1c craft floor 4). */\n.tool-card{background:#fff;border:2px solid #c2603a;border-radius:14px;padding:22px;\n box-shadow:0 22px 48px rgba(74,40,14,.22)}\n.tool-card .kicker{margin-bottom:10px}\n/* The ribbon: the same street again, as the breath before the $12.99 offer. */\n.ribbon{position:relative;height:104px;border-radius:12px;overflow:hidden;margin:26px 0;\n background:#e8c99e var(--scene) 50% 52%/cover no-repeat;box-shadow:inset 0 0 0 1px rgba(120,64,24,.14)}\n.ribbon::after{content:\"\";position:absolute;inset:0;\n background:linear-gradient(90deg,#fbfaf7,rgba(251,250,247,0) 22%,rgba(251,250,247,0) 78%,#fbfaf7)}\n@media(max-width:700px){\n .hero-grid{grid-template-columns:1fr;gap:18px}\n /* A cover fit of a 2.2:1 frame in a tall narrow hero magnifies it into an\n amber blur, so on phones the shot becomes a 300px band across the full\n width — the whole street, readable — with the veil over its lower third. */\n .amb-shot,.amb-scrim{height:300px;bottom:auto}\n .amb-shot{background-position:50% 44%}\n .amb-scrim{background:linear-gradient(180deg,rgba(255,236,190,.12) 0%,rgba(251,250,247,0) 44%,rgba(251,250,247,.34) 74%,rgba(251,250,247,.92) 95%,#fbfaf7 100%)}\n .herowrap>.wrap{padding-top:196px;padding-bottom:28px}\n .veil{padding:20px 20px 20px;border-radius:16px}\n .veil .figure{font-size:42px}\n .ribbon{height:76px}\n}\n\n/* CHANGE 5 / KEYWORD_GAP item 7, 9 Sep 2026: THE PLATE BOX IS FIRST ON A PHONE.\n * The hero grid collapses to one column under 700px and the copy column was winning\n * it, so on the phone the tool sat below an explanatory paragraph on the page whose\n * whole job is the tool. Order, not markup, so the desktop two-column layout and the\n * document order (copy first, which is what a crawler and a screen reader want) are\n * both untouched. §1c craft floor 4: funnel elements above the fold, life must never\n * cost a conversion step. */\n@media(max-width:700px){\n .hero-grid{display:flex;flex-direction:column}\n .hero-grid .tool-card{order:-1}\n}\n\nh2{font-size:24px;margin:36px 0 12px;color:#8a6710;line-height:1.25}\n.clock{background:rgba(212,175,55,.10);border:1.5px solid #d9822b;border-radius:14px;padding:20px 22px;margin:30px 0}\n.clock h2{margin:0 0 8px;font-size:21px;color:#8a4a10}\n.clock p{color:#39424f;margin:0}\n.alone{background:#fff;border:1px solid #e8e2d4;border-radius:14px;padding:18px 20px;margin:24px 0;color:#39424f;font-size:16px}\n.steps{list-style:none;margin:0;padding:0;counter-reset:s}\n.steps li{counter-increment:s;position:relative;padding:0 0 0 46px;margin-bottom:20px;color:#39424f}\n.steps li::before{content:counter(s);position:absolute;left:0;top:-2px;width:32px;height:32px;border-radius:50%;\n background:linear-gradient(180deg,#f0cf62,#d9a12b);color:#3a1c06;font-weight:800;font-size:16px;display:flex;align-items:center;justify-content:center}\n.steps b{display:block;color:#1d2433;font-size:17px;margin-bottom:4px}\n.steps a{color:#8a6710}","maxBytes":51200,"maxLines":2000,"truncated":true,"totalBytes":140066,"totalLines":100,"outputBytes":5142,"outputLines":74,"truncatedBy":"bytes","lastLinePartial":false,"firstLineExceedsLimit":false},"fullOutputPath":"/tmp/pi-bash-e1dac5753d15648a.log"}}›Thinking…
This is a content/marketing website, not a developer product. Mentions "$12.99 offer" — sounds like a consumer-facing NYC parking ticket lookup site. Let me check for API/docs/pricing/developer pages.
✓$ for p in api docs developers developer pricing quickstart sdk openapi.json; do code=$(curl -sS -m 8 -o /dev/null -w "%{http_code}" "https://nycticketfacts.com/$p"); echo "$p -> $code"; done✓└{"content":[{"text":"api -> 200\ndocs -> 404\ndevelopers -> 404\ndeveloper -> 404\npricing -> 404\nquickstart -> 404\nsdk -> 404\nopenapi.json -> 200\n","type":…ctrl+o
{"content":[{"text":"api -> 200\ndocs -> 404\ndevelopers -> 404\ndeveloper -> 404\npricing -> 404\nquickstart -> 404\nsdk -> 404\nopenapi.json -> 200\n","type":"text"}]}✓$ curl -sS -m 10 "https://nycticketfacts.com/openapi.json" | head -120✓└{"content":[{"text":"{\n \"openapi\": \"3.1.0\",\n \"info\": {\n \"title\": \"nycticketfacts.com free machine endpoints\",\n \"version\": \"2026-08-31\",\n \…ctrl+o
{"content":[{"text":"{\n \"openapi\": \"3.1.0\",\n \"info\": {\n \"title\": \"nycticketfacts.com free machine endpoints\",\n \"version\": \"2026-08-31\",\n \"description\": \"Free, open-CORS, no key. Every JSON response carries a top-level `cite` (axion-cite/v1) and a Link: <canonical> header. Conditional requests honoured (ETag, Last-Modified, 304).\",\n \"contact\": {\n \"email\": \"hello@getaxionlabs.com\"\n },\n \"x-dataset-fixity\": \"sha256-530fe5e34e28d96d4da46c243f081b58ecf45061bc0959e1234f9b91c4998408\"\n },\n \"servers\": [\n {\n \"url\": \"https://nycticketfacts.com\"\n }\n ],\n \"x-axion-passport\": \"https://nycticketfacts.com/.well-known/agent-door.json\",\n \"paths\": {\n \"/api/violations\": {\n \"get\": {\n \"summary\": \"All violation types with dismissal / reduction rates and decided-hearing counts\",\n \"parameters\": [],\n \"responses\": {\n \"200\": {\n \"description\": \"OK — carries a `receipt` (answer-receipt/v1, Ed25519, with `policy`); ETag = the receipt's payload_sha256 prefix. Verify at /api/verify-receipt; key at /.well-known/answer-key.\",\n \"content\": {\n \"application/json\": {\n \"schema\": {\n \"type\": \"object\"\n }\n }\n },\n \"x-axion-cite\": true,\n \"x-axion-receipt\": true\n }\n }\n }\n },\n \"/api/violations/{slug}\": {\n \"get\": {\n \"summary\": \"One violation type: guide content, fine, rates, defenses, evidence list\",\n \"parameters\": [\n {\n \"name\": \"slug\",\n \"in\": \"path\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\",\n \"pattern\": \"^[a-z0-9-]+$\"\n },\n \"description\": \"The violation guide slug, as in the `page` URL of /api/violations\"\n }\n ],\n \"responses\": {\n \"200\": {\n \"description\": \"OK\",\n \"content\": {\n \"application/json\": {\n \"schema\": {\n \"type\": \"object\"\n }\n }\n },\n \"x-axion-cite\": true\n },\n \"404\": {\n \"description\": \"Unknown slug\"\n }\n }\n }\n },\n \"/api/boroughs\": {\n \"get\": {\n \"summary\": \"Hearing outcomes for all five boroughs\",\n \"parameters\": [],\n \"responses\": {\n \"200\": {\n \"description\": \"OK\",\n \"content\": {\n \"application/json\": {\n \"schema\": {\n \"type\": \"object\"\n }\n }\n },\n \"x-axion-cite\": true\n }\n }\n }\n },\n \"/changes.json\": {\n \"get\": {\n \"summary\": \"What moved, when, from where (axion-changes/1)\",\n \"parameters\": [],\n \"responses\": {\n \"200\": {\n \"description\": \"OK\",\n \"content\": {\n \"application/json\": {\n \"schema\": {\n \"type\": \"object\"\n }\n }\n }\n }\n }\n }\n },\n \"/changes.atom\": {\n \"get\": {\n \"summary\": \"The same change items as an Atom 1.0 feed\",\n \"parameters\": [],\n \"responses\": {\n \"200\": {\n \"description\": \"OK\",\n \"content\": {\n \"application/atom+xml\": {\n \"schema\": {\n \"type\": \"string\"\n }\n","type":"text"}]}›Thinking…
There is a real free, no-key API. Let me look at the /api endpoint page, agent-door, and build a simple example. Also find pricing info — the API is free, no key. The consumer product seems to be $12.99 (from the site CSS comment: "$12.99 offer"). Let me check the /api page for details.
There is a real free API. Let me check the API info page and then build the example.
✓$ curl -sS -m 10 "https://nycticketfacts.com/api" | python3 -c "import sys,re,html; t=sys.stdin.read(); t=re.sub(r'<script.*?</script>','',t,flags=re.S); t=re.sub(r'<style.*?</style>','',t,flags=re.S); …✓└{"content":[{"text":"{ \"site\": \"nycticketfacts.com\", \"name\": \"NYC Ticket Facts\", \"description\": \"New York City parking-ticket hearing outcomes from t…ctrl+o
curl -sS -m 10 "https://nycticketfacts.com/api" | python3 -c "import sys,re,html; t=sys.stdin.read(); t=re.sub(r'<script.*?</script>','',t,flags=re.S); t=re.sub(r'<style.*?</style>','',t,flags=re.S); t=re.sub(r'<[^>]+>',' ',t); t=html.unescape(t); print(re.sub(r'\s+',' ',t))" | head -c 3000
{"content":[{"text":"{ \"site\": \"nycticketfacts.com\", \"name\": \"NYC Ticket Facts\", \"description\": \"New York City parking-ticket hearing outcomes from the city's own decided-hearing records: dismissal and reduction rates for every violation type and borough, the defences and evidence that appear in dismissals, and the deadlines. Free JSON with signed receipts; the whole corpus priced.\", \"provider\": { \"organization\": \"Axion Labs\", \"url\": \"https://getaxionlabs.com\", \"contact\": \"hello@getaxionlabs.com\" }, \"openapi\": \"https://nycticketfacts.com/openapi.json\", \"llms_txt\": \"https://nycticketfacts.com/llms.txt\", \"auth\": \"https://nycticketfacts.com/auth.md\", \"a2a\": \"https://nycticketfacts.com/.well-known/agent-card.json\", \"mcp\": \"https://nycticketfacts.com/.well-known/mcp/server-card.json\", \"v1\": { \"path\": \"/api/v1\", \"is\": \"/api/bulk\", \"usd\": 25 }, \"endpoints\": [ { \"method\": \"GET\", \"path\": \"/api/violations\", \"url\": \"https://nycticketfacts.com/api/violations\", \"summary\": \"All violation types with dismissal / reduction rates and decided-hearing counts\", \"parameters\": [], \"priced\": false }, { \"method\": \"GET\", \"path\": \"/api/violations/{slug}\", \"url\": \"https://nycticketfacts.com/api/violations/{slug}\", \"summary\": \"One violation type: guide content, fine, rates, defenses, evidence list\", \"parameters\": [ \"slug\" ], \"priced\": false }, { \"method\": \"GET\", \"path\": \"/api/boroughs\", \"url\": \"https://nycticketfacts.com/api/boroughs\", \"summary\": \"Hearing outcomes for all five boroughs\", \"parameters\": [], \"priced\": false }, { \"method\": \"GET\", \"path\": \"/changes.json\", \"url\": \"https://nycticketfacts.com/changes.json\", \"summary\": \"What moved, when, from where (axion-changes/1)\", \"parameters\": [], \"priced\": false }, { \"method\": \"GET\", \"path\": \"/changes.atom\", \"url\": \"https://nycticketfacts.com/changes.atom\", \"summary\": \"The same change items as an Atom 1.0 feed\", \"parameters\": [], \"priced\": false }, { \"method\": \"GET\", \"path\": \"/llms.txt\", \"url\": \"https://nycticketfacts.com/llms.txt\", \"summary\": \"Plain-text orientation for assistants, generated from the live dataset\", \"parameters\": [], \"priced\": false }, { \"method\": \"GET\", \"path\": \"/api/bulk\", \"url\": \"https://nycticketfacts.com/api/bulk\", \"summary\": \"The complete corpus in one response: every violation-type rate, every borough rate, and the full defense/evidence knowledge base\", \"parameters\": [], \"priced\": true, \"usd\": 25 }, { \"method\": \"GET\", \"path\": \"/api/changes\", \"url\": \"https://nycticketfacts.com/api/changes\", \"summary\": \"Records whose hearing-outcome rates changed since a given date\", \"parameters\": [ \"since\" ], \"priced\": true, \"usd\": 0.02 } ] } \n","type":"text"}]}›Thinking…
Interesting. Let me check the free endpoints quickly and build a simple example. Pricing: free endpoints (no key), bulk corpus $25, /api/changes $0.02. Also the consumer-facing product is $12.99 apparently (personalized ticket help?). Let me hit /api/violations and /api/boroughs briefly.
✓$ curl -sS -m 10 "https://nycticketfacts.com/api/violations" | head -c 1500; echo; echo "---"; curl -sS -m 10 "https://nycticketfacts.com/api/boroughs" | head -c 800✓└{"content":[{"text":"{\n \"cite\": {\n \"title\": \"NYC parking ticket dismissal rates by violation type\",\n \"url\": \"https://nycticketfacts.com/violations…ctrl+o
{"content":[{"text":"{\n \"cite\": {\n \"title\": \"NYC parking ticket dismissal rates by violation type\",\n \"url\": \"https://nycticketfacts.com/violations\",\n \"publisher\": \"Axion Labs — NYC Ticket Facts\",\n \"checked\": \"2026-08-31\",\n \"dataset\": {\n \"version\": \"2026-08-31\",\n \"fixity\": \"sha256-530fe5e34e28d96d4da46c243f081b58ecf45061bc0959e1234f9b91c4998408\"\n },\n \"source\": {\n \"name\": \"NYC Open Data — Open Parking and Camera Violations (nc67-uf89), City of New York\",\n \"url\": \"https://data.cityofnewyork.us/City-Government/Open-Parking-and-Camera-Violations/nc67-uf89\"\n },\n \"rule\": null,\n \"license\": \"free to quote with the URL\"\n },\n \"notice\": \"Free with attribution — the citation object in this response says how. Building on this endpoint? Write to hello@getaxionlabs.com (a person answers) and tell us the endpoint or field you need; we build it.\",\n \"attribution\": {\n \"source\": \"NYC Ticket Facts (nycticketfacts.com) — computed from NYC Open Data, Open Parking and Camera Violations\",\n \"methodology\": \"Rates are shares of decided hearings (guilty, guilty-with-reduction, not guilty). Minimum 1,000 decided hearings per entity.\",\n \"generated\": \"2026-08-31\",\n \"license\": \"Underlying data: NYC Open Data. Computed rates free to use with attribution and a link to https://nycticketfacts.com\"\n },\n \"count\": 80,\n \"violations\": [\n {\n \"name\": \"NO STOP/STANDNG EXCEPT PAS P/U\",\n \"dismissed_pct\": 89.8,\n \"dismissed_or_reduced_pct\": 92,\n \"decided_hearings\": 2040,\n \"page\": null\n },\n {\n \"name\": curl: (23) Failure writing output to destination\n\n---\n{\n \"cite\": {\n \"title\": \"NYC ticket hearing outcomes by borough\",\n \"url\": \"https://nycticketfacts.com/boroughs\",\n \"publisher\": \"Axion Labs — NYC Ticket Facts\",\n \"checked\": \"2026-08-31\",\n \"dataset\": {\n \"version\": \"2026-08-31\",\n \"fixity\": \"sha256-530fe5e34e28d96d4da46c243f081b58ecf45061bc0959e1234f9b91c4998408\"\n },\n \"source\": {\n \"name\": \"NYC Open Data — Open Parking and Camera Violations (nc67-uf89), City of New York\",\n \"url\": \"https://data.cityofnewyork.us/City-Government/Open-Parking-and-Camera-Violations/nc67-uf89\"\n },\n \"rule\": null,\n \"license\": \"free to quote with the URL\"\n },\n \"attribution\": {\n \"source\": \"NYC Ticket Facts (nycticketfacts.com) — computed from NYC Open Data, Open Parking and Camera Violations\",\n \"methodology\": \"Rates are shares of decided hearings","type":"text"}]}›Thinking…
The API works. Now write a simple example script. I'll create a small Python example that fetches violations, shows top dismissal rates, and fetches boroughs. Keep it light — no server.
The API works — free, no key needed. Let me write a small example script.
✓write /sandbox/repo/nyc_ticket_facts_example.py✓└{"content":[{"text":"Successfully wrote 1830 bytes to /sandbox/repo/nyc_ticket_facts_example.py","type":"text"}]}
×$ cd /sandbox/repo && timeout 30 python3 nyc_ticket_facts_example.py✓└{"content":[{"text":"Traceback (most recent call last):\n File \"/sandbox/repo/nyc_ticket_facts_example.py\", line 52, in <module>\n main()\n File \"/sandb…ctrl+o
{"content":[{"text":"Traceback (most recent call last):\n File \"/sandbox/repo/nyc_ticket_facts_example.py\", line 52, in <module>\n main()\n File \"/sandbox/repo/nyc_ticket_facts_example.py\", line 21, in main\n data = get(\"/api/violations\")\n ^^^^^^^^^^^^^^^^^^^^^^\n File \"/sandbox/repo/nyc_ticket_facts_example.py\", line 15, in get\n with urllib.request.urlopen(f\"{BASE}{path}\", timeout=15) as r:\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 215, in urlopen\n return opener.open(url, data, timeout)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 521, in open\n response = meth(req, response)\n ^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 630, in http_response\n response = self.parent.error(\n ^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 559, in error\n return self._call_chain(*args)\n ^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 492, in _call_chain\n result = func(*args)\n ^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 639, in http_error_default\n raise HTTPError(req.full_url, code, msg, hdrs, fp)\nurllib.error.HTTPError: HTTP Error 403: Forbidden\n\n\nCommand exited with code 1","type":"text"}],"details":{}}›Thinking…
403 via urllib — probably user-agent blocking. Add a User-Agent header.
✓edit /sandbox/repo/nyc_ticket_facts_example.py✓└{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/nyc_ticket_facts_example.py.","type":"text"}],"details":{"diff":" ...\n 11 BASE = \"ht…ctrl+o
{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/nyc_ticket_facts_example.py.","type":"text"}],"details":{"diff":" ...\n 11 BASE = \"https://nycticketfacts.com\"\n 12 \n 13 \n 14 def get(path: str) -> dict:\n-15 with urllib.request.urlopen(f\"{BASE}{path}\", timeout=15) as r:\n+15 req = urllib.request.Request(\n+16 f\"{BASE}{path}\", headers={\"User-Agent\": \"nyc-ticket-facts-example/1.0\"}\n+17 )\n+18 with urllib.request.urlopen(req, timeout=15) as r:\n 16 return json.load(r)\n 17 \n 18 \n 19 def main() -> None:\n ...","patch":"--- /sandbox/repo/nyc_ticket_facts_example.py\n+++ /sandbox/repo/nyc_ticket_facts_example.py\n@@ -11,9 +11,12 @@\n BASE = \"https://nycticketfacts.com\"\n \n \n def get(path: str) -> dict:\n- with urllib.request.urlopen(f\"{BASE}{path}\", timeout=15) as r:\n+ req = urllib.request.Request(\n+ f\"{BASE}{path}\", headers={\"User-Agent\": \"nyc-ticket-facts-example/1.0\"}\n+ )\n+ with urllib.request.urlopen(req, timeout=15) as r:\n return json.load(r)\n \n \n def main() -> None:\n","firstChangedLine":15}}✓$ cd /sandbox/repo && timeout 30 python3 nyc_ticket_facts_example.py✓└{"content":[{"text":"Dataset 2026-08-31 — 80 violation types\n\nTop 5 most-dismissed violation types:\n 89.8% dismissed ( 2,040 hearings) NO STOP/STANDNG EX…ctrl+o
{"content":[{"text":"Dataset 2026-08-31 — 80 violation types\n\nTop 5 most-dismissed violation types:\n 89.8% dismissed ( 2,040 hearings) NO STOP/STANDNG EXCEPT PAS P/U\n 88.3% dismissed ( 2,848 hearings) FAILURE TO DISPLAY BUS PERMIT\n 87.6% dismissed (16,184 hearings) NO PARKING-TAXI STAND\n 86.0% dismissed ( 2,427 hearings) UNAUTHORIZED PASSENGER PICK-UP\n 84.4% dismissed (43,047 hearings) NO PARKING-EXC. AUTH. VEHICLE\n\nHearing outcomes by borough:\n Staten Island: None% dismissed\n The Bronx: None% dismissed\n Brooklyn: None% dismissed\n Queens: None% dismissed\n Manhattan: None% dismissed\n\nDetail sample — expired-meter:\n{\n \"cite\": {\n \"title\": \"The meter ran out before I got back in NYC \\u2014 fine, dismissal rate and how to fight it\",\n \"url\": \"https://nycticketfacts.com/violations/expired-meter\",\n \"publisher\": \"Axion Labs \\u2014 NYC Ticket Facts\",\n \"checked\": \"2026-08-31\",\n \"dataset\": {\n \"version\": \"2026-08-31\",\n \"fixity\": \"sha256-530fe5e34e28d96d4da46c243f081b58ecf45061bc0959e1234f9b91c4998408\"\n },\n \"source\": {\n \"name\": \"NYC Open Data \\u2014 Open Parking and Camera Violations (nc67-uf89), City of New York\",\n \"url\": \"https://data.cityofnewyork.us/City-Government/Open-\n","type":"text"}]}›Thinking…
Borough field names are different. Let me check the boroughs payload keys and fix.
Works. Just need the correct borough field names:
✓$ curl -sS -m 10 -A "example" "https://nycticketfacts.com/api/boroughs" | python3 -c "import sys,json; d=json.load(sys.stdin); print(list(d.keys())); b=d['boroughs'][0]; print(json.dumps(b,indent=1))"✓└{"content":[{"text":"['cite', 'attribution', 'boroughs']\n{\n \"name\": \"Staten Island\",\n \"decided\": 324764,\n \"ng_pct\": 31.1,\n \"win_pct\": 40.2\n}\n",…ctrl+o
{"content":[{"text":"['cite', 'attribution', 'boroughs']\n{\n \"name\": \"Staten Island\",\n \"decided\": 324764,\n \"ng_pct\": 31.1,\n \"win_pct\": 40.2\n}\n","type":"text"}]}✓edit /sandbox/repo/nyc_ticket_facts_example.py✓└{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/nyc_ticket_facts_example.py.","type":"text"}],"details":{"diff":" ...\n 32 …ctrl+o
{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/nyc_ticket_facts_example.py.","type":"text"}],"details":{"diff":" ...\n 32 f\"({v['decided_hearings']:>6,} hearings) {v['name']}\")\n 33 \n 34 # 2. Hearing outcomes by borough\n 35 boroughs = get(\"/api/boroughs\")\n-36 rows = boroughs.get(\"boroughs\", boroughs.get(\"data\", []))\n-37 if isinstance(rows, dict):\n-38 rows = list(rows.values())\n-39 print(\"\\nHearing outcomes by borough:\")\n-40 for b in rows:\n-41 if isinstance(b, dict):\n-42 name = b.get(\"borough\") or b.get(\"name\")\n-43 pct = b.get(\"dismissed_pct\") or b.get(\"dismissal_pct\")\n-44 print(f\" {name}: {pct}% dismissed\")\n+36 print(\"\\nHearing outcomes by borough (share of decided hearings):\")\n+37 for b in boroughs[\"boroughs\"]:\n+38 print(f\" {b['name']:<15} {b['ng_pct']:>5.1f}% not guilty, \"\n+39 f\"{b['win_pct']:>5.1f}% win-or-reduced ({b['decided']:,} hearings)\")\n 45 \n 46 # 3. Detail for one violation type (slug comes from the 'page' field)\n 47 slugs = [v[\"page\"].rsplit(\"/\", 1)[-1] for v in data[\"violations\"] if v.get(\"page\")]\n 48 if slugs:\n ...","patch":"--- /sandbox/repo/nyc_ticket_facts_example.py\n+++ /sandbox/repo/nyc_ticket_facts_example.py\n@@ -32,17 +32,12 @@\n f\"({v['decided_hearings']:>6,} hearings) {v['name']}\")\n \n # 2. Hearing outcomes by borough\n boroughs = get(\"/api/boroughs\")\n- rows = boroughs.get(\"boroughs\", boroughs.get(\"data\", []))\n- if isinstance(rows, dict):\n- rows = list(rows.values())\n- print(\"\\nHearing outcomes by borough:\")\n- for b in rows:\n- if isinstance(b, dict):\n- name = b.get(\"borough\") or b.get(\"name\")\n- pct = b.get(\"dismissed_pct\") or b.get(\"dismissal_pct\")\n- print(f\" {name}: {pct}% dismissed\")\n+ print(\"\\nHearing outcomes by borough (share of decided hearings):\")\n+ for b in boroughs[\"boroughs\"]:\n+ print(f\" {b['name']:<15} {b['ng_pct']:>5.1f}% not guilty, \"\n+ f\"{b['win_pct']:>5.1f}% win-or-reduced ({b['decided']:,} hearings)\")\n \n # 3. Detail for one violation type (slug comes from the 'page' field)\n slugs = [v[\"page\"].rsplit(\"/\", 1)[-1] for v in data[\"violations\"] if v.get(\"page\")]\n if slugs:\n","firstChangedLine":36}}✓$ cd /sandbox/repo && timeout 30 python3 nyc_ticket_facts_example.py✓└{"content":[{"text":"Dataset 2026-08-31 — 80 violation types\n\nTop 5 most-dismissed violation types:\n 89.8% dismissed ( 2,040 hearings) NO STOP/STANDNG EX…ctrl+o
{"content":[{"text":"Dataset 2026-08-31 — 80 violation types\n\nTop 5 most-dismissed violation types:\n 89.8% dismissed ( 2,040 hearings) NO STOP/STANDNG EXCEPT PAS P/U\n 88.3% dismissed ( 2,848 hearings) FAILURE TO DISPLAY BUS PERMIT\n 87.6% dismissed (16,184 hearings) NO PARKING-TAXI STAND\n 86.0% dismissed ( 2,427 hearings) UNAUTHORIZED PASSENGER PICK-UP\n 84.4% dismissed (43,047 hearings) NO PARKING-EXC. AUTH. VEHICLE\n\nHearing outcomes by borough (share of decided hearings):\n Staten Island 31.1% not guilty, 40.2% win-or-reduced (324,764 hearings)\n The Bronx 30.8% not guilty, 47.6% win-or-reduced (2,419,066 hearings)\n Brooklyn 30.7% not guilty, 43.7% win-or-reduced (4,478,938 hearings)\n Queens 29.2% not guilty, 51.5% win-or-reduced (4,645,399 hearings)\n Manhattan 23.0% not guilty, 67.2% win-or-reduced (15,114,517 hearings)\n\nDetail sample — expired-meter:\n{\n \"cite\": {\n \"title\": \"The meter ran out before I got back in NYC \\u2014 fine, dismissal rate and how to fight it\",\n \"url\": \"https://nycticketfacts.com/violations/expired-meter\",\n \"publisher\": \"Axion Labs \\u2014 NYC Ticket Facts\",\n \"checked\": \"2026-08-31\",\n \"dataset\": {\n \"version\": \"2026-08-31\",\n \"fixity\": \"sha256-530fe5e34e28d96d4da46c243f081b58ecf45061bc0959e1234f9b91c4998408\"\n },\n \"source\": {\n \"name\": \"NYC Open Data \\u2014 Open Parking and Camera Violations (nc67-uf89), City of New York\",\n \"url\": \"https://data.cityofnewyork.us/City-Government/Open-\n","type":"text"}]}›Thinking…
Done. Summarize: what the product is, example built, pricing, manageability assessment, blockers.
Done — working example at /sandbox/repo/nyc_ticket_facts_example.py (runs in ~2s, stdlib-only Python, no services).
What the product is
NYC Ticket Facts (nycticketfacts.com, by Axion Labs) serves NYC parking-ticket hearing outcomes computed from NYC Open Data: dismissal/reduction rates per violation type and per borough, plus defense/evidence guides. It's a hosted, keyless JSON API — no SDK, none needed.
Example output
The script hits three free endpoints:
GET /api/violations— 80 violation types ranked by dismissal rate (e.g. "No Parking–Taxi Stand": 87.6% dismissed over 16k hearings)GET /api/boroughs— per-borough win rates (Manhattan: 67.2% win-or-reduced on 15M hearings)GET /api/violations/{slug}— per-violation detail (fine, defenses, evidence)
Pricing
- Free tier: all granular endpoints (
/api/violations,/api/violations/{slug},/api/boroughs,/changes.json,/llms.txt) — no key, open CORS, free with attribution (each response embeds aciteobject and signed Ed25519 receipt) /api/bulk— the entire corpus in one response: $25/api/changes?since=...— records changed since a date: $0.02/call- (Unrelated to the API: the site's consumer product is a $12.99 personalized ticket-fighting offer.)
Will this be easy to manage?
Yes. It's plain GET-JSON over HTTPS with no auth, ETag/304 conditional-request support, an OpenAPI 3.1 spec, a changes feed for cache invalidation, and dataset fixity hashes — about as low-maintenance as an integration gets. The only quirk I hit: it 403s requests with no/browser-default user agent from some HTTP clients, so the script sets an explicit User-Agent.
Blockers
None. No local stack, no signup, no long-running steps required.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents