Blog 10

API Management

July 20, 202617 min readBy Ashutosh Singh

Inside your system, everything is allowed to change. At the boundary, everything you expose becomes a promise.

Bonus E ended at a threshold: the moment routing understands requests — Layer 7 — a bigger question appears. Who shapes those requests? Who decides what a client may ask, how often, in what form, and what they're owed back?

We've spent nine articles building the inside of the system: caches, brokers, storage, keys, fleets, balancers. Today we design the outside surface — the part other people build on top of.

And "other people" is the whole point. An endpoint you wrote for one frontend last year is now called by the web app, the mobile app, two internal teams, a partner integration, and a cron job nobody remembers writing.

You didn't ship an endpoint. You signed a contract.

The Simple Mental Model: The Restaurant Menu and the Front Desk

Part 9 gave our restaurant its kitchen — counters, stations, food trucks, temporary cooks. Today, the two things customers actually see.

The API is the menu. The menu tells customers what they can ask for, in what format, and what they'll get back. Butter chicken, serves two, arrives in twenty minutes. The customer doesn't know, and must never need to know, how many chefs, ovens, or stations exist behind the wall.

The API gateway is the front desk. The host checks who's allowed in, seats parties at the right counter, manages the crowd at the door, enforces house rules, and keeps a rush from overwhelming the kitchen.

Four lessons packed in there: API = contract, the menu is a promise, not a suggestion. Backend services = kitchen stations, free to reorganize as long as the menu stays honest. Gateway = front desk, policy and routing at the entrance, before anything reaches a station. And bad API design exposes kitchen chaos to customers — if ordering dal requires knowing which chef is on shift, the menu has failed.

An API Is a Contract, Not Just a URL

Here's the mindset shift this article exists for. A URL is where the API lives. The contract is everything a client is entitled to rely on: request format (what to send, which fields, which types), response format (what comes back, guaranteed), error format (how failure is communicated), authentication expectation (how a caller proves who they are), authorization expectation (what each caller may do), pagination behavior (how large results are walked), rate limits (how much is too much), idempotency behavior (what happens when a request is retried), versioning rules (how change arrives), backward compatibility expectations (what will never silently change), and ownership (who answers when it breaks).

