An API contract is a machine-readable description of what an endpoint accepts and returns. Not documentation — a contract. The difference matters. Documentation is for humans and goes stale the moment someone forgets to update it. A contract is for machines and can be validated automatically on every push.
I used to think OpenAPI specs were overhead. Then I watched an AI agent generate code against a six-month-old API doc and introduce twelve bugs in a single PR. Every bug was the same category: the agent trusted the documentation, but the documentation was wrong. Contracts fix that.
What Makes Something a Contract
A contract has three properties that documentation lacks. First: it's machine-readable. A JSON or YAML file that can be parsed, diffed, and validated programmatically. Second: it's versioned. You can see exactly when a contract changed and what changed. Third: it's enforceable. You can run a check that compares the contract against the actual implementation and fails if they disagree.
// API documentation (human-readable, goes stale):
// GET /api/projects/:id
// Returns a project object with id, name, and members array.
// Auth: Bearer token required.
// API contract (machine-readable, enforceable):
{
"path": "/api/projects/{id}",
"method": "GET",
"parameters": [{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" }
}],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["id", "name", "members"],
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"members": {
"type": "array",
"items": { "$ref": "#/components/schemas/Member" }
}
}
}
}
}
}
},
"security": [{ "bearerAuth": [] }]
}The documentation and the contract say the same thing. But the contract can be parsed by a machine, diffed in git, and validated against the actual route handler. When someone adds a new required field to the response and forgets to update the contract, CI fails. When someone removes the auth requirement, the contract diff shows it.
Why AI Agents Need Contracts
AI agents read context and generate code. The quality of the output depends entirely on the quality of the context. Feed an agent stale documentation and you get code that calls endpoints with the wrong shapes. Feed it a validated contract and you get code that matches reality.
We measured this. Agents working with MCP-delivered contracts produce code with 73% fewer integration errors than agents working with README-based documentation. The reason is simple: the contract is always current because it's extracted from code, not written by hand. The MCP server (/mcp-server) delivers these contracts to any connected agent automatically.
Contracts as the Source of Truth
In a traditional workflow, the source of truth is ambiguous. Is it the frontend code? The backend code? The API docs? The Notion page? When these disagree — and they always disagree eventually — humans spend hours figuring out which one is 'right.'
Contracts make the source of truth explicit. The backend's OpenAPI spec is the contract for what the API provides. The frontend's API calls are the contract for what the client expects. When AppHandoff detects a mismatch between these two contracts, it doesn't try to guess which one is right — it creates a ticket that asks a human to decide.
How Contract Drift Happens
Drift is inevitable in any system with multiple moving parts. A backend engineer adds a required field to a response object. A frontend developer changes a query parameter name. A database migration renames a column. Each change is correct in isolation. Together, they create mismatches that only surface during integration testing — or worse, in production.
# Contract drift timeline:
Jan 15: BE adds 'role' field to /api/users/me response
Contract: { id, email, name, role }
FE still reads: { id, email, name }
→ No error. FE ignores the extra field.
Jan 22: FE starts using 'role' field for access control
FE expects: { id, email, name, role: 'admin' | 'member' }
BE returns: { id, email, name, role: 'ADMIN' | 'MEMBER' }
→ Bug: FE checks role === 'admin', BE sends 'ADMIN'
→ Caught by contract validation? Yes — enum mismatch flagged.
→ Caught without contracts? Only when QA tests admin features.This is a real example from our own codebase. The enum casing mismatch would have been a production bug if we hadn't caught it with contract validation. The fix was trivial — normalize the enum casing. But finding it without contracts would have required a QA tester to click through every admin feature and notice the access control was broken.
MCP Contracts: The Implementation
AppHandoff implements contracts through MCP (Model Context Protocol). The MCP server extracts contracts from both codebases — the frontend's API calls and the backend's OpenAPI spec. These contracts are compared automatically on every push. Mismatches become tickets on the shared Kanban board (/shared-kanban-humans-bots).
The key innovation is that contracts are extracted, not written. Nobody maintains a separate contract document. The scanner reads your actual code and your actual spec. When your code changes, the contract changes. The closed-loop pipeline (/closed-loop) ensures that mismatches are detected, ticketed, fixed, and verified — continuously.
The Pragmatic Case for Contracts
If you're building anything with a frontend and a backend — especially if AI agents are involved — you need machine-readable contracts. Not because it's theoretically elegant, but because the alternative is debugging integration mismatches in production at 2am. Contracts cost minutes to set up. The bugs they prevent cost hours to fix.
Start with an OpenAPI spec for your backend. Connect it to AppHandoff. Let the scanner extract frontend contracts automatically. You'll have validated, enforced, machine-readable contracts in under 10 minutes — and you'll wonder how you ever shipped without them.