DEV Community

Quinn Li
Quinn Li

Posted on

The Day I Trusted an Agent With a Free API Key

Last week, a DEV thread asked: "What do you do while AI codes?" My answer: watch the meter. Because last month, I stopped watching for one afternoon.

I pointed an agent at a free model endpoint. The endpoint came with a token allowance and a free server for testing. The agent failed on a malformed payload. So it retried. Then it retried again. Forty calls later, the allowance was gone. The agent did not care. The server did not care. Only my calendar did.

This is a letter to the me who built that stack without a gate. If you are about to do the same, read it.

Disclosure

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option for experiments. I used both to test the pattern below. The code is generic. It works with any OpenAI-compatible chat completions endpoint.

Why an agent needs a meter

Agents treat context as free. They do not see quotas. They do not see billing cycles. They only see errors and timeouts.

A human sees the bill. That is the missing control.

Recent threads ask why "AI promoted every developer to reviewer." My review found the real gap: nobody reviews spending. We review code, not token burn.

The budget gate

The solution is a small reverse proxy. It sits in front of the model API. It checks the daily budget before every call. It returns 402 when the budget is exceeded. Then it records real usage after the call.

This is not magic. It is a middleman.

Here is the full gate in Python. It uses FastAPI and SQLite.

# budget_gate.py
import os
import sqlite3
import datetime

import httpx
from fastapi import FastAPI, Request

app = FastAPI()

DB = os.getenv("USAGE_DB", "usage.db")
DAILY_LIMIT = int(os.getenv("DAILY_TOKEN_LIMIT", "100000"))
UPSTREAM = os.getenv("UPSTREAM_URL")

if not UPSTREAM:
    raise RuntimeError("Set UPSTREAM_URL to your model's chat completions URL")

def _db():
    con = sqlite3.connect(DB)
    con.execute("CREATE TABLE IF NOT EXISTS usage (day TEXT, tokens INTEGER)")
    return con

def used_today() -> int:
    con = _db()
    row = con.execute(
        "SELECT COALESCE(SUM(tokens), 0) FROM usage WHERE day = ?",
        (datetime.date.today().isoformat(),),
    ).fetchone()
    con.close()
    return int(row[0])

def estimate(payload: dict) -> int:
    chars = sum(
        len(m.get("content", ""))
        for m in payload.get("messages", [])
    )
    return max(1, chars // 4)

@app.post("/v1/chat/completions")
async def gate(request: Request):
    payload = await request.json()
    guessed = estimate(payload)

    if used_today() + guessed > DAILY_LIMIT:
        return {
            "error": {
                "message": "daily token budget exceeded",
                "used": used_today(),
                "limit": DAILY_LIMIT,
            }
        }, 402

    headers = {"Authorization": request.headers.get("Authorization", "")}
    async with httpx.AsyncClient() as client:
        upstream = await client.post(UPSTREAM, json=payload, headers=headers)

    data = upstream.json()
    usage = data.get("usage", {})
    tokens = usage.get("total_tokens", guessed)

    con = _db()
    con.execute(
        "INSERT INTO usage (day, tokens) VALUES (?, ?)",
        (datetime.date.today().isoformat(), tokens),
    )
    con.commit()
    con.close()

    return data
Enter fullscreen mode Exit fullscreen mode

How to run it

  1. Save the file as budget_gate.py.
  2. Install dependencies: pip install fastapi uvicorn httpx.
  3. Set the real endpoint: export UPSTREAM_URL="https://your-provider.example/v1/chat/completions".
  4. Set a sane daily cap: export DAILY_TOKEN_LIMIT=50000.
  5. Start the gate: uvicorn budget_gate:app --host 0.0.0.0 --port 8000.
  6. Point your agent at http://localhost:8000/v1.

The agent never sees a normal reply after a 402. Most agents stop on HTTP errors. The retry loop dies before the budget does.

Test the gate

Run two calls. The first should pass. Then lower the limit to one token. The second should return 402.

curl -s http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer test" -d '{"messages":[{"role":"user","content":"ping"}],"max_tokens":5}'

sqlite3 usage.db "SELECT SUM(tokens) FROM usage WHERE day = date('now');"
Enter fullscreen mode Exit fullscreen mode

Debugging the retry loop

When your agent retries, don't patch the agent. Check the boundary. Run the same call with curl. If curl works, your agent sends a different payload. If curl fails, inspect the status code.

  • 402 means budget exhausted. Increase the cap or wait for reset.
  • 400 means malformed payload. Fix the prompt schema.
  • 429 means rate limited. Sleep before retry.
  • 401 means bad key. Rotate it.

The free server temptation

The free server removes local GPU pain. It also hides the burn. You no longer watch your fan spin. You watch a dashboard only if you build one. This gate is that dashboard.

When this gate makes sense

Workload Use gate? Why
Interactive coding Yes Agents retry on any error.
Batch processing Maybe A queue with a stop condition may be cleaner.
One-off script Yes It costs 30 seconds to add.
Production traffic No You need a real rate limiter and auth.

Limitations

The estimate is crude. It divides characters by four. English tokens average roughly four characters. Code and JSON vary.

SQLite is fine for one user. A team server needs Postgres or Redis.

This gate checks tokens only. It does not block filesystem access, tool calls, or prompt injection. It is not a security boundary.

Who should not use this

Skip the gate if your agent is a single curl. Skip it if your company already has a proxy. Skip it if you need sub-millisecond latency.

A Python middleman adds maybe 10 milliseconds. That matters only for tight loops.

The letter I would send back

Dear past me: the free tier is not a license to forget. The agent will not stop. The server will not stop. Only you can make the meter.

Run the gate. Set the cap. Check the log.

If you start a free MonkeyCode stack, bring this gate. It turns "free" into "metered free." Your future self will not burn a day on a quota error.

Top comments (0)