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:


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

  1. 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.
  2. Explicit Inputs and Outputs: The API specifies exact data formats (JSON, Protocol Buffers), parameter types, mandatory headers, and status codes.
  3. 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

APIs replace shared database access with explicit, governed service boundaries.

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:
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/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

CodeStatus NameCategoryCorrect Usage
200OK$2xx$ SuccessStandard success for GET, PUT, or PATCH operations.
201Created$2xx$ SuccessResource created successfully via POST. Returns Location header.
204No Content$2xx$ SuccessOperation succeeded, but no payload is returned (e.g. DELETE).
400Bad Request$4xx$ Client ErrorRequest body failed JSON schema validation or contained malformed syntax.
401Unauthorized$4xx$ Client ErrorRequest lacks valid authentication credentials (Authorization header).
403Forbidden$4xx$ Client ErrorClient is authenticated, but lacks authorization permissions for this resource.
404Not Found$4xx$ Client ErrorTarget URI or resource ID does not exist in the database.
429Too Many Requests$4xx$ Client ErrorClient exceeded allowed rate limits. Returns Retry-After header.
500Internal Server Error$5xx$ Server ErrorServer threw an unhandled exception or crash.
503Service Unavailable$5xx$ Server ErrorServer 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)

Breaking Changes (Requires a new major API version)


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:&quot;customerId&quot;
ProductID string json:&quot;productId&quot;
Quantity int json:&quot;quantity&quot;
ShippingAddressID string json:&quot;shippingAddressId&quot;
}

type OrderResponse struct {
OrderID string json:&quot;orderId&quot;
Status string json:&quot;status&quot;
TotalPrice float64 json:&quot;totalPrice&quot;
CreatedAt time.Time json:&quot;createdAt&quot;
}

type RFC7807Error struct {
Type string json:&quot;type&quot;
Title string json:&quot;title&quot;
Status int json:&quot;status&quot;
Detail string json:&quot;detail&quot;
Instance string json:&quot;instance&quot;
}

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 StylePrimary TransportData FormatBest Use CasesArchitectural Trade-offs
REST (RESTful HTTP)HTTP/1.1 or HTTP/2JSON / XMLPublic web APIs, mobile app backends, general integrations.Human-readable JSON; higher network payload size than binary protobufs.
gRPC / RPCHTTP/2Binary Protocol BuffersInternal microservice-to-microservice high-speed communication.Extremely fast serialization and low latency; requires schema compilation.
GraphQLHTTP (POST)JSONComplex frontends requiring flexible multi-resource data fetching.Eliminates client over-fetching; increases backend query compilation complexity.
WebSocketsTCP (WebSocket Protocol)Text / BinaryReal-time bi-directional streaming (chat applications, live tickers).Low latency for full-duplex communication; requires stateful server connections.

Failure Modes and Engineering Mitigations

Failure ScenarioRoot CauseSystem SymptomDetection SignalMitigation Strategy
1. Un-announced Breaking ChangeBackend 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 ErrorsAPI returns HTTP 200 OK with body {&quot;success&quot;: false, &quot;error&quot;: &quot;DB Failed&quot;}.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 AccessInternal 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 BodiesAPI 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

  1. An API is a formal contract: APIs define explicit boundaries between clients and providers, encapsulating internal database tables and logic.
  2. Never integrate via shared databases: Direct database coupling creates brittle systems where schema changes cause cascading production outages.
  3. Use standard HTTP status codes: Communicate outcomes using standard status categories ($2xx$ Success, $4xx$ Client Error, $5xx$ Server Error).
  4. Govern API evolution: Make additive non-breaking changes on existing versions; issue new major API versions (/v2) for breaking schema changes.
  5. Format errors for machines: Return structured error payloads (RFC 7807) with machine-readable error codes alongside human messages.

Glossary of Terms

TermDefinition
API (Application Programming Interface)A documented contract specifying how software components communicate across boundaries.
ClientThe application or process initiating an HTTP request to consume an API service.
ProviderThe backend service processing API requests and returning responses.
EndpointA specific URI path and HTTP method combination exposing an API operation (e.g. POST /v1/orders).
PayloadStructured 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 ChangeAn API schema modification that causes existing client code to fail.
DeprecationMarking 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:

  1. Explain why the current database view integration will block the database migration.
  2. 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

Track: Software Design and Architecture

Previous: WebSockets — Full-Duplex Connections for Real-Time Apps

By Shubham Jain

All articles · Study paths

Shubham Jain · Learning Lab