Before you move a workload onto free capacity, run it there once and write down what it costs you in wall-clock and attention. Tokens are the only line item that goes to zero. Queue wait, retries, and review minutes all survive the move — and they usually get bigger.
You already know the price per token. What you don't know is tokens per accepted output, attempts per accepted output, and how long a job sits before anyone touches it. Budget decisions made without those three numbers are guesses wearing a spreadsheet.
So the useful question about a free tier isn't "how much can I run for nothing". It's "what can I learn for nothing". Free model access and a free server are excellent for the second question and misleading for the first, because a free host and a free endpoint let you observe the shape of your pipeline at zero marginal price while telling you almost nothing about throughput you can promise someone.
Record every attempt, not every run
The artifact below is deliberately boring. One JSON line per attempt, written before anything else can fail. Python 3.10+, standard library only.
# ledger.py - one row per attempt, nothing clever
import json, os, time
from datetime import datetime, timezone
LEDGER = os.environ.get("LEDGER", "ledger.jsonl")
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds")
def run_job(job_id: str, prompt: str, call, max_attempts: int = 3):
"""call(prompt) -> object with .prompt_tokens/.completion_tokens, or raises."""
for attempt in range(1, max_attempts + 1):
queued = _now()
t0 = time.monotonic()
started = _now()
try:
out = call(prompt)
status, err = "ok", None
except Exception as exc:
out, status, err = None, "error", type(exc).__name__
with open(LEDGER, "a", encoding="utf-8") as fh:
fh.write(json.dumps({
"job_id": job_id,
"attempt": attempt,
"queued_at": queued,
"started_at": started,
"ended_at": _now(),
"wall_s": round(time.monotonic() - t0, 3),
"prompt_tokens": getattr(out, "prompt_tokens", None),
"completion_tokens": getattr(out, "completion_tokens", None),
"status": status,
"error": err,
}) + "\n")
if status == "ok":
return {"job_id": job_id, "attempts": attempt}
time.sleep(2 ** attempt)
return None
One rule matters more than the code: turn off your SDK's automatic retries before you run this. If the client retries behind your back, the ledger records one attempt where four happened, and your retry amplification silently reads 1.00 while your bill disagrees.
Four numbers, then a decision
# report.py - the numbers that actually move a budget
import json, statistics
from collections import defaultdict
from datetime import datetime
rows = [json.loads(l) for l in open("ledger.jsonl", encoding="utf-8") if l.strip()]
jobs = defaultdict(list)
for r in rows:
jobs[r["job_id"]].append(r)
accepted = [j for j, rs in jobs.items() if any(r["status"] == "ok" for r in rs)]
n = max(len(accepted), 1)
tokens = sum((r["prompt_tokens"] or 0) + (r["completion_tokens"] or 0) for r in rows)
waits = [(datetime.fromisoformat(r["started_at"])
- datetime.fromisoformat(r["queued_at"])).total_seconds() for r in rows]
walls = [r["wall_s"] for r in rows]
p = lambda xs, q: sorted(xs)[min(int(len(xs) * q), len(xs) - 1)] if xs else float("nan")
print(f"accepted {len(accepted)} / {len(jobs)}")
print(f"attempts/accepted {len(rows) / n:.2f}")
print(f"tokens/accepted {tokens / n:,.0f}")
print(f"queue wait p50/p95 {p(waits, 0.5):.2f}s / {p(waits, 0.95):.2f}s")
print(f"wall time p50/p95 {p(walls, 0.5):.2f}s / {p(walls, 0.95):.2f}s")
Now the arithmetic. Let attention cost one dollar per minute. If a free run needs three extra minutes per accepted output for re-driving and review, you have spent three dollars of attention to save whatever the tokens cost. If your paid cost per accepted output sits under that number, free capacity is the more expensive option and the invoice simply arrives as a calendar instead of a statement.
That comparison is the whole method. You are not comparing token prices; you are comparing tokens-into-attention against money-into-tokens.
Reading the calibration
| What the ledger says | Reading | Move |
|---|---|---|
| attempts/accepted ≈ 1.0, under 5 min attention per output | free tier is a fine development loop | stay, re-measure monthly |
| attempts/accepted > 1.5, driven by rate-limit errors | attention scales with attempts, not outputs | move volume to paid, keep free for tests |
| p95 wall time swings between runs of the same job | noise bigger than signal | measure longer, commit nothing yet |
| free host CPU pegged near 100% | you measured the host, not the model | split host cost out before deciding |
| over 15 min human review per accepted output | attention dominates the bill | free tokens save the smallest line |
If you want to run that first pass without paying for tokens while you're still measuring, MonkeyCode's free model access and free server option are a reasonable place to point it — bring your own ledger and keep your own numbers.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Where this measurement lies to you
Quotas and rate limits move. Check the current terms on the day you measure, not a figure you read in a post — including this one. A number that was true last quarter is a budget error with good intentions.
Your own timestamps cannot see server-side queueing. started_at is when you handed the request over; if the endpoint held it for forty seconds first, that time lands in wall_s and nowhere else. Report it, don't explain it away. A quiet measurement window also tells you nothing about your Monday burst, where rate limits change your effective concurrency and you end up measuring a different system than the one you ship.
And if the free server is CPU- or memory-bound, the calibration is about that host. Model latency and host latency are two bills; don't merge them into one conclusion.
Who should skip this
Anyone with a hard latency SLO, data that cannot leave your boundary under the provider's terms, or a workload whose retry behavior is driven by a client you don't control. In those cases the measurement itself is the risk, and a short paid pilot with a known ceiling is the cheaper way to learn the same thing.
For everyone else: run the free tier as an instrument once. Then decide with a number instead of a feeling.
Top comments (0)