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.

When not to use interview scaffolding as production design

Why method still matters

Interview structure exists to show judgment under time pressure: clarify, estimate, API surface, deep dive, failures. Standout candidates spend most time on the correctness and failure path of the write path, not drawing every box in the system.

Failure-mode drill (always close with this)

For any design: double-submit, dependency down, hot key, deploy skew, multi-region lag. Name the signal, the mitigation, and the user-visible degradation.

🧠 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.

Under the hood: how leading platforms enforce API rules

Leading developer platforms run specialized engines to enforce rate limiting, versioning, and security constraints:

  1. Date-Based Version Pinning (Stripe): The gateway maintains a map of account_id → api_version_date. When a request arrives, the gateway applies version-specific transformation filters (up-casting or down-casting request/response fields) to align the payload with the account's pinned API version.
  2. Cost-Based Leaky Bucket (Shopify): Rather than counting requests, the platform calculates a query cost based on the depth of requested GraphQL nodes, subtracting cost tokens from a Redis leaky-bucket counter in real time.
  3. SigV4 Request Authentication (AWS): The gateway validates incoming requests by computing an HMAC signature chain based on HTTP components (path, body, headers, timestamps) and comparing it to the client's signature, preventing transit modifications.

How to debug & inspect it

Debugging multi-platform client integrations requires validating response headers and parsing signature payloads.

Validate lesson format and HTML layout locally:

$ python3 verify_lessons.py Looking for required h2 sections... $ npx htmlhint lessons/prep-06-industry-practices.html No HTML syntax errors found.

Symptom → Cause → Fix:

Symptom Likely Cause Fix
Client requests to AWS APIs fail with 403 SignatureDoesNotMatch Clock skew on the client machine exceeds the 5-minute maximum window allowed by SigV4 Synchronize the client system clock with NTP and regenerate the signature timestamp
Shopify integration gets blocked under high bulk traffic Complex nested GraphQL queries exceed the query-cost limit bucket Optimize GraphQL queries to reduce depth, specify limit limits, and introduce client delay pauses
Webhook payload parsing errors in application code Comparing webhook signatures using standard string comparison (vulnerable to timing attacks) Validate the HMAC-SHA256 signature using a constant-time comparison helper function

Key takeaways

Sources & further reading