🛸 Hitchhiker's Guide — Phase 09: RESTful APIs, JSON & Secure AuthN/AuthZ
Read this if: you can build a REST controller but "design our API edge" means something vaguer than it should. The gateway, the status codes, the idempotency, the rate limiter — they're a small set of sharp rules, and this is the compressed tour the WARMUP derives slowly. Skim it, then read the WARMUP, then build the gateway.
0. The 30-second mental model
Every request to a managed API crosses a gateway first, and the gateway makes the same
four decisions in the same order: does this route exist (404) → who is this and may
they (401/403) → are they over budget (429) → call the backend (200). One
sentence to tattoo: the gateway is a pipeline, and the order is the security policy —
route before auth (nothing to authorize otherwise), auth before rate-limit (don't let
probing cost rate budget), backend last (it's the expensive thing you're shielding).
1. The methods, one breath
GET safe, idempotent, cacheable read
HEAD safe, idempotent read headers
PUT idempotent (NOT safe) replace wholesale → retry-safe
DELETE idempotent (NOT safe) remove → retry-safe
POST neither create/process → NOT retry-safe
PATCH not guaranteed idempotent partial update → retry only if you made it so
Idempotent = doing it twice == doing it once == safe to retry on a timeout. That single
property is why PUT-to-a-known-id and POST-with-an-idempotency-key are how principals
design writes — so a client can retry through the network's "did it happen?" fog.
2. The status codes to tattoo on your arm
| Code | Means | Client should |
|---|---|---|
200/201/204 | OK / Created / No Content | proceed |
400 | malformed request | fix the request, don't retry as-is |
401 | un-authenticated (no/bad/expired token) | re-authenticate |
403 | authenticated, not authorized (wrong scope) | get more scope (re-auth won't help) |
404 | no such route/resource | check the URL |
409 | conflict (optimistic concurrency) | re-read, re-apply |
415/406 | wrong Content-Type / Accept | fix media type |
429 | over rate/quota | back off Retry-After seconds |
5xx | server failed (transient) | retry with exponential backoff (P14) |
The one rule under all of it: 4xx is your fault (don't retry unchanged), 5xx is our
fault (retry with backoff), 429 is "slow down" (back off, then retry).
3. 401 vs 403 vs 429 — the thing they actually test
401 "I don't know you." → refresh your token
403 "I know you. The answer's no." → ask for more scope
429 "I know you, you're allowed, → wait Retry-After, retry
but not this fast."
Get 401/403 backwards and you cause an incident: return 401 on a forbidden call and
a smart client refreshes its token and retries the forbidden call forever (refresh storm
- hot loop). Return
403on an expired token and the client gives up instead of refreshing.
4. The APIM nouns (use them and you sound like you've shipped one)
Product = bundle of APIs + a subscription (key) + product policies
API = operations sharing a base path + backend
Operation = one METHOD + URL template (GET /users/{id})
Policy = inbound → backend → outbound → on-error
Policies compose top-down (global → product → API → operation), like nested middleware. The
inbound section is where 90% of the action is: validate-jwt, rate-limit-by-key,
transform, route. An inbound policy can short-circuit the whole pipeline (a failed
validate-jwt returns 401 and the backend is never called).
5. The numbers you'll actually use
| Thing | Number / rule |
|---|---|
| Idempotent methods | GET HEAD PUT DELETE (+ OPTIONS/TRACE); not POST, not PATCH |
Retry-After on 429 | (1 − tokens) / refill_per_sec, rounded up, min 1 s (RFC 9110 §10.2.3) |
| Token bucket | tokens = min(capacity, tokens + elapsed·refill); spend 1 per call |
capacity vs refill | capacity = burst you tolerate; refill = sustained rate you guarantee |
| Rate-limit vs quota | rate-limit = calls/second (burst); quota = calls/day (fairness/billing) |
Clock skew (JWT exp) | Entra default leeway ~±300 s (P03) |
scp vs roles | scp = delegated (user present, space-delimited); roles = app-only (daemon) |
| Pagination default | cursor/keyset, not offset (offset drifts + slows at depth) |
6. az / policy one-liners
# Create an APIM instance (Consumption tier = serverless, pay-per-call).
az apim create -n my-apim -g rg --publisher-email me@x.com --publisher-name "Me" --sku-name Consumption
# Import an API from an OpenAPI spec.
az apim api import -g rg --service-name my-apim --api-id users \
--path users --specification-format OpenApi --specification-url https://.../openapi.json
# Show a subscription key (the per-consumer identity/meter).
az apim subscription list -g rg --service-name my-apim -o table
<!-- The two policies that are 90% of the edge: authenticate + throttle. -->
<inbound>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401">
<openid-config url="https://login.microsoftonline.com/{tid}/v2.0/.well-known/openid-configuration" />
<audiences><audience>api://my-api</audience></audiences> <!-- aud check: anti confused-deputy -->
<required-claims><claim name="scp" match="all"><value>users.read</value></claim></required-claims>
</validate-jwt> <!-- missing scope → 403 -->
<rate-limit-by-key calls="10" renewal-period="1"
counter-key="@(context.Subscription.Key)" /> <!-- 10/s per consumer; over → 429 -->
</inbound>
7. War story shapes you'll relive
- "Clients are hammering Entra with token refreshes." → someone returned
401for an authorization failure. The client thinks the token's bad, refreshes, retries the same forbidden call, loops. Fix: it's a403. The code is the contract. - "We double-charged customers during the outage." → retry-on-
5xxlogic replayed a non-idempotentPOST /charge. Fix: idempotency keys, so a replay is a no-op. - "
/users/mereturns the wrong user." → route matching picked/users/{id}withid="me"over the literal/users/me. Fix: most-specific-wins (literal beats wildcard). - "The throttle isn't working / is too aggressive." → confusing rate-limit (burst) with
quota (monthly), or sizing
capacity(burst) wrong. Draw the bucket; do the arithmetic. - "Service A accepts service B's token." →
validate-jwtchecked the signature but notaud. Confused deputy. Always checkaud. - "Our
429s aren't helping." → noRetry-After, so clients retry too fast and make it worse. Emit the precise back-off.
8. Vocabulary that signals you've held the pager
- Idempotent — twice == once; the property that makes a write retry-safe.
- Confused deputy — a service that honors a token minted for someone else (skipped
aud). - Fail closed — the default decision is deny; you only allow what you can prove.
- Token bucket — burst capacity + sustained refill; the canonical rate limiter.
- Noisy neighbor — one tenant's flood starving others; rate-limit-by-key is the bulkhead.
- Policy pipeline — APIM's
inbound → backend → outbound → on-error; order is the policy. scpvsroles— delegated (a user is present) vs app-only (a daemon).- Cursor pagination — opaque "last key I saw"; stable under writes, unlike offset.
- Content negotiation —
Accept/Content-Typedance; mismatch is406/415.
9. Beginner mistakes that mark you in interviews
- Saying "REST" but designing RPC (
POST /api/doStuff) — no resources, no uniform methods. - Confusing
401and403(the single most common edge bug, and it causes real incidents). - Returning
200with an error in the body — breaks every layer that reads the status. - Retrying a
POSTon a timeout without an idempotency key — duplicate writes. 429withoutRetry-After— clients retry too fast and amplify the overload.- Checking the JWT signature but not
aud— the confused-deputy breach. - Rate-limiting before authenticating — letting unauth probes drain rate budget.
- Route matching that lets
/users/{id}shadow the literal/users/me. - Offset pagination on a write-heavy collection — skipped/duplicated rows at the seams.
10. How this phase pays off later
- The gateway pipeline is where reliability (P14:
429/Retry-After, circuit breaking), identity (P03: the JWT this phase validates), and observability (P13: every4xx/5xxis a KQL signal) all meet — it's the edge of the whole platform. - Idempotency recurs everywhere: ARM
PUT(P01), Service Bus dedup (P10), Durable replay (P11). The "twice == once" instinct you build here is a track-wide muscle. scp/rolesauthorization is Phase 03's token, consumed. This is where minting and validating a token turns into gating real operations with it.- The token-bucket math is the same
429/Retry-Afterarithmetic you'll compose into a retry budget and a circuit breaker in P14.
Now read the WARMUP slowly, then build the gateway. You'll reach for its pipeline order and
its 401/403 rule in every API design review for the rest of your career.