DEV Community

Finley Zhou
Finley Zhou

Posted on

Legacy Code Night Shift: Drafting a Living API Contract with a Free Model and a Free Server

You inherited a Flask app with no OpenAPI spec, no contract tests, and one comment that reads "works in prod." You need to refactor it before the next feature lands. The real question is not whether the app works today; it's whether you can know what it is supposed to return before you touch anything. Reading every handler is slow, and handwriting a contract is slower.

A free model can draft that contract while you sleep, and a free server can validate it every morning. I used MonkeyCode, an open-source project that currently provides free model access with a 10 million token allowance and a free server instance, to run the whole workflow. Disclosure: This article was prepared as part of MonkeyCode's product outreach. No credit card, no GPU rental, and no late-night manual inspection of route handlers.

Why a living contract beats a one-off document

A one-off API spec is a fossil. It describes the code as it was on the day you wrote it, then it quietly rots while the routes evolve. A living contract, by contrast, is regenerated from the current source and checked against the running app on a schedule.

The workflow has four stages:

  1. Extract every route and its view function source from the Flask app.
  2. Ask a free model to generate a JSON Schema for each response body.
  3. Store the drafts in a contracts/ folder.
  4. Run a validator on a free server every night and report drift.

This gives you a cheap, repeatable way to notice when a refactor changes an API payload before a frontend team discovers it in production.

Step 1: Extract routes from your Flask app

The first script walks the Flask URL map and grabs the source of every view function. It skips static assets and normalizes methods so you only see the useful HTTP verbs.

# extract_views.py
import inspect
from app import app

routes = []
for rule in app.url_map.iter_rules():
    if rule.rule.startswith('/static'):
        continue
    view_func = app.view_functions[rule.endpoint]
    source = inspect.getsource(view_func)
    routes.append({
        "path": rule.rule,
        "methods": sorted(rule.methods - {'HEAD', 'OPTIONS'}),
        "source": source
    })
Enter fullscreen mode Exit fullscreen mode

Run it and you get a structured list of route paths, methods, and the exact code that produces each response. That list is the input to the next step.

Step 2: Ask a free model to write the schema

You can call MonkeyCode's model endpoint with a prompt that asks for nothing but JSON. I used requests and three environment variables to point at the API base, the key, and the model name.

# contract_draft.py
import os
import json
import requests
from extract_views import routes

BASE_URL = os.getenv("MONKEYCODE_API_BASE")
API_KEY = os.getenv("MONKEYCODE_API_KEY")
MODEL = os.getenv("MONKEYCODE_MODEL")

prompt_template = """
You are an API contract writer. Given this Flask view source:
{source}

For route {path} with methods {methods}, generate a JSON Schema for the response body.
Return only valid JSON: {{"schema": ..., "confidence": 0.0-1.0, "notes": ""}}
"""

for r in routes:
    prompt = prompt_template.format(**r)
    resp = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "model": MODEL,
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0
        },
        timeout=30
    )
    data = json.loads(resp.json()["choices"][0]["message"]["content"])
    data["route_path"] = r["path"]
    path_safe = r["path"].replace("/", "_")
    with open(f"contracts/{path_safe}.json", "w") as f:
        json.dump(data, f, indent=2)
    print(f"Wrote contracts/{path_safe}.json")
Enter fullscreen mode Exit fullscreen mode

Keep the temperature at zero. You are not asking for creative descriptions; you want deterministic output that the validator can rely on.

Step 3: Validate the contract against reality

Now that you have candidate schemas, the free server becomes your night watchman. A simple validator loads each contract, calls the real endpoint, and compares the response against the schema with jsonschema.

# validate_contracts.py
import json
import requests
from pathlib import Path
from jsonschema import validate, ValidationError

for contract_file in Path("contracts").glob("*.json"):
    contract = json.loads(contract_file.read_text())
    schema = contract["schema"]
    route = contract["route_path"]
    response = requests.get(f"http://localhost:5000{route}")
    try:
        validate(instance=response.json(), schema=schema)
        print(f"PASS {route}")
    except ValidationError as exc:
        print(f"FAIL {route}: {exc.message}")
Enter fullscreen mode Exit fullscreen mode

Schedule this on the free server with cron, and you get a nightly report of which endpoints violate their own generated contract.

30 3 * * * cd /opt/contract-watch && python validate_contracts.py >> nightly.log
Enter fullscreen mode Exit fullscreen mode

Deciding when to trust the model's schema

Not every schema deserves a place in your happy path. I used a simple decision table based on the model's self-reported confidence and the complexity of the route.

Confidence Route shape Action
0.8 - 1.0 Simple CRUD, no authentication Auto-add as draft contract
0.5 - 0.79 Contains conditionals or auth Require human review before merge
< 0.5 Any Discard and write by hand

Treat the confidence score as a triage signal, not a fact. It helps you decide where to spend your expensive attention, not whether the schema is correct.

What the workflow does not solve

This approach works best for JSON APIs with straightforward response shapes. It will not catch subtle error-handling differences, binary payloads, or WebSocket message contracts. The model can hallucinate fields that look plausible but were never emitted by the code, and your sample requests can miss deeply nested branches.

The validator only checks one route at a time. If an endpoint returns different schemas depending on query parameters, you need multiple sample calls per route. The scripts here also assume the app is already running locally; you must handle database seeding and authentication headers yourself.

Who should not use this

Do not use this as your only source of truth for a payment API or a healthcare integration. If a contract mistake could cost money or legal compliance, you need human-written tests and formal verification, not a nightly draft from a free model. Use this workflow for internal services, prototype backends, or legacy code you are trying to understand before a rewrite.

Skip it entirely if your routes are already covered by strong contract tests and your team is confident in the API shape. The value here is in discovering the unknown, not documenting what you already know.

Try it on your own routes

Fork the idea, point it at your legacy Flask service, and see what the model gets wrong. The failures you find become better prompts for the next run, and the contracts you accept become the skeleton of a real API test suite. If you build a mutation that exposes a recurring hallucination, open a PR or share the prompt template. Everyone's legacy code gets easier when the free model's drafts are openly challenged.

Top comments (0)