At the beginning of 2026, I found myself annoyed at the Chicago Transit Authority (CTA) for not having a yearly wrapped. Transit agencies in other cities like Washington DC have it, so why couldn't Chicago? My friends in DC get to easily brag on social media about the number of transit trips they take in a given year, so why can't I? I decided to try and figure out a way to grab my transit data in an automated fashion and generate yearly summary visuals for it. After a bit of trial and error I was able to build something that worked, and the code is here. I'm moving from Chicago this month so my 2026 data is complete and I'm sharing it now:
Fetching my Ventra card usage#
The CTA (along with Pace and Metra) all use Ventra, a transit fare system for the Chicago metro area. When paying for transit, you tap a Ventra card on a fare reader on the bus or at a train station. On the web you can visit ventrachicago.com to view recent usage of your Ventra card (up to your 100 most recent card transactions). I figured the easiest way to grab my data and do something with it would be writing a script that used my Ventra credentials to log into my account and fetch my usage data via the same APIs the website used. In other words, grabbing my usage data just like I would using a web browser.
When writing a script, figuring out how auth worked took some trial and error. The API that returns Ventra usage history wants two things: proof you're logged in, and an anti-forgery token. Neither is straightforward to get, and there's no /login endpoint that hands back an auth token. Ventra's site runs on ASP.NET Web Forms, so logging in means sending a form-encoded postback to the homepage with the framework's callback fields set alongside your credentials. What you get back is a set of session cookies, and those are what authenticate you from then on. The anti-forgery token was trickier, because no API returns it. It's a hidden input (hdnRequestVerificationToken) sitting in the page HTML, so you have to load your account page and regex it out of the source. Only then does the API call to fetch usage data go through, with the cookies and the token passed as headers. Once I figured this out, I set up a weekly cron entry on my home server that logged into my Ventra account, fetched my recent card usage, and appended it to a JSON file. Once I had my card usage I was able to write logic to do basic data analysis (ex. what day do I ride transit the most?), generate pretty visuals, and then have a script on my home server regularly email myself my weekly and monthly transit usage.
Some random things I learned from my Ventra data#
- Some station names in the data predate the CTA's 1993 adoption of a color naming system for rail lines, still carrying their old branch names. For example, the Blue Line's Oak Park station is called Blue-OakPark_Congressand the Pink Line's Polk station is calledPink-Polk_Douglas
- The downtown subway stations record the cross streets of the entrance I tapped at instead of the station name. Red Line taps in the State Street subway can show up as Red-SS Adams-JacksonorRed-SS Jackson-VanBuren, and Blue Line taps in the Dearborn subway show up asBlue-DB Monroe-Adams. This means a single station can appear under multiple names depending on which end I walked in
- Ventra doesn't record bus stops. For rail taps Ventra records the line1 and station (ex. Jarvis on the Red Line) but simply records the route for buses. It sort of makes sense since buses are on the move, but it would have been neat if Ventra had a way to at least record the GPS coordinates of bus taps so I could have figured out a way to tie them to specific bus stops
- Bus taps occasionally have routes that are mislabeled as 0 DeadheadorCTA Bus Default. I saw this happen with a few taps I made on the 72 bus route earlier in the year (and the buses I tapped on were definitely in service), so I'm guessing these were data quirks where the system couldn't determine what route the bus I was on belonged to
- I slightly messed up my Total spentcalculation. I wanted to track how much I spent on transit and I typically add cash to my Ventra card instead of buying a pass. Each Ventra transaction includes how much money the usage cost ($2.25 for bus, $2.50 for rail, $0 for transfers), so I assumed I could solely sum up those values. However I used a monthly pass in May, and learned Ventra records every transaction as $0 when a pass is active. So my total spent for 2026 is actually$397 + $75 = $473. Someone feel free to open a PR to fix this
In conclusion#
Part of my hope in making this post is that it shows the CTA and Ventra that building an official wrapped feature would be really cool! I think it would be exciting and well received by transit users in Chicago. Also the day I am writing this post is September 1, 2026, which is also the same day the new Northern Illinois Transit Authority officially begins – maybe having a new regional transit authority with a mandate to better connect transit agencies in the region will somehow make it easier for a wrapped feature to be built (maybe even for Pace and Metra too) 🤞
At the very least, I hope other transit users will use this code to better keep track of their transit usage.
Author note#
If anyone from the CTA or Ventra somehow stumbles upon this post: hi, I hope you're not mad that I figured out a way to export my own Ventra data. If you are, please get in touch with me and I can delete the code I wrote and redact the technical details from this post.
Footnotes#
-
Sort of. Tapping at a rail station serving multiple lines either includes just one of the lines (ex. Fullerton is recorded as Red-Fullertonand doesn't mention the Brown or Purple lines) or justLoop(Adams/Wabash is recorded asLoop-Adams/Wabash). ↩