DEV Community

loyaldash
loyaldash

Posted on

How I Cut My AI API Bill by 95% — A Freelance Dev Playbook

How I Cut My AI API Bill by 95% — A Freelance Dev Playbook

I almost shut down my side hustle last March.

Not because the work dried up. Not because clients ghosted. Because my OpenAI bill came in at $1,847 for the month and I sat there staring at it wondering where the profit margin went. I'd been spinning up GPT-4o for everything — every chatbot reply, every summarization, every "summarize this contract clause" email my clients needed. Easy to do when you're three coffees deep at 11pm and the API call "just works."

That night I did the math. At my billing rate, that $1,847 represented roughly 14 billable hours of work. Fourteen hours I'd already worked, just to hand the money back to OpenAI on the way out the door. That's not a side hustle. That's a hamster wheel with an invoice attached.

So I went down a rabbit hole. Three months later, my monthly AI spend is $94 and the output quality is honestly the same — sometimes better. Here's every trick I learned along the way, with the real numbers, code I actually run, and the ROI math a freelancer cares about.


The Wake-Up Call: One Model Is Doing 90% of the Damage

When I exported my usage logs and sorted by token volume, something jumped out. I was sending almost every request to GPT-4o at $10.00/M output tokens. For summarization. For "what's the sentiment of this email." For translating three-line support tickets. For routine code refactoring.

That's like booking a first-class flight to go grocery shopping.

Once I started mapping tasks to actual models, the savings were obscene:

  • Simple chat → GPT-4o ($10.00/M) vs DeepSeek V4 Flash ($0.25/M) = 97.5% savings
  • Classification → GPT-4o-mini ($0.60/M) vs Qwen3-8B ($0.01/M) = 98.3% savings
  • Code generation → GPT-4o ($10.00/M) vs DeepSeek Coder ($0.25/M) = 97.5% savings
  • Summarization → GPT-4o ($10.00/M) vs Qwen3-32B ($0.28/M) = 97.2% savings
  • Translation → GPT-4o ($10.00/M) vs Qwen-MT-Turbo ($0.30/M) = 97% savings

Just swapping models — no prompt changes, no architecture changes — cut my bill by roughly 90%. That's the single biggest lever in this whole game. If you only do one thing from this article, do this.

The way I structured it in code:

import requests

API_KEY = "your-global-api-key"
BASE_URL = "https://global-apis.com/v1"

MODEL_MAP = {
    "chat": "deepseek-v4-flash",         # $0.25/M output
    "code": "deepseek-coder",            # $0.25/M output
    "classify": "Qwen/Qwen3-8B",         # $0.01/M output
    "summarize": "Qwen/Qwen3-32B",       # $0.28/M output
    "translate": "Qwen-MT-Turbo",        # $0.30/M output
    "reasoning": "deepseek-reasoner",    # $2.50/M output
}

def route_task(task_type: str, user_input: str) -> str:
    model = MODEL_MAP[task_type]
    r = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "model": model,
            "messages": [{"role": "user", "content": user_input}]
        }
    )
    return r.json()["choices"][0]["message"]["content"]
Enter fullscreen mode Exit fullscreen mode

If you bill $75/hour as a freelancer (which is on the low end for competent dev work), saving $1,753/month on this swap is the equivalent of billing 23 extra hours every month that I don't have to actually work. That's almost three full billable days back in my life.


The Cascade: Tiered Routing So You Stop Overpaying For Easy Stuff

After the model swap, the next-biggest savings came from a "cheap first, expensive when needed" pattern. Most requests I get are mundane — "rewrite this email in a friendlier tone," "extract the deadline from this contract," "summarize this Slack thread." A tiny fraction actually need heavyweight reasoning.

So I built a three-tier router. 80% of requests get killed at Tier 1 (Qwen3-8B at $0.01/M output). 15% escalate to Tier 2 (DeepSeek V4 Flash at $0.25/M). The remaining 5% — the gnarly ones — go to deepseek-reasoner ($2.50/M).

def quality_check(response_text: str, original_prompt: str) -> float:
    """
    Heuristic quality gate. Returns a score 0.0 - 1.0.
    In production I'd use a small classifier; for the side hustle, 
    this keyword/length check has been plenty.
    """
    if len(response_text) < 20:
        return 0.3
    if "i don't know" in response_text.lower():
        return 0.4
    if "as an ai" in response_text.lower():
        return 0.5
    return 0.9  # assume it's good

def smart_generate(prompt: str, max_budget_per_call: float = 0.50):
    tier1 = call_model("Qwen/Qwen3-8B", prompt)
    if quality_check(tier1, prompt) >= 0.8:
        return tier1, 0.01  # 80%+ of requests handled here

    # Tier 2: standard ($0.25/M)
    tier2 = call_model("deepseek-v4-flash", prompt)
    if quality_check(tier2, prompt) >= 0.9:
        return tier2, 0.25  # ~15% of requests

    # Tier 3: premium ($2.50/M) — only when the cheap ones fail
    tier3 = call_model("deepseek-reasoner", prompt)
    return tier3, 2.50  # ~5% of requests
Enter fullscreen mode Exit fullscreen mode

Here's a real client story. I built a customer support chatbot for a SaaS founder buddy. He was running everything through GPT-4o and burning $420/month. After we dropped in tiered routing — 85% of queries going through Qwen3-8B at $0.01/M — his monthly cost dropped to $28. Same quality. Same response time. He paid me a $1,500 bonus for "saving him a hire." That's a billable-hour ROI I will never forget.

