You've probably read a dozen posts about free AI tiers and another dozen about serverless cron jobs. What's rarely shown is a complete small project that uses both together end to end. This is that case study: a tiny reliability bot that polls JSON endpoints, classifies failures with an AI model, and posts a short incident summary to a channel. It ran for a weekend on a free server with MonkeyCode's free model access, and the most interesting lesson wasn't about uptime at all.
Background
A friend's hobby API kept returning 500s at odd hours, but the logs were too long and too boring to read manually. I needed something to answer one question: what broke while I was sleeping? The constraint list was familiar for a side project: no budget, no always-on laptop, no desire to configure a full observability stack. That meant the polling script had to live somewhere free, and the summary generation had to cost nothing.
MonkeyCode's free tier happened to include both a model playground and a free server runner. That was enough to build the whole thing without opening a wallet. For the AI step I used MonkeyCode's free model access, and the server option ran the cron loop. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Goal
Define the success criteria before touching code. The bot needed to do three things:
- Poll five JSON endpoints every five minutes from a free server.
- Detect any endpoint that returned a non-200 status or took longer than three seconds.
- Send a one-block message containing the failing endpoint, the error, and a possible root cause inferred from response patterns.
Everything else was optional. No database, no dashboard, no fancy alert routing. The entire project had to fit in a single Python file plus a schedule config.
Implementation
The first version was a plain script with loops and conditional checks. Here's the core polling function:
import asyncio
import aiohttp
import json
ENDPOINTS = [
"https://api.example.com/health",
"https://api.example.com/users/latest",
"https://api.example.com/search?q=test",
]
async def poll_once(session, url):
try:
async with session.get(url, timeout=3) as resp:
body = await resp.text()
return url, resp.status, resp.elapsed.total_seconds(), body[:200]
except Exception as exc:
return url, 0, 0.0, f"exception={exc.__class__.__name__}"
async def run_poll():
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(*(poll_once(session, u) for u in ENDPOINTS))
failures = [r for r in results if r[1] != 200]
if not failures:
return # all healthy, skip the AI call
return failures
The important choice was to skip the AI call entirely if everything was healthy. That kept the token consumption near zero during the long calm hours and made the only real expense happen during actual incidents.
When failures existed, the screenshot-style payload went to the model with a strict prompt:
Here is a list of failing endpoints with status codes and response excerpts.
For each endpoint, suggest one likely root cause:
- if the status is 503 or the body mentions database timeout, say "DB saturation"
- if the status is 404 or 422, say "bad request" and quote the validation message
- otherwise say "unknown" and include the raw error snippet
Return JSON only, no markdown.
The model output was parsed and formatted into a concise message. The free server's scheduler ran run_poll every five minutes using a cron-like config. No request ever hit my local machine, and the whole bot stayed under a single directory with no external secrets.
Results
Over 48 hours the bot recorded 14 incident windows. The breakdown:
- 10 were short blips (one failed poll, recovered by the next)
- 3 were sustained timeouts on a third-party search API
- 1 was a true 500 caused by a user-supplied ID that broke a query
The AI classification got the first two categories right every time. It also correctly guessed the database saturation story behind the search timeouts because the response body contained a connection pool exhausted phrase. For the single 500, the model did not find the root cause, but it did quote the exact SQL error from the response, which saved me a minute of searching.
Token usage stayed within the free tier's generous allowance; I deliberately avoided inventing my own benchmark, but the pattern of skipping healthy checks is what kept costs effectively flat. The free server never restarted, and the only downtime was my own laptop's network when I forgot to watch an event.
Lessons Learned
Free tiers are enough for experiments, but only if you design for rare triggers. If I had sent every poll result to the model, the bill would have been nontrivial and the noise would have buried the signal. The conditional invocation turned an AI feature into a surgical tool.
Prompts need a decision table, not vibes. The prompt above works because it maps status codes and keywords to root causes. Without that mapping, the model returned vague prose that read like a horoscope. Write prompts the way you write tests: with expected inputs and outputs.
A free server changes where you can iterate. Once the polling loop lived remotely, I could tweak thresholds and redeploy without keeping a terminal open. The deployment friction was nearly zero, which made me willing to experiment. This is the real value of a free server: it removes the psychological barrier to shipping a boring but useful automation.
Who Should Not Use This Approach
This setup is not a production monitoring system. It has no pager escalation, no alert deduplication, and no retention. If you run a business service that people pay for, spend money on a real uptime product. Use this case study as a template for side projects, internal tools, or a weekend lesson in combining AI with automation.
Final Reproducible Artifact
The full prototype is 80 lines of Python plus a cron line. For your own version, change the endpoint list, adjust the prompt rules, and run the script once locally before scheduling it on the free server. That five-minute local test is what separates a reliable bot from a mystery that only fails while you sleep.
Top comments (0)