And the contract has audiences, each with different stakes: internal APIs (your teams — moves fastest), partner APIs (specific external companies — contractual, sometimes literally), public APIs (anyone — the slowest to change, because you can't email "everyone"), and external client APIs (your own web/mobile apps — deceptively public, since old app versions live on phones for years).

The one-sentence summary of this entire section: changing an API without thinking is not a code change. It is a contract change, and the other signatories weren't in the room.

REST, gRPC, and GraphQL: Three Different Conversations

Three ways to structure the conversation at the boundary. Not competitors — different tools for different audiences.

REST: The Universal Menu

Resource-oriented, riding plain HTTP. Its grammar is the web's grammar:

Resources are nouns:       /orders, /orders/123, /users/42/orders
Methods are verbs:         GET (read), POST (create), PUT/PATCH (update), DELETE
Status codes are outcomes: 200 OK, 201 Created, 404 Not Found, 429 Slow Down

Why it wins so often: simple, universally understood, tool-friendly, cacheable with plain HTTP machinery (Part 6's layers apply directly).

Where it creaks: the server decides response shape. Mobile needs three fields, gets forty (over-fetching). The order screen needs order + user + payment status — three endpoints, three round trips (under-fetching, and its ugly cousin, the N+1 call pattern: one list call, then one call per item).

Reach for REST for public APIs, CRUD-style domains, web/mobile backends — the default, deliberately.

gRPC: The Kitchen Intercom

Contract-first: you define the schema (protobuf), and client/server code is generated from it. Communication is compact binary over HTTP/2, with streaming built in.

Why internal systems love it: speed, tiny payloads, and a compiler that enforces the contract — Part 9's microservices calling each other thousands of times per second is exactly its habitat.

Its trade-offs: less friendly for browsers and public consumption in many cases; binary payloads mean you can't just read a request the way you eyeball JSON. Debugging requires tooling, not curiosity.

Reach for gRPC for internal service-to-service traffic, low-latency high-throughput paths, polyglot fleets sharing one schema.

GraphQL: The Custom Order

One endpoint. The client writes a query naming exactly the fields it needs, across related entities, and gets exactly that shape back.

The genuine win: over-fetching and under-fetching evaporate. The mobile app asks for three fields; the dashboard composes order + user + payments in one round trip. For frontends with complex, changing data needs, it's a gift.

The bill, itemized: the flexibility moves complexity inward. Resolvers can hide N+1 database patterns behind one innocent query. Any client can compose an arbitrarily expensive request, so query-cost limits stop being optional. Authorization becomes field-level. And HTTP caching gets awkward when every request is a unique POST to one URL.

Side by Side

RESTgRPCGraphQL
Conversation styleFixed menuTyped intercomCustom order
ContractConvention + docsSchema-enforced (protobuf)Schema-enforced (GraphQL SDL)
PayloadJSON over HTTPBinary over HTTP/2JSON over HTTP (usually POST)
Best audiencePublic, web/mobile, CRUDInternal service-to-serviceComplex client-driven frontends
CachingEasy — plain HTTPCustomHard — needs its own strategy
Watch out forOver/under-fetching, chattinessBrowser friction, opacityResolver N+1, query cost, field auth

When Not to Use GraphQL

GraphQL deserves its own warning label, because it's the one most often adopted as fashion. GraphQL is not a default upgrade over REST. Think twice when: the domain is simple CRUD (you'd be adding a query language to a menu with four dishes); clients don't actually need flexible queries (one frontend, stable screens); the team lacks schema governance (a flexible contract with no owner rots faster than a rigid one); query complexity can't be controlled (no depth/cost limits means any client can write your next outage); authorization is field-level and complicated (every resolver becomes a security decision); caching must stay simple (Part 6's HTTP machinery won't help you here); backend teams aren't ready for resolver performance work (the N+1s don't announce themselves); or it's public with abuse risk (an unlimited query language, offered to the internet, is an invitation).

The honest framing: GraphQL trades client simplicity for server responsibility. Take the trade when the client-side win is real, not because the logo is newer.

API Gateway: The Front Door of the System

Part 9 built a fleet of services. Without a front door, every client must know every service's address, every service must implement its own auth, rate limiting, and logging, and your internal topology becomes your public interface.

