API-first architecture is not a new idea, but the gap between teams that implement it correctly and those that bolt APIs on as an afterthought has widened significantly as the integration demands on enterprise software have grown. Modern enterprise applications need to feed data to mobile apps, third-party integrations, automation pipelines, AI agents, and analytics platforms simultaneously. The only sustainable architecture for that reality is one where the API is the product — not an afterthought.
What API-First Actually Means
API-first is a development approach where the API contract is designed and agreed before implementation begins. The implications are significant:
The API is treated as the primary interface — not the UI. The UI is just one consumer of the API, alongside mobile clients, automation tools, and external integrations.
The API contract (OpenAPI specification or GraphQL schema) is version-controlled and reviewed with the same rigour as application code. Breaking changes require explicit versioning decisions.
Consumer teams (frontend, mobile, third-party integrators) can build against a mock API from the specification before the backend is implemented. This parallelises development and eliminates the blocking dependency of waiting for the backend to ship.
REST vs GraphQL: Choosing the Right Query Model
The choice between REST and GraphQL is not a matter of one being universally better. It is a function of the consumption pattern.
REST is the right choice when:
- Consumers are well-defined and relatively few in number
- The resource model maps cleanly to HTTP semantics (GET, POST, PUT, DELETE)
- You need maximum simplicity for external API consumers who may not be engineers
- Caching is important — REST responses cache naturally via HTTP caching, GraphQL requires additional effort
- The team has strong REST tooling and expertise
GraphQL is the right choice when:
- Multiple consumers with significantly different data requirements query the same API (classic example: a mobile app needs less data than a desktop UI)
- You want to eliminate over-fetching and under-fetching — the ability for each consumer to request exactly the fields it needs is GraphQL's core advantage
- The schema is complex and hierarchical — GraphQL's type system handles nested relationship queries more elegantly than REST
- You want a self-documenting API with built-in introspection
For most enterprise backends, the practical answer is: REST for external-facing APIs (simpler for integrators) and GraphQL or a purpose-built internal API for data-intensive frontend consumption.
API Design Principles
Regardless of protocol, well-designed APIs share these characteristics:
Resource-oriented thinking. Design around nouns (resources) rather than verbs (actions). `/invoices/{id}/approve` is a verb endpoint that fights REST semantics. `/invoices/{id}` with a PATCH body of `{ "status": "approved" }` is resource-oriented and composable.
Consistent response envelopes. All API responses should follow a consistent structure. Success responses and error responses should be predictable:
{
"data": { ... },
"meta": { "requestId": "abc123", "timestamp": "2025-06-01T10:00:00Z" }
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "Email address is required",
"fields": { "email": "Required" }
}
}Meaningful HTTP status codes. 200 for success, 201 for created, 400 for client validation errors, 401 for unauthenticated, 403 for unauthorised, 404 for not found, 422 for semantic validation failures, 429 for rate limited, 500 for server errors. Use the full vocabulary — returning 200 with an error body is a common anti-pattern that breaks client error handling.
Pagination on all list endpoints. No list endpoint should return unbounded results. Cursor-based pagination (returning a `nextCursor` token) is preferable to offset-based pagination at scale — it avoids the N+1 database query problem and handles concurrent inserts correctly.
Idempotency keys for mutations. For any write operation that should not be duplicated — creating an invoice, processing a payment — support an idempotency key header that allows clients to safely retry without risk of creating duplicates.
Versioning Strategy
API versioning is one of the most consequential design decisions you will make, and it is one that cannot easily be changed later.
The most common approaches:
URI versioning — `/v1/invoices`, `/v2/invoices`. Simple, highly visible, easy to route. The standard approach for public APIs.
Header versioning — `Accept: application/vnd.tecsynth.v2+json`. Keeps URLs clean but is less discoverable and harder to test in a browser.
Date-based versioning — `/2024-01-01/invoices`. Used by Stripe and Shopify. Allows very granular deprecation — a specific API behaviour is deprecated on a specific date rather than a whole version.
For most enterprise backends, URI versioning is the right choice: it is explicit, easily understood by all consumers, and simple to implement via routing.
The critical rule: once a version is deployed and consumed, breaking changes require a new version. Never break an existing contract. Additive changes (new fields, new endpoints) are backwards compatible and do not require a version bump.
Authentication and Authorisation
Enterprise API authentication has effectively standardised on OAuth 2.0 with JWT bearer tokens. The key implementation decisions:
Token lifetime strategy. Short-lived access tokens (15 minutes to 1 hour) paired with longer-lived refresh tokens (7–30 days). The access token is used for API calls; the refresh token is used to obtain new access tokens. Short access token lifetimes limit the blast radius of token leakage.
Scope-based authorisation. Token scopes define what the token is permitted to do: `invoices:read`, `invoices:write`, `admin:users`. Consumers request the minimum scopes they need. This implements the principle of least privilege at the API layer.
API keys for service-to-service. Machine-to-machine integrations (automation pipelines, internal services) should use API keys with specific permissions rather than user credentials. API keys should be rotatable without disrupting the integration.
Rate limiting per consumer. Every authenticated consumer should have rate limits applied at the API gateway or middleware layer. This prevents any single consumer from degrading the API for others, and provides a natural signal for detecting misuse.
Scalability Patterns
A well-designed API architecture handles load growth without requiring structural changes. The key patterns:
Stateless handlers. API handlers should be stateless — all state lives in the database or cache, not in application memory. Stateless handlers scale horizontally by adding more instances behind a load balancer.
Database connection pooling. Direct database connections from API handlers are expensive. Connection pooling (PgBouncer for PostgreSQL, connection pooling built into ORMs) dramatically reduces the connection overhead at high request volumes.
Response caching. GET endpoints for data that changes infrequently should be cached — either via HTTP cache headers (for public data) or an application-level cache like Redis (for authenticated data). Define cache TTLs based on the acceptable staleness of each resource type.
Async processing for heavy operations. API endpoints that trigger long-running operations — sending emails, generating reports, calling third-party APIs — should enqueue the work and return a 202 Accepted response immediately. The client polls for completion or receives a webhook notification. Synchronous long-running endpoints degrade the entire API's responsiveness under load.
Circuit breakers for downstream dependencies. When your API depends on third-party services, implement circuit breakers that detect when a downstream service is degraded and fail fast rather than blocking threads waiting for timeouts. Libraries like `opossum` (Node.js) implement this pattern.
API Gateway Architecture
In a microservices or multi-service architecture, an API gateway sits in front of all services and handles:
- Authentication and authorisation (validate JWT, check scopes)
- Rate limiting and throttling
- Request routing to the appropriate upstream service
- Response caching for common requests
- Logging and observability (every request logged with trace ID)
- SSL termination
The gateway pattern keeps cross-cutting concerns out of individual service implementations. Every service does not need its own authentication middleware — that logic lives once, in the gateway.
Documentation as a First-Class Deliverable
An API without documentation is an API that will be misused. The standard for enterprise APIs is auto-generated documentation from the OpenAPI specification, hosted alongside the API, and kept in sync with the implementation.
Swagger UI and Redoc both render OpenAPI specs into interactive documentation that allows developers to explore and test endpoints without writing code. This dramatically reduces the friction for new integrators and eliminates the support burden of answering "how do I call this endpoint?"
The specification itself should include: clear descriptions of every endpoint, parameter, and response; example request and response bodies; error code documentation; and authentication requirements. Treat the specification as the contract between your API and its consumers — it should be as carefully maintained as the code.