API Design

Foundations · Lesson 01

What an API really is

Strip away the buzzword and an API is one thing: a contract between two pieces of software. One side promises, "ask me like this and I'll answer like that." Everything else in this course is a variation on that single idea.

⏱ 9 min Difficulty: intro No prerequisites

By the end you'll be able to

The one idea: an interface hides a machine

Think of a vending machine. You see a keypad and a slot. You press B4, money goes in, a snack comes out. You have no idea whether a robotic arm, a spiral coil, or a tiny gnome fetched it — and you don't need to. The keypad is the interface; the mechanism behind it is the implementation. As long as "press B4 → get the B4 snack" stays true, the company can swap the entire internals and you'd never notice.

An API — Application Programming Interface — is exactly this, but the one pressing the buttons is another program instead of a human. It's the keypad that software offers to other software.

Client another program API (the contract) GET /things/42 200 + JSON Implementation DB? cache? gnome? — hidden —
The contract is the only thing the client depends on. Behind the dashed line, you can change anything.

A contract has two halves: the request and the response

Every API call is a tiny negotiation. The client makes a request in an agreed shape, and the server returns a response in an agreed shape. Here's a real one — fetching a user from a web API:

# The request — what the client promises to send
GET /v1/users/42 HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer abc123

# The response — what the server promises to return
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 42,
  "name": "Ada Lovelace",
  "created_at": "1843-10-15T00:00:00Z"
}

Read it like a sentence: "Using the GET method, fetch the resource at /v1/users/42; I'll prove who I am with this token, and I'd like the answer as JSON." The server answers: "Status 200 — here it is, as JSON." Those labelled parts (method, path, headers, status, body) are the vocabulary of nearly every web API you'll ever touch. Lesson 02 unpacks them in full.

🎯 Interview angle

When an interviewer says "design the API for X," they almost never mean code. They mean: name the resources, the operations on them, the request and response shapes, and the failure cases. Start by stating the contract, not the database. Saying "the interface is the product; the implementation is hidden" up front signals senior thinking.

Why hiding the internals is the whole point

The vending machine could replace its coils with robot arms because customers only ever depended on the keypad. Software gets the same superpower: as long as the contract holds, the team behind the API can rewrite the database, add a cache, move to a new language — and no client has to change. This decoupling is why APIs let huge systems evolve without everyone moving in lockstep. It's also why a well-known, stable contract is worth more than clever internals.

✅ Do this, not that

Do design the response around what the client needs ({ "name": "Ada" }). Don't leak your storage layer ({ "usr_tbl_col_nm": "Ada" }). The moment your internal column names appear in the contract, you can never refactor them without breaking callers — the contract has swallowed your implementation.

Where you've already met APIs

The word covers more than web services. They're a family:

KindThe "keypad"Example
Web / HTTP APIURLs + methods over the networkStripe's /v1/charges
Library APIFunctions you importMath.max()
Operating-system APISystem callsopen(), read()
Hardware APIThe CPU instruction setx86 / ARM opcodes

All four are the same pattern — a published way to ask for a service while the provider keeps the right to change how it's done. In this course "API" means the web/HTTP kind unless we say otherwise, because that's what interviews focus on.

⚠️ Common trap

