Defining APIs first
Why sketching the API contract before the architecture produces better system designs, and how to define endpoints, parameters, and responses in the first 10 minutes.
TL;DR
- Define the API before drawing any architecture boxes. The API is a living requirements document that forces clarity about inputs, outputs, and data flow.
- Use the 3-step sketch: list operations, define request/response shapes, identify key fields. This takes 3 to 5 minutes and anchors every architecture decision.
- Each endpoint reveals hidden requirements: pagination tokens imply ordered data and cursor handling, immediate write acknowledgments may imply async fan-out, and fast reads may imply caching.
- REST naming in interviews is a safe default. Use RPC-style naming only for action-heavy operations that don't map cleanly to resources.
- The API drives your data model. The nouns in your responses become entities, and the query patterns in your GETs become access patterns that shape your schema.
Why this framework matters
Picture this. You're 10 minutes into "Design Twitter." You've drawn a write service, a fan-out service, a timeline cache, and a notification pipeline. The interviewer asks: "How does a user load their home timeline?" You freeze. You realize your fan-out service writes tweet IDs to a cache, but you never defined what the timeline API actually returns. Does it return full tweet objects or just IDs that need hydration? Does it include author profiles inline or require a separate lookup? Is it cursor-paginated or offset-paginated?
Your beautiful architecture doesn't answer these questions because you never asked them. You designed the plumbing before deciding what flows through it.
This failure is easy to avoid: trace one request through the contract before committing to components. Otherwise fields can be missing, response shapes can fail to match the client, and pagination or error behavior can disappear from the design.
The fix is to write the API first. Before a single box on the whiteboard, spend a few minutes sketching the core endpoints. The API is the contract the system must fulfill, and it is a fast way to discover what is actually being built.
Architecture without API is guesswork
If you can't describe the request and response for your core operations, you don't understand your system yet. The API sketch is what turns a vague prompt into concrete engineering requirements. Skip it and you'll spend the interview retrofitting an architecture that doesn't serve the real data flow.
When to use this framework
Use API-first design for user-facing systems and for internal services with meaningful contracts, especially when the prompt leaves the data flow ambiguous. Abbreviate it for a purely internal scaling question or a design that is explicitly about storage internals. The goal is not a production-ready specification; it is a contract precise enough to drive the data model, flows, and component choices.
The Twitter-like rates, payloads, latency targets, and page sizes in this article are illustrative assumptions. Replace them with values from the prompt or with measured requirements; they are not universal product behavior or performance guarantees.
APIs as living requirements documents
An API endpoint isn't just a URL. Each one is a mini requirements document that tells you:
- What data goes in. The request body reveals what the caller knows and controls.
- What data comes out. The response shape reveals what the system must compute, store, and assemble.
- Who calls it. The authentication model (user token vs service key) reveals the trust boundary.
- How often it's called. Read endpoints are often called much more frequently than writes, which can drive caching and scaling decisions; verify the ratio from the workload.
When you write GET /timeline/{user_id}?cursor=abc&limit=20, you've just discovered four requirements without trying:
- Timelines are per-user (partitioning hint)
- Pagination is cursor-based (you need cursor state somewhere)
- There's a default page size (20 items, which affects cache sizing)
- The caller identifies users by ID (not username, so you need a lookup if the frontend has usernames)
A useful practice is to treat the API sketch as the first architecture artifact. It is something you can write before drawing boxes, and it makes subsequent decisions easier to inspect.
The API acts as a bridge between scoping and architecture. Without it, you're jumping from vague requirements straight to boxes and arrows, and that's where designs go sideways.
Step-by-step method: the 3-step API sketch
You don't need a polished OpenAPI spec. You need enough contract detail to trace data through the system. The following three steps are a useful default.
Step 1: List the core operations
After scoping, you know the 2 to 4 core user actions. Turn each one into an operation. For "Design Twitter":
Core operations:
1. Post a tweet β write operation
2. Read home timeline β read operation (high volume)
3. Follow a user β write operation (low volume)
4. Like a tweet β write operation (medium volume)
That's it. Four operations. Don't go hunting for edge cases yet. Search, trending topics, notifications, DMs: all out of scope unless the interviewer specifically asked for them.
Step 2: Define request and response shapes
For each operation, write the HTTP method, path, request body, and response. Be specific about field names.
POST /tweets
Request: { user_id, content (280 chars max), media_ids[]? }
Response: { tweet_id, created_at }
Notes: Responds immediately. Fan-out happens async.
GET /timeline/{user_id}?cursor={token}&limit=20
Response: { tweets: [{ tweet_id, author_id, author_name,
content, created_at, like_count }],
next_cursor }
Notes: Illustrative target: < 200ms P99. Cursor-based pagination.
POST /follows
Request: { follower_id, followee_id }
Response: { status: "created" } or 409 Already Following
POST /tweets/{tweet_id}/likes
Request: { user_id }
Response: { status: "created" } or 409 Already Liked
Notice what just happened. The timeline response includes author_name inline. That's a denormalization decision you've already made, and you haven't even drawn your schema yet.
Step 3: Identify key fields and hidden requirements
Look at each endpoint and ask: "What does this field imply about my system?"
| Field / Pattern | Hidden Requirement |
|---|---|
cursor parameter | Need ordered data, efficient range queries, and either an encoded cursor or server-side cursor state |
author_name in timeline | Denormalized author data, or a join/hydration step |
like_count in timeline | Either precomputed counters or a COUNT query per tweet |
media_ids[] optional | Media upload is a separate flow, tweets reference media by ID |
409 Already Following | Need uniqueness constraint on the follow relationship |
Immediate tweet_id return | Write is acknowledged before fan-out completes |
For your interview: say "Each of these fields tells me something about my data model and architecture. Let me trace through the implications." Then walk through 2 to 3 of the most interesting ones.
The 3-5 minute rule
The entire API sketch should take 3 to 5 minutes. You're not writing production docs. You're creating a contract that your architecture must satisfy. If you're spending more than 5 minutes, you're over-polishing. Write the operations, the shapes, and the key fields, then move to the diagram.
REST vs. RPC naming in interviews
This comes up less than you'd think, but it's good to have a clear stance.
REST (resource-oriented) works when your operations map to CRUD on resources. Most interview systems fit this pattern:
GET /tweets/{id} β Read a tweet
POST /tweets β Create a tweet
DELETE /tweets/{id} β Delete a tweet
GET /users/{id}/followers β List followers
RPC-style (action-oriented) works when the operation is an action that doesn't map cleanly to a resource:
POST /rides/estimate β Not creating an "estimate" resource
POST /payments/refund β Reversing a payment, not creating a "refund" object
POST /search/query β Running a query, not CRUDing a search
A practical default is REST naming for resource-oriented operations. Switch to RPC-style only when the operation is clearly an action such as estimate, refund, search, or transfer. Do not spend interview time debating REST purity; state the convention and keep it consistent.
The one thing to never do is mix styles randomly. If POST /tweets creates a tweet, don't also have POST /createTweet. Pick a convention and stay consistent.
Pagination, Cursors, and Common Patterns
Any large or unbounded list endpoint needs pagination. A list endpoint without a size limit or pagination leaves its memory, latency, and response-size behavior undefined.
Cursor-based pagination (preferred)
GET /timeline/{user_id}?cursor=eyJjcmVhdGVkX2...&limit=20
Response: {
tweets: [...],
next_cursor: "eyJjcmVhdGVkX2...",
has_more: true
}
The cursor encodes the position in the dataset (typically the created_at timestamp or a composite key of the last item). The server decodes it and does a WHERE created_at < cursor_value ORDER BY created_at DESC LIMIT 20 query.
Why cursor often beats offset: deep offsets can scan and discard many rows. OFFSET 10000 LIMIT 20 may inspect 10,020 rows, while a cursor can use an indexed keyset predicate such as WHERE created_at < cursor_value ORDER BY created_at DESC LIMIT 20. The exact complexity depends on the index and query plan; the important property is avoiding work proportional to the skipped prefix.
Other patterns to know
| Pattern | When to use |
|---|---|
| Cursor-based | Infinite scroll, feeds, timelines, any ordered list |
| Offset/limit | Admin dashboards, small datasets, when "jump to page 50" is needed |
| Keyset with sort | Sortable tables (ORDER BY different columns) |
| Token-based | When the server manages state (search results, complex queries) |
For an interview, include a cursor and limit parameter when the list can grow large, and state why the ordering and cursor are stable. Use offset pagination for small, bounded, or page-number-oriented datasets when that trade-off is acceptable.
Worked example: a Twitter-like API
The following is an illustrative API sketch for a Twitter-like system. The rates, response fields, and latency target are assumptions for showing the method; replace them with the prompt's requirements.
WRITE PATH
ββββββββββββββββββββββββββββββββββββββββββ
POST /tweets
Auth: Bearer token (user_id extracted from token)
Request: { content: string (max 280), media_ids?: string[] }
Response: { tweet_id: string, created_at: timestamp }
Rate: illustrative peak of ~5K writes/sec
Notes: Returns immediately. Fan-out to follower timelines
happens asynchronously via a message queue.
POST /follows
Auth: Bearer token
Request: { followee_id: string }
Response: 201 Created or 409 Conflict
Rate: illustrative peak of ~500/sec
Notes: Updates the social graph. May trigger timeline
backfill for the new followee's recent tweets.
POST /tweets/{tweet_id}/likes
Auth: Bearer token
Request: (empty, user from token)
Response: 201 Created or 409 Already Liked
Rate: illustrative peak of ~50K/sec
Notes: Increments like_count. Eventual consistency
acceptable (counter can lag by seconds).
READ PATH
ββββββββββββββββββββββββββββββββββββββββββ
GET /timeline/home?cursor={token}&limit=20
Auth: Bearer token (user_id from token)
Response: {
tweets: [{
tweet_id, author_id, author_name, author_avatar,
content, media_urls[], created_at, like_count,
liked_by_me: boolean
}],
next_cursor: string,
has_more: boolean
}
Rate: illustrative peak of ~300K reads/sec
Latency: illustrative target of < 200ms P99
Notes: The critical hot path. Pre-computed timeline
or fan-out-on-read for high-follower accounts.
GET /users/{user_id}/profile
Response: { user_id, username, display_name, bio,
avatar_url, follower_count, following_count }
Rate: illustrative rate of ~100K/sec
Now look at what this API reveals about the architecture:
Each service in this diagram is tied to an operation or an explicit asynchronous requirement. The Tweet Service handles POST /tweets. The Timeline Service handles GET /timeline/home. The Fan-out Worker exists because the POST returns before the GET's pre-computed data is ready. The point is to make every boundary explainable.
That's the power of API-first design: the architecture explains itself because it's derived from concrete operations.
How the API drives your data model
This is the transition point to schema design. Every noun in your API responses becomes a candidate entity in your data model.
From the Twitter API above:
| API Response Field | Entity | Storage Implication |
|---|---|---|
tweet_id, content, created_at | Tweet | Core entity, high write volume |
author_id, author_name, author_avatar | User | Read-heavy, cache candidates |
follower_count, following_count | User (derived) | Pre-computed counters |
liked_by_me | Like (join) | Per-user per-tweet lookup |
next_cursor | Timeline position | Cursor state in cache or encoded in response |
media_urls[] | Media | Object storage, such as S3, stored separately from tweet |
The access patterns come straight from the GET endpoints:
GET /timeline/homeneeds tweets from all followed users, ordered by time, paginated. This is the critical query that drives the entire read architecture.GET /users/{id}/profileneeds user data plus aggregated counts. Simple point lookup with counters.
The useful shorthand is: "Your API is your data model in disguise. The nouns are your entities, the GETs are your queries, and the POSTs are your write operations." It is a starting point, not a substitute for validating indexes, invariants, and lifecycle requirements.
Common mistakes
Too many endpoints. You're designing a system, not building a REST API catalog. Three to five core operations is usually enough for an interview sketch. Listing every edge-case endpoint can consume the time needed to reason about architecture.
Missing pagination. Any large or unbounded list endpoint should expose a limit and a pagination strategy. Otherwise the response size, query cost, and behavior as the dataset grows are undefined.
No error responses. Mention at least one error case per write endpoint. "409 Conflict if the user already follows" shows you understand idempotency and constraint handling.
REST purism over clarity. Don't debate whether likes should be POST /likes or PUT /tweets/{id}/likes or POST /tweets/{id}/like. Pick one that's clear, state it, and move on. The interviewer cares about your architecture, not your HTTP method theology.
Skipping the API entirely. Jumping straight to "I'll have a write service and a read service" without defining what those services actually serve makes the architecture a guess.
Ignoring rate differences. Not all endpoints are equal. If your timeline read is 100x more frequent than your tweet write, say so. This drives caching, scaling, and resource allocation decisions.
The API-as-checklist trick
Before moving to architecture, read your API endpoints back as a checklist: "Under these illustrative assumptions, can my design handle POST /tweets at 5K/sec? Can it serve GET /timeline at 300K/sec under 200ms? Can it handle POST /follows updating the social graph?" If any answer is "I'm not sure," that's where you need to focus your architecture.
Trade-offs, limitations, and failure modes
API-first design clarifies the contract, but it can create false confidence if the surface is mistaken for the whole system. A public API should not expose every internal service boundary, and a clean endpoint does not prove that the underlying query, transaction, or fan-out is feasible. Keep the external contract stable while allowing internal components to change.
For each endpoint, check authentication and authorization, validation, idempotency, error responses, pagination, consistency, and versioning. A retry can duplicate a write unless the API has an idempotency key or an equivalent uniqueness rule. An immediate response can acknowledge persistence before asynchronous work completes, so the contract should state whether the result is committed, queued, or merely accepted. A partial failure needs a defined status and recovery path.
Interview application
30-second answer
"I sketch the smallest API that covers the core user actions before drawing components. For each operation I define the request, response, error behavior, and rate or latency assumption, then ask what the fields imply about storage, pagination, consistency, and asynchronous work. The contract becomes a checklist for the architecture rather than a catalog of every endpoint."
5-minute explanation
"First I state the scope and list three to five core operations. For each one I write the method, path, authenticated identity, request shape, response shape, and at least one failure case. List endpoints get a bounded page size and a stable cursor or an explicit reason to use offsets. Write endpoints get idempotency and conflict behavior where retries could duplicate work.
"Then I read the fields as architecture clues: response nouns suggest entities, GET predicates suggest indexes and partition keys, inline fields may require hydration or denormalization, and an immediate acknowledgment may imply a queue behind the synchronous boundary. I use the resulting rates and latency targets as illustrative assumptions until the prompt or a benchmark supplies real values. Finally I trace the read and write paths, state the trade-offs and failure modes, and stop when the contract is sufficient to drive the next phase."
Level-appropriate depth
For an earlier-level interview: Define two or three core endpoints before designing architecture. Request and response shapes demonstrate concrete thinking; perfect REST semantics are less important than clear inputs and outputs.
For a senior-level discussion: Make the API sketch drive the architecture. Be ready to explain cursor choice, retry safety, consistency, and the boundary between public and internal contracts.
For a broader system-design discussion: Use the API to scope the conversation and distinguish the external contract from service-to-service interfaces. API versioning, backward compatibility, and ownership may become part of the design.
Across levels, the common failure mode is spending too long on the API. Write the endpoints, note their implications, and transition to architecture; go deeper only when the contract itself is the interesting constraint.
Test Your Understanding
Use the following prompts to practice extracting one or two architectural consequences from an API contract.
Recap
- Define the API before drawing architecture. The API is a contract that forces clarity about inputs, outputs, and data flow. Without it, your architecture is speculation.
- Use the 3-step sketch: list 3 to 5 core operations, define request/response shapes with specific fields, and identify hidden requirements from those fields. Total time: 3 to 5 minutes.
- Every field in your API response has an architectural implication. Cursor tokens imply ordered storage, inline author names imply denormalization, like counts imply pre-computed aggregates.
- Default to REST naming in interviews. Switch to RPC-style only for clear actions (estimate, refund, search). Don't waste time on HTTP method debates.
- Large or unbounded list endpoints need pagination. Cursor-based pagination is a strong default for feeds and timelines; offsets are acceptable when the dataset is small or page-number navigation is a real requirement.
- The API drives your schema. Response nouns become entities, GET query patterns become access patterns, and POST frequency determines write throughput requirements.
- Read your API as an architecture checklist. If your design can't serve every endpoint at the stated scale and latency, you've found the gap you need to solve.
Related Concepts
- Approach & Structure - Places API design in the wider requirements-to-architecture flow.
- Scoping the Problem - Narrows the product prompt before the API contract is sketched.
- Schema Design Approach - Turns API entities and access patterns into tables, keys, and indexes.
- Common Pitfalls - Covers missing pagination, unclear flows, and unjustified complexity during an interview.
Related Articles
How to turn a vague system design prompt into a focused build plan in under 5 minutes, so you design the right system instead of a generic one.
A 6-phase framework for any system design interview: requirements, NFRs, APIs, flows, architecture, and deep dives, with time splits for each.
How to design a data schema in a system design interview, starting from entities, mapping access patterns, picking storage, and making normalization decisions.