DEV Community

Avery Lin
Avery Lin

Posted on

Count the Tokens Before You Trust the Free Tier

Consider a solo founder shipping a login-page summarizer on a Sunday night. Model calls come from a free tier, and the worker sits on a free server. The invoice is zero, and the demo works.

Then week three arrives. Requests start failing, and nobody can say which job ate the allowance. The founder never wrote down what a single call cost.

That gap is the whole problem. Free tiers fail quietly, not loudly. A ledger turns the silence into a number you can read.

Record the usage the provider already sends

Most model responses carry a usage block. Read it at the call site, and write one line per call. Do not estimate tokens from character counts; that drift compounds across a week.

# ledger.py — one append per model call
import json, os, time, pathlib

LEDGER = pathlib.Path(os.environ.get("LEDGER_PATH", "usage.jsonl"))

def record(job: str, model: str, usage: dict) -> None:
    row = {
        "ts": time.time(),
        "job": job,        # stable label: "summarize", "classify", "reply"
        "model": model,
        "in": int(usage.get("prompt_tokens", 0)),
        "out": int(usage.get("completion_tokens", 0)),
    }
    with LEDGER.open("a", encoding="utf-8") as f:
        f.write(json.dumps(row) + "\n")
Enter fullscreen mode Exit fullscreen mode

Wrap the call once, and every feature inherits the ledger for free.

def run_job(client, job: str, prompt: str) -> str:
    resp = client.chat(prompt)
    record(job, resp.model, resp.usage)   # field names vary by SDK; check yours
    return resp.text
Enter fullscreen mode Exit fullscreen mode

The job label matters more than the totals. It tells you which surface to trim when the projection turns red. A founder with three labels can make a decision in a minute.

Project the burn, then fail the build

A ledger nobody reads is a diary. Add one script that converts the file into days remaining. Run it in CI, so a prompt change cannot quietly double your spend.

# budget.py — project exhaustion, exit non-zero when it is too close
import collections, json, os, pathlib, sys

ALLOWANCE = int(os.environ["ALLOWANCE"])
WINDOW_DAYS = float(os.environ.get("WINDOW_DAYS", "30"))
LEDGER = pathlib.Path(os.environ.get("LEDGER_PATH", "usage.jsonl"))

rows = [json.loads(l) for l in LEDGER.read_text().splitlines() if l.strip()]
if not rows:
    sys.exit("ledger is empty; run a job first")

per_job = collections.Counter()
for r in rows:
    per_job[r["job"]] += r["in"] + r["out"]

total = sum(per_job.values())
span = max(r["ts"] for r in rows) - min(r["ts"] for r in rows)
per_day = total / max(span / 86400, 1.0)
left = ALLOWANCE / per_day if per_day else float("inf")

for job, tok in per_job.most_common(5):
    print(f"{job:20s} {tok:>10,d}  {tok/total:6.1%}")
print(f"burn {per_day:,.0f} tokens/day, {left:.1f} days of allowance left")

if left < WINDOW_DAYS:
    sys.exit(1)
Enter fullscreen mode Exit fullscreen mode

Run it with the number printed on your plan page.

ALLOWANCE=10000000 WINDOW_DAYS=30 python budget.py
Enter fullscreen mode Exit fullscreen mode

MonkeyCode is one option that removes the key-management step here: it lists free model access plus a free server option, and the operator states a 10M free token allowance. Check the current page before you hardcode that figure, because plan terms move and your env var should not.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The point is not the provider. The point is that the allowance lives in an env var, and the ledger decides whether the month survives.

Replay a golden set, not your feelings

Token counts drift when prompts grow. Save twenty real jobs to golden.jsonl and replay them nightly. Compare per-job totals against a stored baseline, and fail when any job grows more than fifteen percent.

python replay.py --jobs golden.jsonl --out run-$(date +%F).jsonl
python compare.py baseline.jsonl run-$(date +%F).jsonl --max-growth 0.15
Enter fullscreen mode Exit fullscreen mode

Start with the top labels from budget.py output. A long system prompt pasted into three features is the usual culprit. Cache that prompt, or shorten it, and the projection relaxes on its own.

Probe the free server before you trust it

Free servers restart. That is normal, and it is not a bug. The bug is keeping your queue in process memory.

# probe.py — deploy on the free server, then hit it twice a day
import json, pathlib, time

MARK = pathlib.Path("/tmp/boot-probe")

if MARK.exists():
    up = time.time() - float(MARK.read_text())
    print(json.dumps({"cold": False, "seconds_since_first_request": round(up)}))
else:
    MARK.write_text(str(time.time()))
    print(json.dumps({"cold": True, "note": "first request since boot"}))
Enter fullscreen mode Exit fullscreen mode

A cold: true response means the process started fresh since your last check. Two of those in a week is a design constraint, not an incident. Move the queue to an external store, and test by deleting every local file before boot.

A small decision table for a zero bill

Situation Free hosted model access Free server option
Bursty, low volume Fits; ledger every call More setup than it saves
Needs your own package or binary Awkward Fits, if state is external
Long batch job Watch for mid-run cutoffs Watch for restarts
Paying customers with an SLA Not yet Not yet

The founder in the opening story needed one column, not both. Pick the column that matches this week's traffic, and revisit the table when traffic doubles.

Where this stops

A ledger measures spend, never quality. Keep a small eval set beside golden.jsonl, or you will ship a cheaper wrong answer.

Usage fields can be missing or rounded by the client library. Treat the numbers as a close estimate and log the raw response when a job looks strange.

Every call now costs one local write. On a free server with a slow disk, batch the appends or buffer them in memory for a short window.

Skip this approach if you sell a service with an uptime commitment. Skip it for regulated data that must stay inside a known region. Skip it for jobs where a restart mid-run costs more than the compute you saved. A weekend project, a prototype, or a single-founder product fits; anything with a contract attached does not.

If a zero bill is the constraint this month, one afternoon on the free access and free server is worth the test. Bring the ledger with you.

Top comments (0)