Confusing the API with the server. The server is a running machine; the API is the promise it exposes. Two completely different servers can offer the identical API (that's how load balancers and gateways work), and one server can expose several APIs at once. Keep "the contract" and "the thing running the contract" separate in your head.

Under the hood: how an HTTP API call actually works

When your code calls fetch("https://api.example.com/v1/users/42"), a precise sequence of events unfolds before the response arrives. Here is every step, end to end, at the wire level.

  1. DNS resolution. The name api.example.com must be converted to an IP address. Your OS checks its local cache; if it misses, it queries a recursive resolver, which walks the DNS hierarchy and returns e.g. 93.184.216.34.
  2. TCP connect. The kernel opens a socket and sends a SYN to 93.184.216.34:443. The server replies with SYN-ACK; the client completes the three-way handshake with ACK. One round trip spent purely on setup.
  3. TLS handshake. For HTTPS, client and server negotiate a cipher suite, exchange certificates, and derive a shared session key — roughly one more round trip before any plaintext is exchanged.
  4. HTTP request on the wire. The client sends a UTF-8 text stream over the TLS-encrypted TCP connection. The request line, headers, blank line, and body are sent as raw bytes.
  5. Server processes. The server's application code reads the request, queries its data store, and serialises a response.
  6. HTTP response on the wire. The server sends the status line, response headers, a blank line, and the response body back down the same TCP connection.
  7. Connection reuse or close. With Connection: keep-alive (the HTTP/1.1 default), the TCP socket stays open for the next request. Without it, a FIN/FIN-ACK four-way teardown happens.

Concrete traced example — the exact bytes that cross the wire for GET /v1/users/42:

━━━ REQUEST (client → server, after TLS) ━━━━━━━━━━━━━━━━━━━━━━━━━
GET /v1/users/42 HTTP/1.1          ← request line: method · path · version
Host: api.example.com              ← required in HTTP/1.1; tells a shared server which vhost
Accept: application/json            ← content negotiation: "I want JSON back"
Authorization: Bearer abc123       ← credential; never in the URL (ends up in logs)
User-Agent: MyApp/2.1               ← who's calling (optional but courteous)
                                     ← blank line signals end of headers; GET has no body

━━━ RESPONSE (server → client) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
HTTP/1.1 200 OK                       ← status line: version · code · reason phrase
Content-Type: application/json      ← tells client how to parse the body
Content-Length: 67                  ← body size in bytes
Cache-Control: max-age=60           ← client may cache this response for 60 s
Connection: keep-alive              ← keep the TCP socket open for the next request
                                     ← blank line; body follows
{
  "id": 42,
  "name": "Ada Lovelace",
  "created_at": "1843-10-15T00:00:00Z"
}

The sequence below shows every phase as a timing swimlane, so you can see which phases can be eliminated by keep-alive or a CDN:

time → DNS ~20 ms TCP RTT ~50 ms TLS ~1 RTT ~50 ms Request sent Server work ~5 ms Response arrives keep-alive skips all three setup phases on subsequent requests t=0 ~20 ms ~70 ms ~120 ms ~175 ms total
A cold HTTPS call to a server 50 ms away. DNS + TCP + TLS consume ~120 ms before your request even starts. A warm keep-alive connection skips all three — that is the source of the "first call slower" effect.

How to debug & inspect it

The single most useful tool for seeing the raw HTTP exchange is curl -v. It prints every phase — DNS, TLS, request headers, response headers, and body — with clear labels. Read the > lines (sent) and < lines (received):

$ curl -v https://api.example.com/v1/users/42 \ -H "Accept: application/json" \ -H "Authorization: Bearer abc123" * Trying 93.184.216.34:443... * Connected to api.example.com (93.184.216.34) port 443 * TLSv1.3 handshake complete * > GET /v1/users/42 HTTP/1.1 > Host: api.example.com > Accept: application/json > Authorization: Bearer abc123 > < HTTP/1.1 200 OK < Content-Type: application/json < Content-Length: 67 < {"id":42,"name":"Ada Lovelace","created_at":"1843-10-15T00:00:00Z"}

To see per-phase timing in one line, use curl -w with a format string. This is exactly how you measure DNS, connection, TLS, and transfer time separately:

$ curl -s -o /dev/null -w \ "dns=%{time_namelookup}s tcp=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n" \ https://api.example.com/v1/users/42 dns=0.018s tcp=0.067s tls=0.118s ttfb=0.143s total=0.145s # ↑ DNS ↑ +TCP RTT ↑ +TLS RTT ↑ TTFB (server done) ↑ transfer complete

A symptom-to-cause lookup for the most common HTTP-level errors you'll hit when inspecting raw calls:

SymptomLikely causeFix / next step
curl: (6) Could not resolve hostDNS lookup failed — wrong hostname, DNS outage, or misconfigured /etc/resolv.confTry dig api.example.com to isolate DNS; check the hostname spelling
curl: (7) Failed to connectTCP connect refused — server not listening on that port, or a firewall drops the SYNCheck the port (ss -lntp on the server); test with nc -zv host port
curl: (35) SSL connect errorTLS handshake failed — expired cert, mismatched SNI, or cipher mismatchAdd -v to see the TLS alert; try openssl s_client -connect host:443
400 Bad RequestMalformed request line or a required header is missingInspect raw request with -v; check Host header is present and correct
401 UnauthorizedMissing or expired Authorization header, or wrong credentialsCheck the token is in the Authorization: Bearer … header, not the URL
404 Not FoundThe path doesn't match any route on the serverCheck path capitalisation and trailing slash; compare against the API docs
Response body is empty / truncatedContent-Length mismatch, or server closed the connection earlyRun with --verbose --trace-ascii /tmp/dump to see raw bytes

Inspection checklist:

  1. Start with curl -v URL to see the raw request and response headers.
  2. If it fails before the request, add --resolve to bypass DNS or -k to skip TLS verification (for diagnosing only — never in production).
  3. Use -w "…" timing to identify which phase is slow (DNS, TCP, TLS, TTFB, or transfer).
  4. Compare the > request headers against the API's expected contract — missing Host, wrong Content-Type, or no Authorization are the most common culprits.
  5. Check the < response status code and Content-Type first; they immediately tell you whether the problem is routing (404/405), auth (401/403), or logic (400/422/500).

🧠 Quick check

1. The clearest one-sentence definition of an API is:

An API is the agreed interface — the request/response contract — independent of any particular server or storage behind it.

2. A team rewrites their service from Python to Go but keeps every endpoint, request, and response identical. What happens to existing clients?

Clients depend on the contract, not the implementation. If the interface is identical, the language swap is invisible to them. That decoupling is the core benefit of APIs.

3. Which response design will hurt you most over time?

Leaking internal names couples every client to your storage layer, so you can never refactor it without a breaking change. The contract has absorbed your implementation details.

✍️ Drill: define an API for a coffee machine (try before opening)

An interviewer asks you to "design an API to operate a networked coffee machine." Sketch the contract — resources, operations, one request and one response — before reading on.

A solid answer names the contract, not the wiring:

# Resource: a brew job
POST /v1/brews        # start a brew
  → body: { "drink": "latte", "size": "M", "sugar": 0 }
  ← 202 Accepted { "id": "brew_88", "status": "brewing" }

GET  /v1/brews/brew_88 # check progress200 { "status": "ready" }

Rubric: ✓ models a resource (brews) ✓ picks correct methods (POST creates, GET reads) ✓ shows request and response shapes ✓ uses an async status because brewing takes time ✓ never mentions the machine's internal hardware. Hitting all five = a strong "intro" answer.

Key takeaways

Sources & further reading

These are starting points to go deeper — all original explanation above, grounded in public references: