API Design

Interview Prep · Lesson 06

How leading APIs do it

Naming what a real company does — "Stripe uses a token bucket in Redis," "GitHub versions with a dated header" — turns a generic answer into a credible one. This page collects how Stripe, AWS, GitHub, HubSpot and Shopify actually implement the patterns this course teaches, so you can cite real systems on the whiteboard. Exact numbers drift; always confirm against current docs (linked at the bottom).

⏱ 16 minDifficulty: coreReference

By the end you'll be able to

Rate limiting

Almost every public API limits you, but the algorithm and the headers differ. Note the split: token/leaky bucket (smooth, allows controlled bursts) vs fixed windows, and whether limits are per-account, per-IP, or per-cost.

CompanyAlgorithmLimit (typical)Signals to the client
StripeToken bucket (in Redis)~100 read & ~100 write req/s (live)429 + Retry-After
ShopifyLeaky bucket (REST); calculated query cost (GraphQL)e.g. 40-req bucket, leaks ~2/s (standard REST)429 + Retry-After; X-Shopify-Shop-Api-Call-Limit
GitHubFixed hourly quota + secondary abuse limits5,000 req/hour (authenticated)X-RateLimit-Limit/Remaining/Reset/Used; 403/429
AWS API GatewayToken bucket (steady rate + burst)Configurable per stage/method + usage-plan quotas429 Too Many Requests
HubSpotRolling window, per account~100–190 req / 10 s (varies by tier) + daily cap429; X-HubSpot-RateLimit-*
🎯 Deep dive: Stripe's four rate limiters

Stripe publicly described running four cooperating limiters, not one — a great thing to cite when asked "how would you protect an API under load?":

  1. Request rate limiter — a token bucket per account (implemented in Redis) capping requests/second; the everyday limit that returns 429.
  2. Concurrent request limiter — caps how many requests one account can have in flight at once, protecting against a few slow expensive calls hogging workers.
  3. Fleet usage load shedder — reserves a slice of total capacity for critical request types (e.g. creating a charge) so non-critical traffic can't starve the money path during a surge.
  4. Worker utilization load shedder — when workers are nearly saturated, sheds the least-important traffic first to keep the system alive.

The lesson: at scale, "rate limiting" becomes layered — a fairness limiter plus load shedders that protect the most important work. (See the rate-limiting lesson for the algorithms.)

API versioning

CompanySchemeHow it's selected
StripeDate-based (e.g. 2024-06-20)Each account pinned to the version it onboarded on; override per request with the Stripe-Version header. Backward compatibility held for years.
GitHubDate-basedX-GitHub-Api-Version header (e.g. 2022-11-28); announced sunsets.
AWSDate-based service versionse.g. EC2 Version=2016-11-15 request parameter; SDKs pin it.
HubSpotURL path/crm/v3/... — major version in the path; documented deprecation dates.
AzureDate-based query param?api-version=2023-07-01.

The standout is Stripe's pinning model: when you first call the API, your account is locked to the current dated version. New dated versions ship constantly, but your integration keeps seeing the exact shapes it was built against until you opt to upgrade (and read the changelog of what changed between dates). It's the cleanest answer to "how do you evolve without breaking anyone" — additive changes go out freely; anything breaking is gated behind a new date. (See versioning and evolving APIs.)

Authentication

CompanyMechanismNotable detail
StripeAPI keys (Bearer)Secret (sk_live_…) vs publishable (pk_…); restricted keys with per-resource scopes; separate test/live keys.
AWSSignature V4 request signingClient signs each request: build a canonical request, derive a signing key via an HMAC chain (date → region → service → aws4_request), send the signature in Authorization with X-Amz-Date. The secret never travels.
GitHubPAT, OAuth App, GitHub AppA GitHub App signs a short-lived JWT with its private key, then exchanges it for an installation access token scoped to one install.
HubSpotOAuth 2.0 + private-app tokensLegacy API keys were deprecated (2022) in favour of OAuth and private-app access tokens.
GoogleOAuth 2.0 + OIDCAccess tokens for authZ, ID tokens (JWT) for authN.

The contrast worth naming: Stripe's bearer keys are dead-simple (great DX) but the secret is sent on every call (TLS-protected); AWS SigV4 never transmits the secret and signs the request body for integrity, at the cost of complexity. Different points on the simplicity↔security curve. (See keys & JWTs and OAuth.)

API gateways

SystemTypeWhat it handles
AWS API GatewayManagedRouting, throttling (token bucket), usage plans + API keys, authorizers (Lambda/Cognito/JWT), request/response mapping, stages + canary, caching, WAF.
KongSelf-hosted (on NGINX)Plugin model: auth, rate limiting, transformations, logging.
EnvoyProxy / mesh data planeL7 routing, retries, circuit breaking, observability; core of many service meshes.
Netflix Zuul / Spring Cloud GatewaySelf-hostedEdge routing, auth, and cross-cutting filters at massive scale.

The pattern across all of them: the gateway is where the cross-cutting concerns live — auth, rate limiting, TLS termination, routing — so individual services don't each reimplement them. (See the API gateway deep dive.)

Idempotency

The money-handling APIs converged on the same design: a client-supplied idempotency key the server uses to dedupe retries.

CompanyHeaderBehaviour
StripeIdempotency-KeyClient sends a unique key (e.g. a UUID) on a POST; Stripe stores the result and replays the same response for ~24 hours.
PayPalPayPal-Request-IdSame idea for safely retrying payment calls.
AdyenIdempotency-KeyDedupes duplicate payment submissions.

(See idempotency and the payments case study.)

Webhook signatures

CompanyHeaderScheme
StripeStripe-SignatureHMAC-SHA256 over timestamp + "." + payload with the endpoint's signing secret; includes a t= timestamp checked against a tolerance window to block replays. Failed deliveries are retried with backoff.
GitHubX-Hub-Signature-256HMAC-SHA256 over the raw body with the webhook secret.
HubSpotX-HubSpot-Signature (v3)HMAC-SHA256 over method + URI + body + timestamp.

The universal rule: verify the signature on the raw body before trusting a webhook, and make handlers idempotent because all of them retry. (See designing webhooks and debugging webhooks.)

Pagination

CompanyStyleMechanism
StripeCursorlimit + starting_after/ending_before (an object id as the cursor); response has has_more.
GitHubPage + Link header?page=&per_page= with an RFC-5988 Link header carrying rel="next"/"prev"/"last".
SlackCursorcursor + response_metadata.next_cursor.

Note the trend: high-write APIs (Stripe, Slack) prefer cursors because offset pagination drifts and slows on large, changing datasets. (See pagination.)

✅ How to use this in an interview

Drop one concrete reference per decision: "I'd rate-limit with a token bucket — the way Stripe does, per-account in Redis, returning 429 with Retry-After." "I'd version like Stripe: date-based, pin accounts, additive-only between dates." It signals you've read real systems, not just theory. Don't over-claim exact numbers — say "around" and note they change.

⚠️ Common trap

Citing a company incorrectly is worse than not citing one. Don't say "AWS uses JWTs for everything" (it's SigV4 request signing) or "Stripe uses URL versioning" (it's dated header/pinning). If you're unsure of the specifics, describe the pattern ("a token-bucket limiter returning 429") rather than misattributing it.

🧠 Quick check

1. Stripe's everyday rate limiter is built on which algorithm?

Stripe described a per-account token bucket implemented in Redis as its request rate limiter, alongside a concurrency limiter and two load shedders. It returns 429 with Retry-After.

2. How does Stripe stop a new API version from breaking existing integrations?

Stripe uses date-based versions and pins each account to the version it started on. Additive changes ship freely; breaking changes are gated behind a new dated version the integrator chooses to adopt.

3. AWS Signature V4 differs from a Stripe bearer key mainly because:

SigV4 signs each request with a key derived from the secret via an HMAC chain, so the secret itself never goes over the wire and the request body is integrity-protected — more complex than a bearer key, but stronger.

Key takeaways

Sources & further reading