Design APIs contract-first, not UI-first. Define a stable schema (OpenAPI), enforce consistent error shapes, version intentionally, and treat auth, idempotency, and rate limits as part of the API contract—not afterthoughts. AI can generate endpoints fast, but without contracts you lock in bad abstractions that are painful to undo.
Why AI tools default to bad API patterns
Most AI-generated backends look fine on day one and painful by week three.
Common defaults:
one endpoint per UI screen
no shared schema or contract
ad-hoc JSON responses
inconsistent error handling
Why this happens:
AI optimizes for making the UI work, not for long-term API design.
So you get endpoints like:
POST /createUserAndAssignPlanAndSendEmail
GET /dashboardDataThey solve the immediate need.
They break as soon as requirements evolve.
The contract-first pattern (the one that scales)
Instead of generating endpoints first, define the contract.
Use an OpenAPI spec as your source of truth.
What this gives you:
clear request/response shapes
shared understanding across frontend and backend
easier testing and validation
Example (simplified):
paths:
/users:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreate'
responses:
201:
description: User createdThe API is no longer “whatever works.”
It is defined before implementation.
Versioning strategy (don’t wait for v2 panic)
AI-generated APIs often skip versioning entirely.
Then breaking changes happen.
And everything breaks.
Start with versioning from day one:
Simple pattern:
/v1/users/v1/orders
But the real rule is:
never break existing contracts silently
Options:
version in URL (/v1/)
version in headers
additive changes only
The mistake is not versioning.
The mistake is pretending you won’t need it.
Error handling (where most APIs fail)
AI tools usually return:
{ "error": "something went wrong" }This is useless.
A good API defines structured errors.
Use a standard like application/problem+json:
{
"type": "https://example.com/errors/invalid-input",
"title": "Invalid input",
"status": 400,
"detail": "Email is required",
"code": "EMAIL_REQUIRED"
}Add:
machine-readable codes
human-readable messages
retry hints where applicable
Errors are part of your contract, not an afterthought.
Auth and scoping (must live at the API layer)
AI-built apps often handle auth only in the UI.
That’s not enough.
Your API must enforce:
user identity
tenant scope
permission checks
Every request should be:
authenticated
authorized
scoped
Example:
user belongs to tenant A
API ensures they cannot access tenant B data
This is especially critical for multi-tenant systems.
Rate limiting and idempotency (design, not infra)
These are often treated as infrastructure concerns.
But they are API design concerns first.
Rate limiting
Define:
limits per user or API key
response when limit exceeded
Example:
429 Too Many RequestsIdempotency
Critical for:
payments
order creation
retries
Use idempotency keys:
Idempotency-Key: abc123This ensures repeated requests don’t duplicate actions.
If you skip this,
you will create duplicate records under load.
Internal vs external APIs (different rules)
Not all APIs are equal.
Internal APIs
faster iteration
less strict versioning
optimized for team velocity
External APIs
strict contracts
strong backward compatibility
clear documentation
The mistake is treating both the same.
External APIs require discipline.
The real problem: UI-driven APIs
Most AI-generated APIs are shaped by the UI.
This leads to:
tightly coupled systems
hard-to-reuse endpoints
brittle architecture
Instead, design around:
resources (users, orders, payments)
actions on those resources
Not screens.
Prompt patterns that actually work
The quality of your API starts with the prompt.
Bad prompt:
“Build backend APIs for this app”
Good prompt:
“Design REST APIs using OpenAPI, resource-based endpoints, versioning, structured error responses, and idempotency for write operations.”
Even better:
specify schemas
define constraints
include examples
AI will follow structure if you provide it.
Where structured systems help
Unstructured AI coding leads to:
inconsistent endpoints
missing contracts
rework later
Structured systems like Avery.dev enforce:
contract-first design
review before merge
consistent patterns across endpoints
This is what prevents API drift.
The real cost of getting this wrong
Bad APIs don’t fail immediately.
They fail when:
features grow
teams scale
integrations increase
Fixing them later means:
breaking clients
rewriting endpoints
managing migrations
This is expensive and slow.
