Build a Token Ledger Before You Burn Through a Free Model Tier A developer built a stateful token budget guard to prevent free model endpoints from exhausting their allowance during retry loops. The tool checks projected costs before API calls, records actual usage afterward, and refuses to send requests that would exceed the budget. It is designed as a disposable first pass for testing OpenAI-style chat completions on free endpoints like MonkeyCode's. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Why this is worth reading: a free model endpoint with a large token allowance is a good place to validate a new CLI workflow, but it can burn through the allowance in a single retry loop before you notice. I built a small stateful budget guard that checks the projected cost before the call, records actual usage after the call, and refuses to touch the ledger when the endpoint sends an unexpected response. It works as a disposable first pass on a free endpoint and leaves you a clean exit when the shape changes. MonkeyCode's outreach describes an open-source project with a free model route and a free hosted server. I do not treat either as a permanent dependency. I treat them as a test target: an endpoint I can call without a contract while I am still changing prompts, timeouts, and schemas. The tool below is independent of MonkeyCode's exact model list; it assumes only an OpenAI-style chat completion path and usage accounting in the response. Swap one function if the free server does not follow that shape. Most model dashboards report aggregate usage after the fact. That is enough for casual work, but it is not enough when you wire an endpoint into a loop. I have seen two avoidable failures in my own drafts. A retry-on-timeout wrapper restarted a slow request four times before the first response arrived, multiplying total token spend. A long context buffer kept sending the same 6k-token history on every turn because I forgot to trim old messages. The dashboard showed the total drop, but not which call caused it. A local ledger fixes that by refusing to send the request when the projected total exceeds the budget. It does not replace the provider dashboard. It makes the decision before the endpoint gets a chance to consume tokens. The script below does three jobs: Preflight is deliberately rough: prompt bytes divided by four, plus the requested max response tokens, plus a 15 percent margin. That is not tokenizer-accurate for non-English text or code-heavy prompts, but it is intentionally conservative because the goal is to stop accidental waste, not to replace metering. If you need precise preflight numbers, add a local tokenizer for the model you are calling. python -m venv .venv && source .venv/bin/activate pip install httpx .env MONKEYCODE BASE URL=https://your-free-server.example.com/v1 MONKEYCODE API KEY=your-key MODEL NAME=the-current-free-model TOKEN BUDGET=30000000 MAX RESPONSE TOKENS=256 TIMEOUT S=30 LEDGER PATH=token ledger.json Then source it and call: set -a; source .env; set +a python budgeted call.py 'Summarize this connection error in one sentence.' budgeted call.py : python /usr/bin/env python3 import json import os import sys import time from pathlib import Path import httpx BASE URL = os.getenv 'MONKEYCODE BASE URL', '' .rstrip '/' API KEY = os.getenv 'MONKEYCODE API KEY', '' MODEL = os.getenv 'MODEL NAME', '' BUDGET = int os.getenv 'TOKEN BUDGET', '30000000' LEDGER = Path os.getenv 'LEDGER PATH', 'token ledger.json' MAX TOKENS = int os.getenv 'MAX RESPONSE TOKENS', '256' TIMEOUT S = float os.getenv 'TIMEOUT S', '30' def load ledger : if LEDGER.exists : data = json.loads LEDGER.read text return int data.get 'used', 0 return 0 def save ledger used : tmp = LEDGER.with suffix '.tmp' tmp.write text json.dumps {'used': used, 'updated': int time.time }, indent=2 tmp.replace LEDGER def preflight estimate prompt, max tokens : prompt tokens = len prompt.encode 'utf-8' // 4 return prompt tokens + max tokens def run prompt : if not BASE URL or not API KEY or not MODEL: sys.exit 'Set MONKEYCODE BASE URL, MONKEYCODE API KEY, and MODEL NAME first.' used = load ledger estimate = preflight estimate prompt, MAX TOKENS margin = int estimate 0.15 projected = used + estimate + margin if projected BUDGET: sys.exit f'Blocked: projected={projected} used={used} budget={BUDGET}. Shorten the prompt or raise the budget.' response = httpx.post f'{BASE URL}/chat/completions', headers={'Authorization': f'Bearer {API KEY}'}, json={ 'model': MODEL, 'messages': {'role': 'system', 'content': 'Answer concisely. Return JSON only when asked.'}, {'role': 'user', 'content': prompt}, , 'max tokens': MAX TOKENS, }, timeout=TIMEOUT S, response.raise for status payload = response.json usage = payload.get 'usage' or {} total = usage.get 'total tokens' if total is None: total = int usage.get 'prompt tokens', 0 + int usage.get 'completion tokens', 0 if total <= 0: sys.exit 'Endpoint returned no usable token count; ledger was not updated.' used += total save ledger used content = payload 'choices' 0 'message' 'content' print json.dumps { 'text': content, 'total tokens': total, 'used': used, 'remaining': BUDGET - used, }, indent=2 if name == ' main ': run sys.argv 1 if len sys.argv 1 else 'Reply with the word pong.' Use a failure fixture that does not hit the real endpoint. The expected result is a non-zero exit and an unchanged ledger. MONKEYCODE BASE URL=http://127.0.0.1:9 python budgeted call.py 'ping' If you want a decision table for a canary suite, keep the checks tiny: | Scenario | Expected exit | Ledger change | |---|---|---| | Missing base URL or model | non-zero | none | | Unreachable endpoint or timeout | non-zero | none | | Projected spend over budget | non-zero | none | Valid response with usage.total tokens | zero | used increases | | Response without a usable token count | non-zero | none | I run this once before I allow any larger script to call the endpoint. A failed run tells me which part of the integration changed instead of leaving me to guess from a balance chart. For a solo build, the free server is most useful as a canary target, not as a permanent backend. I point this script at the free route first, keep the model name in an environment variable, and store all results in the local ledger. If the endpoint changes one day, the only change is a URL or model name. If the endpoint reports different usage fields, the script stops instead of silently undercounting. I also set a hard mental exit: if the free endpoint is slow enough that I need a timeout above 30 seconds, it is not ready for the actual CLI. The ledger cannot fix latency; it only prevents it from getting expensive while I measure. The reference I was given describes a free tier with a 30,000,000-token allocation and a free server option. I do not verify quota pages as part of a code article, so I keep the number as TOKEN BUDGET in an environment variable rather than hard-coding it. Check the current page before you rely on that number; if the allocation is different today, the script does not need to change. Do not use this local ledger if you need concurrent workers sharing one budget, hard SLOs on latency, audit trails, or compliance review for private data. A single JSON file is not concurrency-safe, and a free endpoint is the wrong home for sensitive prompts. Use the ledger as a canary, not as your production accounting system. If you run this against a free server, tell me which usage fields the response actually returned. That determines whether the missing-usage guard is protecting you or getting in your way: total tokens only, split prompt tokens and completion tokens , or something else entirely. If you have a MonkeyCode free server route, plug it into this script first; if not, the same budget guard works with any endpoint that returns usage.