Last Saturday I decided to build a tiny AI tool for my own blog. The plan was simple: take a Markdown file, summarize it, and post the summary to a Telegram channel. Total budget: $0.
I almost blew it.
Here's what happened, what I cut, and how I tested everything without spending a cent.
The Original (Too Big) Plan
The first version had three features:
- Summarize any Markdown file.
- Auto-detect broken links in the file.
- Generate a
#TODOlist from the content.
Sounds small. It wasn't.
Link checking needs network calls, URL parsing, and retry logic. TODO extraction needs reliable prompt tuning. Summaries alone are usually fine, but all three together meant a weekend of debugging.
I cut features 2 and 3 on Friday night.
The Scope You Actually Need
Final scope:
- Read a Markdown file from disk.
- Send it to an LLM with a strict summary prompt.
- Save the summary to
output.md. - Exit with a clear error if the file is too large or the API call fails.
Fifteen lines of Python. One config file. No database.
Why Free Tiers Feel Like a Trap
Free model access usually comes with rate limits. If your code retries too aggressively, you burn through quota in minutes.
I used MonkeyCode's free model access for this project. It worked for my weekend experiment, but I treated the quota as a real constraint.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
That constraint shaped my code. I added one retry. Not five. I also set a hard timeout so a stuck request wouldn't hang forever.
The Code
Here's the core script. It reads a file, checks token length roughly, and calls the model.
import os
import sys
import time
import requests
API_URL = os.getenv("MC_API_URL", "https://api.monkeycode.example/v1/chat") # Replace with real endpoint
API_KEY = os.getenv("MC_API_KEY")
MAX_CHARS = 12_000 # conservative gate for a 4k-context model
def summarize(file_path: str) -> str:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
if len(content) > MAX_CHARS:
raise ValueError(f"File too long: {len(content)} chars. Split it first.")
payload = {
"model": "free-default", # placeholder, check current model name
"messages": [
{"role": "system", "content": "Summarize in 5 bullet points. Keep facts only."},
{"role": "user", "content": content},
],
"temperature": 0.3,
}
headers = {"Authorization": f"Bearer {API_KEY}"}
for attempt in range(2):
try:
r = requests.post(API_URL, json=payload, headers=headers, timeout=30)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"]
except requests.exceptions.RequestException as e:
if attempt == 0:
time.sleep(2)
continue
raise RuntimeError(f"API failed after retry: {e}") from e
return "" # unreachable but keeps mypy happy
if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit("Usage: python summarize.py path/to/file.md")
try:
result = summarize(sys.argv[1])
except (ValueError, RuntimeError) as e:
sys.exit(f"Error: {e}")
with open("output.md", "w", encoding="utf-8") as f:
f.write(result)
print("Done. See output.md")
Notes:
-
MAX_CHARSis a crude token proxy. It protects free quota from runaway inputs. - One retry only. You do not want an infinite retry loop against a metered API.
- The endpoint and model name are placeholders. Check the current docs before copying.
Testing Like a Cheap Engineer
You can't test a metered API with real calls forever. I wrote three tests that cost nothing.
- Small file test – a 200-word sample. Verify the output is 5 bullets.
- Too-large file test – generate a 15,000-char file. Expect the script to fail fast with a clear message.
-
Timeout test – point
API_URLto a local dummy server that sleeps 40 seconds. Verify your 30s timeout fires and the retry path works.
I ran test 3 with a tiny Python socket server. Took five minutes to write.
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
class SlowHandler(BaseHTTPRequestHandler):
def do_POST(self):
time.sleep(40)
self.send_response(200)
self.end_headers()
HTTPServer(("127.0.0.1", 9999), SlowHandler).serve_forever()
Then run your script against http://127.0.0.1:9999 and watch it fail after ~30 seconds. That's the behavior you want.
Free Server: Great for Demos, Bad for Production
MonkeyCode's free server option let me host this tool for a demo. Fine for a weekend. Not fine for a service that needs uptime guarantees.
I don't know the exact quotas or hardware behind the free server. Don't trust anyone who quotes numbers without a source. For this project, the free server was enough to run the script on a cron schedule for two days.
If you need persistence, monitoring, or a stable domain, spend money on a $5 VPS. Free servers are for experiments.
Who Should Not Use This Approach
Skip this setup if you:
- Process documents longer than 12,000 chars without chunking.
- Need consistent low-latency responses.
- Have a team that depends on the tool being up.
- Want a permanent public endpoint.
This workflow is for learning, prototyping, and demos. It is not a production architecture.
What I Skipped (and Why)
- Retries with exponential backoff – wasted quota on flaky networks.
- Streaming responses – more code, no visible benefit for 5 bullets.
- Caching – each file is unique, cache hit rate would be near zero.
- A web UI – CLI was enough to prove the idea.
Skipping is a feature. The final build took about four hours including tests.
The Result
I now have a script that turns any small Markdown file into a tight summary. It cost me $0 in API fees and showed me exactly where free tiers hurt: input size limits and retry behavior.
If you try this, start with the dumbest version that works. Measure where your quota actually goes. Then decide if the "free" path is worth it.
My next step: add chunking so I can summarize my 9,000-word READMEs. That's a different weekend.
Top comments (0)