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 the two contracts need to diverge, AppHandoff doesn't guess which one is right — the change is 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 disagreements 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 with a published contract? Yes — the enum is in the agreement.
→ Caught without contracts? Only when QA tests admin features.This is a real example from our own codebase. The enum casing disagreement would have been a production bug if the enum hadn't been part of the published contract. 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 agent that builds an interface publishes its contract with publish_contract; the agent that consumes it confirms with confirm_contract. Changes to the agreement become tickets on the shared Kanban board (/shared-kanban-humans-bots).
The key shift is that contracts are published by the agents doing the work, not maintained as separate documents by hand. The agent that just built the endpoint publishes the shape it actually shipped. Changes are ticketed, fixed through reviewed PRs, and auto-closed on merge — the loop stays closed without a human relay.
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 bugs in production at 2am. Contracts cost minutes to set up. The bugs they prevent cost hours to fix.
Start with the interfaces your frontend actually depends on. Have the owning agent publish each one to AppHandoff and the consuming agent confirm it. You'll have explicit, machine-readable contracts in minutes — and you'll wonder how you ever shipped without them.