LLMs love inventing client.getUserByEmail() that never existed. Stop asking models to remember your HTTP surface. Generate typed Node clients from OpenAPI, reject PRs that hand-edit generated files, and let agents only fill call sites against types that already compile. If it is not in the spec, TypeScript will not let it ship.
⚡ TL;DR: OpenAPI is the source of truth →
openapi-typescript/ orval / custom codegen → CI fails on drift → agents import the SDK only. Combine with structured codegen outputs and spec-driven Copilot Workspace patterns.
Generate once; never hand-type routes
# package.json scripts
# ✅
"gen:sdk": "openapi-typescript openapi/payments.v3.yaml -o src/gen/payments.ts && node scripts/gen-client.mjs",
"check:sdk": "git diff --exit-code -- src/gen/"
# ❌ agents invent fetch('/v3/paymnets') typos forever
// src/gen/payments.ts — generated (illustrative)
export interface paths {
"/v3/payments/{paymentId}": {
get: {
parameters: { path: { paymentId: string } };
responses: { 200: { content: { "application/json": Payment } } };
};
};
}
// src/sdk/paymentsClient.ts — thin wrapper over generated types
import createClient from "openapi-fetch";
import type { paths } from "../gen/payments.js";
export const payments = createClient<paths>({ baseUrl: process.env.PAYMENTS_URL! });
// ✅ app code
const { data, error } = await payments.GET("/v3/payments/{paymentId}", {
params: { path: { paymentId } },
});
// ❌ hallucinated
// await payments.GET("/v3/payments/by-email/{email}", ...)
CI gates that make hallucination a compile error
# .github/workflows/sdk.yml
jobs:
sdk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm gen:sdk
- run: pnpm check:sdk # ✅ fail if gen/ dirty
- run: pnpm tsc -p tsconfig.json --noEmit
- name: Ban raw fetch to payments host
run: |
if rg -n "PAYMENTS_URL|payments\\.internal" src --glob '!src/sdk/**' --glob '!src/gen/**'; then
echo "Use payments SDK only"; exit 1
fi
✅ CODEOWNERS: src/gen/ owned by platform; humans/agents do not edit.
❌ “Just update the client manually for this one endpoint.”
What agents are allowed to do
| Task | Allowed |
|---|---|
| Change OpenAPI + regenerate | Yes (spec PR) |
Edit src/gen/** by hand |
No |
| Write call sites using SDK types | Yes |
Add any cast to skip types |
No — lint ban |
// eslint rule — illustrative
// ❌ no-unsafe-call / ban @ts-expect-error on sdk imports
// ✅ agent proposes openapi patch + gen:sdk in the same PR
When agents must invent behavior, constrain them with structured JSON schemas that map to OpenAPI components — not free-form TypeScript.
Closing checklist
✅ Dos
– ✅ Generate clients from OpenAPI on every spec change
– ✅ Fail CI if src/gen drifts
– ✅ Ban raw HTTP to owned services outside the SDK
– ✅ Let agents edit specs + call sites, not generated trees
– ✅ Version the OpenAPI artifact with the service
❌ Don’ts
– ❌ Don’t let models memorize endpoint lists from chat history
– ❌ Don’t commit hand-patched generated files
– ❌ Don’t weaken TypeScript to silence hallucinations
– ❌ Don’t skip contract tests for new operations
– ❌ Don’t forget property tests on client retries
Related reading
- Structured Outputs for Codegen: JSON Schemas That Actually Compile
- Copilot Workspace Tasks: Spec-Driven Changes Across Microservice Boundaries
- AI Test Generation: Property Tests That Catch Race Conditions
- AI PR Reviewers: Diff-Scoped Context Windows That Catch Real Bugs
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Node Fetch vs Got vs Undici: Throughput Under Real TLS Churn - CheatCoders