system-design · intermediate
What Is an API? — Contracts Between Programs
The Central Question
Consider an e-commerce platform running on the APILab platform (apilab.com) processing 5,000,000 requests per day:
- A mobile application needs to display inventory, process user checkouts, and retrieve shipping tracking numbers.
- If the mobile application developers wrote SQL queries directly against the primary production database, any internal schema change—such as renaming
user_addresstoshipping_address—would instantly crash the mobile app for millions of active users.
To prevent this brittle structural coupling, modern software systems communicate across explicit boundaries.
An Application Programming Interface (API) is a formal, documented contract specifying how software components interact, what inputs they accept, what outputs they return, and how operational errors are communicated.
This lesson answers one central question: What defines a software API as a formal contract between programs, how do network HTTP APIs structure requests and responses, and how do clear interfaces prevent system integration failures?
Defining the API: Interface as a Contract
In modern backend architecture, an API defines an abstraction boundary between a client (the program requesting a service) and a provider (the service executing the work).
flowchart LR
subgraph Client Applications
Mobile[Mobile App]
Web[Web SPA]
Partner[Partner Server]
end
subgraph API Contract Boundary
Contract["API Contract (OpenAPI Spec / JSON Schema)<br/>• Endpoints & Methods<br/>• Request Headers & Payload<br/>• Status Codes & Errors"]
end
subgraph Provider Internal Architecture
Logic[Business Logic]
DB[(Internal Relational DB)]
Queue[Message Queue]
end
Mobile -->|HTTP Request| Contract
Web -->|HTTP Request| Contract
Partner -->|HTTP Request| Contract
Contract --> Logic
Logic --> DB
Logic --> Queue
Figure 1: The API contract boundary insulating clients from internal provider architecture.
The Three Promises of an API Contract
- Encapsulation: Internal implementation details (database tables, class names, internal microservices) remain hidden behind the API boundary. The provider can refactor its database engine from PostgreSQL to DynamoDB without altering the public API.
- Explicit Inputs and Outputs: The API specifies exact data formats (JSON, Protocol Buffers), parameter types, mandatory headers, and status codes.
- Behavioral Expectations: The contract guarantees functional outcomes, rate limits, latency goals (SLOs), and failure handling policies.
The Danger of Integration Without APIs
Before network APIs became standard, software teams integrated systems using shared databases or ad-hoc file transfers:
flowchart TB
subgraph Un-governed Shared Database Integration
TeamA[Team A: Order Service] -->|Direct Writes| SharedDB[(Shared PostgreSQL Database)]
TeamB[Team B: Billing Service] -->|Direct Reads| SharedDB
TeamC[Team C: Shipping Service] -->|Direct Writes| SharedDB
end
style SharedDB fill:#f8d7da,stroke:#f5c6cb
Figure 2: Architectural coupling caused by shared database integration.
Why Shared Database Integration Fails
- Tight Schema Coupling: If Team A modifies a table column type from
INTEGERtoBIGINT, Team B and Team C crash instantly in production. - Zero Security Boundaries: Any team can execute un-restricted
UPDATEorDELETEqueries across the entire database, bypassing validation logic. - Un-controlled Concurrency: Direct concurrent writes bypass application-level locks, causing silent data corruption.
Anatomy of a Network HTTP Request and Response
While APIs exist within operating systems (POSIX syscalls) and programming languages (Java interfaces), most backend engineering concerns Network APIs operating over HTTP/HTTPS.
sequenceDiagram
autonumber
actor Client as Mobile Client
participant GW as API Gateway / Server
participant DB as Database
Client->>GW: POST /v1/orders HTTP/1.1 (Headers + JSON Payload)
Note over GW: 1. Authenticate Token<br/>2. Validate JSON Schema<br/>3. Check Rate Limit
GW->>DB: INSERT INTO orders VALUES (...)
DB-->>GW: Order ID 9012 Created
GW-->>Client: HTTP/1.1 201 Created (Content-Type: application/json)
Figure 3: Sequence diagram detailing HTTP request parsing, internal execution, and structured response return.
1. The HTTP Request Structure
An HTTP request sent to an API endpoint contains four primary components:- HTTP Method (Verb): Expresses the action intent (
GET,POST,PUT,PATCH,DELETE). - Target URI (Path): Identifies the resource (
/v1/catalog/products/prod_901). - Headers: Metadata key-value pairs (
Authorization: Bearer <token>,Content-Type: application/json,Accept: application/json). - Request Body (Payload): Structured data payload formatted in JSON or XML.
POST /v1/orders HTTP/1.1
Host: api.apilab.com
Authorization: Bearer eyJhbGciOiJKV1QiLC...
Content-Type: application/json
Accept: application/json
{
"customerId": "cust_4401",
"items": [
{ "productId": "prod_88", "quantity": 2 }
],
"shippingAddressId": "addr_12"
}
2. The HTTP Response Structure
An HTTP response returned by an API contains three primary components:- HTTP Status Code: Numeric indicator of the outcome class ($2xx$ Success, $4xx$ Client Error, $5xx$ Server Error).
- Response Headers: Server metadata (
Content-Type: application/json,Cache-Control: no-cache). - Response Body: Data payload or structured error details.
HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/orders/ord_9012
{
"orderId": "ord_9012",
"status": "PENDING",
"totalAmount": 4999,
"currency": "USD",
"createdAt": "2026-07-24T10:00:00Z"
}
HTTP Status Codes: The Universal Outcome Language
An API contract must communicate operation outcomes using standardized HTTP status code ranges rather than custom, ambiguous responses:
flowchart TD
Code[HTTP Response Status Code] --> Cat2[2xx: Success]
Code --> Cat3[3xx: Redirection]
Code --> Cat4[4xx: Client Error]
Code --> Cat5[5xx: Server Error]
Cat2 --> C200["200 OK: Request Succeeded"]
Cat2 --> C201["201 Created: Resource Created"]
Cat4 --> C400["400 Bad Request: Invalid Payload Schema"]
Cat4 --> C401["401 Unauthorized: Missing or Invalid Auth Token"]
Cat4 --> C403["403 Forbidden: Authenticated but Lacks Permission"]
Cat4 --> C404["404 Not Found: Resource Does Not Exist"]
Cat4 --> C429["429 Too Many Requests: Rate Limit Exceeded"]
Cat5 --> C500["500 Internal Error: Server Code Crash"]
Cat5 --> C503["503 Unavailable: Database / Dependency Outage"]
Figure 4: Taxonomy of HTTP status code categories and primary codes.
Common Status Codes in API Engineering
| Code | Status Name | Category | Correct Usage |
|---|---|---|---|
| 200 | OK | $2xx$ Success | Standard success for GET, PUT, or PATCH operations. |
| 201 | Created | $2xx$ Success | Resource created successfully via POST. Returns Location header. |
| 204 | No Content | $2xx$ Success | Operation succeeded, but no payload is returned (e.g. DELETE). |
| 400 | Bad Request | $4xx$ Client Error | Request body failed JSON schema validation or contained malformed syntax. |
| 401 | Unauthorized | $4xx$ Client Error | Request lacks valid authentication credentials (Authorization header). |
| 403 | Forbidden | $4xx$ Client Error | Client is authenticated, but lacks authorization permissions for this resource. |
| 404 | Not Found | $4xx$ Client Error | Target URI or resource ID does not exist in the database. |
| 429 | Too Many Requests | $4xx$ Client Error | Client exceeded allowed rate limits. Returns Retry-After header. |
| 500 | Internal Server Error | $5xx$ Server Error | Server threw an unhandled exception or crash. |
| 503 | Service Unavailable | $5xx$ Server Error | Server is healthy, but backend database or downstream dependency is offline. |
API Versioning and Evolutionary Governance
As business requirements expand, APIs must evolve without breaking existing mobile apps or partner integrations.
timeline
title API Lifecycle & Version Evolution Timeline
section Release Phase
Publish API v1 : /v1/orders endpoint live for public clients
section Additive Evolution
Non-breaking updates : Add optional 'giftWrap' field to v1 request body
section Major Version Shift
Design API v2 : Rename 'full_name' to 'displayName' (Breaking Change)
section Deprecation Phase
Deprecate v1 : Add Sunset header (Sunset: Wed, 31 Dec 2026); notify clients
section Decommission Phase
Retire v1 : Return 410 Gone for /v1 requests; zero active v1 traffic
Figure 5: The lifecycle timeline of an API version from release to sunset.
Breaking vs. Non-Breaking API Changes
Non-Breaking Changes (Safe to ship on existing version)
- Adding a new optional field to a request body.
- Adding a new field to a response JSON body (clients must ignore unknown fields).
- Adding a new API endpoint URI (
POST /v1/orders/cancellations).
Breaking Changes (Requires a new major API version)
- Removing or renaming an existing field in a response JSON (
user_name$\rightarrow$username). - Changing a field's data type (e.g. string ID to integer ID).
- Removing an existing HTTP endpoint or HTTP method.
- Changing authentication mechanisms.
Complete Worked Example: Go Production HTTP API Handler
Let's inspect a complete Go implementation of a production HTTP API handler for the APILab platform (apilab.com) implementing strict request validation, standard status codes, and RFC 7807 error responses.
package main
import (
"encoding/json"
"net/http"
"time"
)
type CreateOrderRequest struct {
CustomerID string json:"customerId"
ProductID string json:"productId"
Quantity int json:"quantity"
ShippingAddressID string json:"shippingAddressId"
}
type OrderResponse struct {
OrderID string json:"orderId"
Status string json:"status"
TotalPrice float64 json:"totalPrice"
CreatedAt time.Time json:"createdAt"
}
type RFC7807Error struct {
Type string json:"type"
Title string json:"title"
Status int json:"status"
Detail string json:"detail"
Instance string json:"instance"
}
func OrderHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if r.Method != http.MethodPost {
writeError(w, http.StatusMethodNotAllowed, "Method Not Allowed", "Only POST requests are supported.")
return
}
var req CreateOrderRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Invalid Request Body", "Malformed JSON syntax.")
return
}
// Request Validation
if req.CustomerID == "" || req.Quantity <= 0 {
writeError(w, http.StatusBadRequest, "Validation Failed", "Field 'quantity' must be greater than 0.")
return
}
// Success Response (HTTP 201 Created)
res := OrderResponse{
OrderID: "ord_9012",
Status: "PLACED",
TotalPrice: float64(req.Quantity) * 49.99,
CreatedAt: time.Now().UTC(),
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(res)
}
func writeError(w http.ResponseWriter, status int, title, detail string) {
w.WriteHeader(status)
errPayload := RFC7807Error{
Type: "https://api.apilab.com/errors/validation",
Title: title,
Status: status,
Detail: detail,
Instance: "/v1/orders/err_log",
}
json.NewEncoder(w).Encode(errPayload)
}
API Protocols and Architectural Styles
While JSON-over-HTTP (RESTful style) is common, APIs utilize multiple architectural styles depending on performance requirements:
| API Style | Primary Transport | Data Format | Best Use Cases | Architectural Trade-offs |
|---|---|---|---|---|
| REST (RESTful HTTP) | HTTP/1.1 or HTTP/2 | JSON / XML | Public web APIs, mobile app backends, general integrations. | Human-readable JSON; higher network payload size than binary protobufs. |
| gRPC / RPC | HTTP/2 | Binary Protocol Buffers | Internal microservice-to-microservice high-speed communication. | Extremely fast serialization and low latency; requires schema compilation. |
| GraphQL | HTTP (POST) | JSON | Complex frontends requiring flexible multi-resource data fetching. | Eliminates client over-fetching; increases backend query compilation complexity. |
| WebSockets | TCP (WebSocket Protocol) | Text / Binary | Real-time bi-directional streaming (chat applications, live tickers). | Low latency for full-duplex communication; requires stateful server connections. |
Failure Modes and Engineering Mitigations
| Failure Scenario | Root Cause | System Symptom | Detection Signal | Mitigation Strategy |
|---|---|---|---|---|
| 1. Un-announced Breaking Change | Backend team renames a JSON response field in production without versioning. | Mobile app crashes on startup for all users. | Spike in mobile app crash reports and client-side JSON parsing errors. | Enforce automated OpenAPI contract breaking change checks in CI/CD build pipelines. |
| 2. Misuse of HTTP Status 200 for Errors | API returns HTTP 200 OK with body {"success": false, "error": "DB Failed"}. | Client monitoring tools log 100% success while users experience total breakage. | Metrics show 0% HTTP 5xx errors despite user complaint spikes. | Return honest HTTP status codes ($4xx, 5xx$) matching the operation outcome. |
| 3. Shared Database Direct Access | Internal services bypass API gateway and query primary database tables directly. | Schema updates break downstream services silently without compile errors. | Unexpected database connection spikes from external service IP ranges. | Block database network access outside of owning service VPC; enforce API access. |
| 4. Un-structured Free-Text Error Bodies | API returns HTML error pages or un-formatted text strings on failure. | Mobile clients fail to parse errors, displaying raw HTML strings to end users. | Log traces show client string parsing exceptions. | Standardize all API error responses on RFC 7807 JSON Problem Details. |
What You Should Remember
- An API is a formal contract: APIs define explicit boundaries between clients and providers, encapsulating internal database tables and logic.
- Never integrate via shared databases: Direct database coupling creates brittle systems where schema changes cause cascading production outages.
- Use standard HTTP status codes: Communicate outcomes using standard status categories ($2xx$ Success, $4xx$ Client Error, $5xx$ Server Error).
- Govern API evolution: Make additive non-breaking changes on existing versions; issue new major API versions (
/v2) for breaking schema changes. - Format errors for machines: Return structured error payloads (RFC 7807) with machine-readable error codes alongside human messages.
Glossary of Terms
| Term | Definition |
|---|---|
| API (Application Programming Interface) | A documented contract specifying how software components communicate across boundaries. |
| Client | The application or process initiating an HTTP request to consume an API service. |
| Provider | The backend service processing API requests and returning responses. |
| Endpoint | A specific URI path and HTTP method combination exposing an API operation (e.g. POST /v1/orders). |
| Payload | Structured data transmitted in the request or response body (typically JSON). |
| RFC 7807 (Problem Details) | The standard specification for structuring HTTP API error responses in JSON. |
| Breaking Change | An API schema modification that causes existing client code to fail. |
| Deprecation | Marking an API version or endpoint as obsolete while providing a migration window before sunsetting. |
Practice Scenario and Self-Assessment
Architecture Scenario
You are designing an API for a logistics company (`logisticslab.com`). Currently, external shipping partners read tracking data by connecting directly to your PostgreSQL database view `vw_shipment_status`.Your engineering lead wants to migrate the underlying database from PostgreSQL to AWS DynamoDB next month.
Questions:
- Explain why the current database view integration will block the database migration.
- Design an HTTP API endpoint specification (
GET /v1/shipments/{trackingId}) to replace the direct database view, including success and error JSON schemas.
Interactive Self-Assessment
A breaking change shipped without major API versioning.
A non-breaking additive schema change.
A standard HTTP 500 server status code failure.
A Layer 4 network protocol negotiation error.
It creates tight schema coupling where database changes cause cascading production outages.
Relational databases cannot process network queries from more than one server.
Direct database access is slower than calling HTTP APIs over network sockets.
Shared databases prevent engineers from writing SQL queries.
What to Learn Next
- API Design — Clear Contracts Clients Can Trust: Master RESTful resource design, pagination, and error handling.
- API Gateway — Edge Entry for Microservices: Explore edge routing, TLS termination, and authentication.
- REST vs RPC — Wire Protocol Trade-offs: Compare REST JSON vs gRPC Protobuf binary communication.
Track: Software Design and Architecture
Previous: WebSockets — Full-Duplex Connections for Real-Time Apps
By Shubham Jain