I Automated Checking for Driver's License Appointments
/ 6 min read
I need an appointment to renew my driver’s license. The municipal booking site here in Mar del Plata, Argentina, where I live, is one of those old ASP.NET WebForms apps, the kind that feels frozen in 2011, and every time I opened it I got the same banner:
NO HAY TURNOS DISPONIBLES para la opción seleccionada. De Lunes a Viernes a las 00.00hs se habilitan nuevos turnos.
Roughly: “No appointments available. New slots open Monday to Friday at 00:00.”
Except in my experience that’s not really true, or at least not the whole truth. If you’ve ever tried to book one of these you probably know the drill. They say new slots drop at midnight but in practice they seem to show up at some point during the day, and the banner just disappears once they do. Last time I went through this, for the first part (yeah, of course, it has like four parts…) of this same procedure, the slots didn’t open at 00:00 at all, they opened at some random hour in the middle of the day. There’s no published schedule for it because I don’t think there really is one, it’s just whatever the backend feels like doing. I got tired of opening the tab every couple of hours just to see the same message, so I figured this was a job for a script instead of a job for me.
I’ve actually done this before
This isn’t the first time either. A while back I did basically the same thing for a different government procedure here in Argentina, also a legacy form fighting me the whole way. That time I did it fully by hand, but it wasn’t even that involved, just reading the page source was enough. No need to open the Network tab and watch requests fly by, the HTML itself already had everything: the hidden __VIEWSTATE and __EVENTVALIDATION fields, the form field names, all of it right there. From that I figured out the page does full ASP.NET postbacks, every dropdown change fires __doPostBack, resubmits the whole form along with those hidden tokens, and the server re-renders everything from scratch. I wrote a Python script with requests and BeautifulSoup that replayed those postbacks and checked the response for the “no slots” text. It worked fine but took me a good chunk of an afternoon poking around hidden form fields to get there.
Same idea, but this time with Claude Code
This time I used Claude Code instead of doing all that DOM digging myself, and honestly the speed difference was pretty noticeable. I described the problem, the banner, the two dropdowns you have to pick before anything even shows up, the fact that this all seems to happen before the captcha or the “Consultar” (“Check”) button matter at all. It drove an actual browser against the live site, watched the network requests while selecting “Licencias de conducir” (driver’s licenses) and then the specific procedure, and confirmed both of those trigger full postbacks back to the same URL. No captcha needed just to check if there’s availability, which matches what I noticed on my own the first time around. Then it found the actual signal: when there’s nothing available, the response HTML has a <div id="MainContent_pnlSinTurnos"> in it, “sin turnos” being “no slots”, and when slots do exist, that div just isn’t there.
From there it put together a small script that:
- Loads the page once and grabs the current
__VIEWSTATE,__EVENTVALIDATION, and whatever value every other form field currently has. - Replays the postback for picking the department.
- Replays the postback for picking the specific procedure I care about.
- Checks if that
pnlSinTurnosdiv shows up in the final HTML.
The part that’s easy to miss written out like that is the token handling. Every response carries a fresh __VIEWSTATE and __EVENTVALIDATION, and the next postback has to send those back, not the ones you started with. Reuse a stale pair and the server just rejects the request:
It’s the exact same approach I used manually the first time, pull the form state, replay the postbacks with requests, no headless browser, no captcha involved. Just done in a few minutes instead of an afternoon of squinting at hidden inputs.
Then I just needed it to notify me
The rest of this part is honestly the boring bit, which is kind of the point. Once you can answer “is there a slot or not” with a script, turning that into a background watcher is nothing special. You just need something that runs the check every so often and fires a desktop notification when the answer changes.
I use niri these days (I was on Sway before that), so this went into spawn-at-startup in ~/.config/niri/config.kdl, pointing at the script’s venv and checking every 30 minutes:
spawn-at-startup "/home/daniel/repos/turnero/env/bin/python" "/home/daniel/repos/turnero/check_turnos.py" "--watch" "--interval" "1800"On Sway it would’ve just been an exec line calling the same script. On anything else it’d be a cron job, a systemd timer, whatever, it genuinely doesn’t matter. The idea doesn’t care what window manager or OS you’re on: start something at login that polls on an interval and notifies you when a condition flips. It’s a pattern that comes up more often than you’d think, especially for any site that makes you check back later but doesn’t give you an API, a webhook, or even an RSS feed, which describes most government sites I’ve dealt with.
Why I didn’t just put this on my VPS
My first thought was to run this on my VPS so it’d be checking 24/7 whether my laptop is on or not. I dropped that idea pretty quickly though. The VPS is in Germany, and this is an Argentine municipal site that, like a lot of government infrastructure, sits behind Cloudflare and probably has some opinion about traffic that doesn’t look local. I didn’t want to spend an afternoon figuring out whether a German IP hitting an Argentine appointment form every 30 minutes eventually gets flagged or rate limited. Not a rabbit hole I need right now.
Running it locally sidesteps all of that, and it has a nice side effect too. When the notification actually goes off, I’m sitting right there at my own computer, already logged in, ready to jump into the form and grab the slot before someone else does. With systems like this the gap between “a slot exists” and “a slot is gone” can be a matter of minutes, so being at the keyboard when the alert fires matters more than I expected it to.