The API gateway is the single entrance that fixes this. Its job list: routing (/api/orders to the order service, /api/payments to payments — Bonus E's L7 routing, now with a job title), authentication handoff (verify who's calling once, at the door, and pass a trusted identity inward), authorization at the edge (coarse-grained "may this caller reach this API at all" checks, where appropriate), rate limiting, throttling, and quotas (per client, per key, per plan — the crowd control), request validation (malformed requests bounce at the door, not deep in a service), response transformation (shaping internal responses for external consumption), protocol translation (REST outside, gRPC inside — the client never knows), versioning support (routing /v1 and /v2 to their respective implementations), observability, logging, and correlation IDs (every request stamped, per Part 7's discipline, and counted at one chokepoint), and shielding (internal services never face the internet directly).

One warning, learned expensively across the industry: the gateway enforces cross-cutting policy. It is not the place for business logic. The moment order-calculation rules live in the gateway, you've rebuilt the monolith at the worst possible location: in every request's path, owned by no product team, deployed with maximum blast radius. Policies and routing at the door; decisions in the services.

And two structural cautions, since this is the front desk after all: the gateway sits in every request's path, so it can become the bottleneck — scale it like any fleet, Bonus E applies to the balancer in front of your gateways too — and treat it as the single point of failure it naturally wants to be.

Load Balancer vs Reverse Proxy vs API Gateway

Three boxes that look identical on a whiteboard. The intent differs. The load balancer (Bonus E) is about distribution — spreads traffic across healthy instances, cares about health, fairness, and failure, doesn't know what an "API" is. The reverse proxy (Part 4) is about fronting — sits before servers, forwards, terminates TLS, caches, compresses, routes, knows HTTP but doesn't know your API is a product. The API gateway is about contract enforcement — understands APIs as products: auth integration, rate limits, quotas, validation, versioning, per-client observability.

In real platforms the boundaries blur — one deployment often plays all three roles. Fine. What matters architecturally is knowing which job you're configuring: spreading load, fronting servers, or enforcing a contract. Confusion between those is how rate limits end up nowhere and business logic ends up everywhere.

API Versioning and Backward Compatibility

The uncomfortable truth this section orbits: APIs live longer than the code behind them. Often longer than the team that wrote them. So change needs a protocol of its own.

Where the version lives: URL versioning (/v1/orders, /v2/orders — blunt, visible, cache-friendly, universally understood) or header / media-type versioning (the URL stays clean, the version rides a header — elegant, and invisible enough that clients forget it exists). Either works. Having a rule is what matters.

The change taxonomy — memorize this split. Additive changes are safe: new optional fields, new endpoints, new enum values (handled tolerantly). Old clients keep working because nothing they relied on moved. Breaking changes are contract violations: removing a field, renaming one, changing a field's meaning or type, tightening validation. The last one is the assassin — the field still exists, still parses, and now quietly means something else. No error is thrown. Wrong behavior just ships.

When breaking is unavoidable, the adult sequence: publish the new version, mark the old one deprecated with a dated window, actively migrate clients (you can see who's still calling — see observability, below), and only then retire it.

Because the stakes are never abstract: breaking an API breaks other teams, other apps, partner integrations, or paying customers — everyone who believed your promise.

The contract needs paperwork. A promise nobody wrote down is a rumor. Schema-first thinking — an OpenAPI spec or equivalent as the source of truth, with code and docs generated from it — turns the contract into an artifact that can be reviewed, diffed, and owned. Every API gets an owner, a lifecycle stage (active, deprecated, retired), and a change process where a breaking diff is treated like what it is: a contract amendment, not a commit.

Designing APIs for Scale — and for Failure

The contract also has physics. An API that works for ten clients can flatten a fleet at ten thousand, usually through one of these:

Paginate every list endpoint. GET /orders without pagination is a promise to return all orders forever — a promise that gets heavier every day. Page size limits are load limits in disguise.

Bound the filters and sorts. Flexible filtering is lovely until a client sends the query your database can't index. Unbounded search is an outage with a query-string.

Cache what's safely cacheable. GET responses with honest TTLs ride Part 6's entire stack for free — one reason REST's plain-HTTP shape keeps earning its keep.

Make writes retry-safe with idempotency keys. The client sends a unique key with the POST; the server remembers it; the retry becomes a no-op. This is Part 7's effectively-once lesson arriving at the front door, and Bonus E warned you the balancer will retry. (Part 21 builds the full machinery.)

Go async for heavy work. A request that takes minutes shouldn't hold a connection hostage: accept it, return a status URL, let Part 7's queues do the lifting. 202 Accepted is an underused word for "yes, soon."

Don't be chatty. Ten round trips to render one screen is Part 7's coupled chain wearing a client hat. Batch endpoints, or an honest GraphQL adoption, exist for exactly this.

Set the guardrails: rate limits and quotas per client, request and response size limits. Every unbounded dimension is a resource-exhaustion invitation (Part 9's failure list applies to the front door too).

And for failure: every API call needs a timeout (an API without one is a thread leak with good intentions); retries follow Bonus E's budget-and-backoff rules, idempotent routes only; circuit breakers stop hammering a failing downstream (concept now, full treatment in Part 22); and graceful degradation — a partial answer with a warning beats a perfect 500.

Error Design: The Most Ignored Part of API Design

Everyone designs the success path. The error path is where integrations actually live or die.

A good API error tells the client seven things:

{
  "code": "ORDER_ITEM_OUT_OF_STOCK",      // stable, machine-readable
  "message": "Item 8841 is out of stock", // human-readable
  "field": "items[2].sku",                // what rule/field failed
  "retryable": false,                     // is retry safe?
  "requestId": "req_7f3a9c"               // correlation ID for support
}

…plus the right status code family: 4xx (client, fix your request — including 429, slow down) vs 5xx (server, our fault — retry may help). That one bit — whose fault is it, and is retry safe? — is the difference between a client that self-heals and a client that retry-storms you (Bonus E's nightmare, invited by your own error design).

The anti-patterns everyone recognizes: vague 500 for everything, error messages that change wording between releases (clients will parse your strings if you give them nothing better), and three formats across five endpoints. Random errors turn every integration into archaeology.

An error format is part of the contract. Design it once, org-wide, and never surprise anyone with it again.

APIs and Observability

The front door is the best measurement point in the whole system — every client interaction passes through it. Instrument it accordingly: request counts and latency (p95/p99, not averages — Bonus G taught you why), error rates and status-code distribution (a rising 429 line means your rate limits are working, a rising 5xx line means something else is not), rate-limit hits per client, client identity on every request (you cannot deprecate /v1 if you don't know who still calls it), correlation IDs stamped at the gateway and carried through every hop (Part 7's discipline, made mandatory), and downstream latency (so you can tell "the API is slow" from "the API is waiting").

Part 12 turns this list into a discipline. For now: an API without metrics is a promise you can't verify.

Security at the Front Door: A Preview

The gateway checks the visitor's pass, but where do passes come from? Authentication (who are you: tokens, API keys), authorization (what may you do), and secrets (how services prove themselves to each other) have been waved at all series long. Part 11 finally opens the vault: OAuth2, JWT, SSO, and secrets management — the machinery behind every "authentication handoff" this article casually mentioned.

Common Mistakes Engineers Make with APIs

Treating APIs as controller methods — an endpoint is code, an API is a promise, and this mindset gap causes every mistake below. Exposing the database schema directly — now your table design is a public contract, and every migration is a breaking change. No versioning strategy — version one of everything, forever, changed in place. Breaking clients without a migration path — the deprecation window is the difference between evolution and betrayal. Inconsistent error formats — five endpoints, three shapes, zero trust. No pagination on list APIs — works in the demo, times out at ten thousand rows. Unbounded filters and search — the client-supplied outage. Ignoring idempotency on writes — Bonus E retries plus non-idempotent POST equals double orders. Business logic in the gateway — the central-brain monolith, rebuilt at the door. Chatty APIs — N+1 round trips as a client experience. GraphQL by fashion — the trade taken without the client-side win. Ignoring field/resource-level authorization — especially fatal in GraphQL, where every field is reachable. No documentation, no owner — an unowned contract is Part 7's unowned queue, a breach with no adult assigned. No rate limits or quotas — every client trusted infinitely, including the broken one in a retry loop. Not tracking usage per client — you'll meet your unknown consumers on deprecation day, angry.

Applying C.R.E.D to API Design

Clarify — Who are the consumers: internal, partner, public, your own apps? What do they actually need to do?

Requirements — The contract items, per audience: auth expectations, rate limits, latency targets, compatibility promises, error semantics.

Estimate — Calls per client per day, payload sizes, list-endpoint cardinalities, the cost of your most expensive allowed query.

Design — The protocol per audience (REST outside, gRPC inside is a common shape), the gateway policies, the versioning rule, the error format, the idempotency story, and the owner's name.

A good architect does not ask only, "What endpoint should we expose?" A good architect asks, "Who will use this API, what contract are we creating, how will it evolve, how will it be protected, and what happens when traffic or clients behave badly?"

From My Journey: An Architectural Lesson

In many systems, APIs start as simple controller endpoints — created quickly to support one frontend or one integration. There's nothing wrong with that beginning. It's how nearly everything useful starts.

Then dependence accumulates, quietly. The web app calls it. The mobile app joins. An internal team discovers it. A partner integrates against it. An automation job somewhere starts polling it on a schedule nobody documented.

What started as a quick endpoint has become a long-lived contract — and nobody wrote it down as one.

The realisation that follows: creating an endpoint was never the hard part. The hard part is everything after — evolving it without breaking consumers you can't all name, protecting it from clients that misbehave, keeping it observable enough to know who depends on what, and making ownership clear enough that someone answers when it breaks.

Each protocol earned its place as a trade, not a fashion: REST for its universality, gRPC for internal contracts that need speed and enforcement, GraphQL where client-driven data needs justify its server-side bill, and the gateway to hold the cross-cutting rules, without becoming a brain.

The lesson that stuck: API design is boundary design. A messy API boundary leaks internal chaos to every client that depends on it.

An API is not just how clients call your system. It is how your system makes promises.

Key Takeaways

  • An API is a contract with audiences — internal, partner, public, and your own apps — each with different stakes in your promises.
  • REST is the universal default; gRPC is the typed intercom for internal traffic; GraphQL trades client simplicity for server responsibility. Choose by audience, not fashion.
  • The gateway enforces cross-cutting policy at the front door — auth handoff, rate limits, validation, versioning, observability — and must never become the business-logic brain.
  • Load balancer distributes, reverse proxy fronts, gateway enforces contracts — one box may do all three; know which job you're configuring.
  • Additive changes are safe; removals, renames, and meaning-changes are contract violations. Deprecate with dates, migrate with data.
  • Scale lives in the contract: pagination, bounded filters, idempotency keys, async for heavy work, limits on everything unbounded.
  • Error design is contract design — stable codes, retry guidance, correlation IDs. Vague 500s breed retry storms.
  • Instrument the front door: per-client usage, p99s, status distributions. You can't deprecate what you can't see.
  • Schema-first, owned, versioned: a promise nobody wrote down is a rumor.

Interview Lens

The API question is rarely "design an API." It's hiding inside every system design prompt the moment you draw the client.

The classic probe: "REST, gRPC, or GraphQL for this?" The weak answer picks a favorite. The strong answer picks by audience: "REST for the public and mobile surface — universal, cacheable; gRPC for internal service-to-service where the schema enforcement and latency pay off; GraphQL only if the frontend genuinely composes flexible views, and then with query-cost limits and field-level auth from day one."

Expect the follow-ups: "How do you version it?" — URL or header, but lead with the additive-vs-breaking taxonomy and a dated deprecation window. "How do you handle a breaking change with unknown consumers?" — per-client usage metrics first ("you can't deprecate what you can't see"), then migration windows. "Where do rate limits live?" — at the gateway, per client identity; name quotas for partners. "How is a retried POST safe?" — idempotency keys; volunteering this unprompted is the differentiator, because it connects the balancer's retries (Bonus E) to the contract. "What's in your error response?" — almost nobody has an answer prepared; the seven-field error body wins the room.

Real-World Engineering Lens

Review API diffs like schema migrations. A contract change gets the same scrutiny as a database change, because it breaks more people.

Standardize the error envelope org-wide, once. One format, one document, every service. The cheapest integration accelerator that exists.

Write the deprecation policy before you need it. "Ninety days, dated header, per-client outreach" is a paragraph today or a war room later.

Dashboard per-client usage from day one. Deprecation day is when you meet every consumer you didn't know you had.

Keep a one-line gateway rule in the platform doc: policy and routing only — if it knows what an order costs, it's in the wrong layer.

What's Next

The front desk checks every visitor's pass. We never explained where passes come from.

Blog 11 — Security and Authentication: OAuth2, JWT, SSO, and Secrets. Who are you, what may you do, and how do services prove themselves to each other — the machinery behind every "authentication handoff" this article waved at, plus the secrets that keep the whole building locked.

Question for Readers

Pick the most-called endpoint in your system. If you renamed one response field today, who breaks — web, mobile, a partner, a cron job?

And the sharper question: would you find out from your dashboards, or from their angry ticket?

The gap between those two answers is your API management maturity.