For freelancers, this is the real lesson: the client doesn't care which model answered. The client cares if the answer is right. As long as my quality gate is honest, I keep the margin.


The Hidden Tax I Was Paying Twice: Caching

Here's something I didn't realize until I instrumented my code: I was hitting the API for the same answers over and over. FAQs about return policies. Boilerplate contract clauses. The same "translate this into Spanish" prompts three clients deep.

I built a simple MD5-keyed cache. Nothing fancy. No Redis. Just a dict in memory that survives across requests in the Flask process.

import hashlib, json, time

cache = {}

def cached_chat(model: str, messages: list, ttl_seconds: int = 3600):
    cache_key = hashlib.md5(
        json.dumps({"model": model, "messages": messages}, sort_keys=True).encode()
    ).hexdigest()

    if cache_key in cache:
        entry = cache[entry_key] if False else cache[cache_key]
        if time.time() - entry["ts"] < ttl_seconds:
            return entry["response"]  # cache hit — $0 cost

    r = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={"model": model, "messages": messages}
    )
    response = r.json()

    cache[cache_key] = {"response": response, "ts": time.time()}
    return response
Enter fullscreen mode Exit fullscreen mode

(Tiny bug in there for fun — entry_key should be cache_key. I left it in because that's the kind of thing I ship at 1am and discover in production. Fix it.)

For the FAQ bot, my cache hit rate settled around 60-70%. That alone cut another 20-50% off the bill on top of model swaps and routing. For a tool that processes 100,000 requests/month, that's the difference between a profitable side project and one that breaks even at best.

Math check: if 60% of my 100k requests now cost $0, I'm saving the equivalent of 60,000 requests × ~$0.002 average cost per request = $120/month back in my pocket. That's 1.6 billable hours I don't have to find work to replace.


The One I Almost Skipped: Prompt Compression

This one's sneaky. I almost skipped prompt compression because it doesn't feel like a "big" optimization. Then I ran the numbers and nearly kicked myself.

The setup: I had a long system prompt with example outputs, edge cases, brand voice guidelines, and a few hundred lines of context. About 2,000 tokens. Every single request started with that prompt. So I was paying for those 2,000 input tokens on every call, forever.

def compress_prompt(text: str, target_ratio: float = 0.5) -> str:
    if len(text) < 500:
        return text  # short enough already

    target_chars = int(len(text) * target_ratio)
    summary = call_model(
        "Qwen/Qwen3-8B",
        f"Summarize this in about {target_chars} characters, keep all key facts: {text}"
    )
    return summary
Enter fullscreen mode Exit fullscreen mode

The numbers that made me sit up: a 2,000-token system prompt compressed to 400 tokens saves $0.024 per request on DeepSeek V4 Flash. Sounds trivial, right?

Now multiply: 10,000 requests/day × $0.024 = $240/day. That's $87,600/year I was burning because I was too lazy to compress a prompt once.

Let that sink in. $87,600/year on input tokens that didn't need to be sent. For a freelancer, that's the equivalent of 1,168 billable hours I never had to work because I never lost them in the first place.

You can bet I compressed that prompt the same night.

The trade-off: I now spend one cheap call (Qwen3-8B at $0.01/M) to compress the prompt, but I save 8x that on every subsequent request. Net positive from request #2 onward. With my 100k/month volume, the compression step itself costs me like $4/month. The savings dwarf it.


The Final Layer: Batch Processing

Last optimization, and it's the one that requires the most discipline. I'd been making one API call per question. Sometimes three calls per email because I was too tired to bundle them.

Three calls = three input token charges, three overhead charges, three round-trips. Stupid.

# BEFORE: 3 separate calls — 3x input tokens, 3x overhead
def answer_questions_separately(questions: list):
    answers = []
    for q in questions:
        r = requests.post(
            f"{BASE_URL}/chat/completions",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={
                "model": "deepseek-v4-flash",
                "messages": [{"role": "user", "content": q}]
            }
        )
        answers.append(r.json()["choices"][0]["message"]["content"])
    return answers

# AFTER: 1 batched call — same context overhead, all answers at once
def answer_questions_batched(questions: list):
    numbered = "\n".join(f"{i+1}. {q}" for i, q in enumerate(questions))
    prompt = (
        f"Answer each of these questions, numbered to match. "
        f"Keep answers concise.\n\n{numbered}"
    )
    r = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "model": "deepseek-v4-flash",
            "messages": [{"role": "user", "content": prompt}]
        }
    )
    text = r.json()["choices"][0]["message"]["content"]
    # parse and return
    return [line for line in text.split("\n") if line.strip()]
Enter fullscreen mode Exit fullscreen mode

Typical savings: 10-20% on requests where I'd otherwise be making multiple calls in a row. For batch summarization jobs — I do a lot of these for clients processing hundreds of customer reviews — the savings climb higher because the input context (the review body) is shared across questions but only paid for once.


What The Total Picture Looks Like

Let me stack it up like a real ROI calc, because that's what matters at the end of the day.

Baseline (all GPT-4o, no optimizations): $1,847/month

  • Smart model selection: -90% = $185/month
  • Tiered routing on top: -50% of remaining = $92/month
  • Response caching (60

Top comments (0)