REST API Complete Guide | HTTP Semantics, Richardson Maturity, HATEOAS, Versioning & Production

REST API Complete Guide | HTTP Semantics, Richardson Maturity, HATEOAS, Versioning & Production

이 글의 핵심

A protocol-level guide to REST APIs: Richardson maturity, HTTP method semantics and idempotency, HATEOAS, negotiation, versioning, and production-ready patterns.

Introduction

REST (Representational State Transfer) is a set of constraints for modeling resources, representations, and transitions on top of HTTP. Pretty URLs are not enough: you need method semantics, safety and idempotency, a view of maturity, hypermedia where it pays off, content negotiation, an evolvable contract (versioning), and production operations. This guide focuses on the protocol and architecture internals that teams hit in real systems.


Table of contents

  1. Richardson maturity model
  2. HTTP method semantics and idempotency
  3. HATEOAS principles and patterns
  4. Content negotiation
  5. API versioning strategies
  6. Production REST patterns
  7. Summary

Richardson maturity model

The Richardson Maturity Model describes how fully a service uses HTTP’s intended features. Use it to locate your API and plan the next increment—not as a purity contest.

Level 0: HTTP as a tunnel

A single endpoint (often one POST) carries every operation—RPC-style over HTTP. The protocol is transport-only; methods, status codes, and caching semantics are largely unused. You may see this in legacy bridges; caches, proxies, and standard clients get little help.

Level 1: Resources

You introduce multiple URIs that identify meaningful resources, e.g. /orders, /orders/{id}, /customers/{id}. The model is resource-centric, but verbs and status codes may still be inconsistent (e.g. everything via POST).

Level 2: HTTP verbs and status codes

You map GET / POST / PUT / PATCH / DELETE to intended meanings and return meaningful status codes (200, 201, 204, 400, 404, 409, 429, …). This is where shared caches, conditional requests (ETag), retries, and idempotency start to work as HTTP expects. Many public APIs aim here for a practical “RESTful” baseline.

Level 3: Hypermedia controls (HATEOAS)

Responses include next steps—links, forms, or relation names—so clients follow transitions instead of hard-coding URLs. Servers can evolve workflows behind rel identifiers. The trade-off is media types, relation vocabularies, and client complexity; mobile apps with fast release cycles often pair OpenAPI + versioning instead of full hypermedia.

How to apply the model

  • Internal microservices: Level 2 plus a crisp error model and idempotent design is often the sweet spot.
  • Public or partner APIs: Deprecation headers, Problem Details, and a version policy usually rank above “level 3” purity; add HAL/JSON:API-style links where they earn their keep.
  • Prefer a contract consumers and operators understand over winning semantic debates.

HTTP method semantics and idempotency

HTTP methods are not arbitrary verbs; RFC 9110 (and related specs) define semantics that caches, proxies, and clients rely on. Misuse can cause cache poisoning, duplicate side effects, or unrecoverable state.

Safe, idempotent, cacheable

  • Safe: The request is not expected to change resource state. Typical examples: GET, HEAD, OPTIONS (when used as specified).
  • Idempotent: Repeating the same successful request leaves resource state as after the first success. Critical for retries and network redelivery.
  • Cacheable: Whether a response can be cached depends on the method and response metadata (Cache-Control, etc.). GET is often cacheable, but personal data requires private or no-store.

A practical summary:

MethodSafeIdempotentNotes
GETYesYesReads. Avoid secrets in query strings (logs, Referer).
HEADYesYesMetadata only; no body.
OPTIONSYes*YesCORS preflight, capabilities. Scope what you expose.
PUTNoYesReplace semantics; create-if-missing may be allowed by contract.
DELETENoYesDefine behavior for repeat deletes (404 vs 204) consistently.
POSTNoNoCreates, actions, pipelines—use idempotency keys when needed.
PATCHNoUsually noPartial update; idempotency depends on representation and server.

* OPTIONS is usually side-effect free; logging/metering may still apply as policy.

PUT vs PATCH

  • PUT: Natural model is full replacement of the resource representation. Whether omitted fields mean null, defaults, or validation errors must be documented.
  • PATCH: Partial updates. application/merge-patch+json vs application/json-patch+json vs domain-specific JSON behave differently; Merge Patch’s null-as-delete semantics are a common footgun for public APIs.

Idempotency keys for POST

For payments, orders, or tickets—exactly-once user intent—use a client-generated idempotency key (e.g. Idempotency-Key) and a server-side store of request fingerprints with TTL. Same key + same body → replay the first successful response; conflicting body → 409 Conflict.

POST /v1/payments HTTP/1.1
Host: api.example.com
Content-Type: application/json
Idempotency-Key: 7b291f6c-2c4a-4f1e-9d0a-3e8c5f2a1b00

{"amount":{"value":"1000","currency":"KRW"},"orderId":"ord_42"}

Why not put writes on GET

GET may be cached, prefetched, or crawled. A GET /cancelOrder?id=... style endpoint can fire unintentionally. Model mutations with POST / PUT / PATCH / DELETE, and apply auth, CSRF (for browsers), and audit explicitly.

CONNECT, TRACE, and the rest

CONNECT is mainly for proxy tunnels; TRACE is diagnostic and often disabled for security. Typical resource-oriented REST design centers on GET/HEAD/OPTIONS and state-changing methods.


HATEOAS principles and patterns

Hypermedia as the Engine of Application State means transitions live in representations—clients discover what they can do next from links and relation names, not from a static URL catalog.

Resource state vs application state

  • Resource state: persisted domain data (balances, order status).
  • Application state: where the client is in a workflow. HATEOAS tries to encode that navigational state in hypermedia rather than out-of-band assumptions.

Trade-offs

  • Pros: Servers control URI changes, visibility of actions (permissions, lifecycle), and workflow evolution behind stable rel names.
  • Costs: Media types, a relation vocabulary, and client logic to interpret links—often heavier than OpenAPI + versioning for short-cycle apps.

HAL (application/hal+json)

Use _links for self, next, and custom relations.

{
  "id": "ord_42",
  "status": "PENDING_PAYMENT",
  "_links": {
    "self": { "href": "/orders/ord_42" },
    "pay": { "href": "/orders/ord_42/payments" },
    "cancel": { "href": "/orders/ord_42/cancellation" }
  }
}

Clients bind to rel names, not raw paths—more resilient to URL changes.

JSON:API, Siren, Collection+JSON

  • JSON:API: links, relationships, included—heavy but consistent.
  • Siren: embeds actions (method + fields)—good for form-like hypermedia.
  • Collection+JSON: collection-centric; use when it fits your domain.

Pragmatic hypermedia

If full standards are too heavy:

  1. Ship a map of relation name → URL (e.g. team-standard _actions).
  2. Use RFC 7807 Problem Details with typed instance / type URIs.
  3. Document link fields and Link headers in OpenAPI.

Content negotiation

Negotiation chooses a representation for the same resource. Key headers: Accept, Accept-Language, Accept-Encoding, Content-Type.

Proactive negotiation (request headers)

GET /reports/2026/q1 HTTP/1.1
Host: api.example.com
Accept: application/vnd.example.report+json; version=1, application/json;q=0.8
Accept-Language: ko-KR, en;q=0.7

The server picks a representation or returns 406 Not Acceptable under strict contracts; some APIs fall back to a default representation with a warning.

300 Multiple Choices or Link headers offer alternatives—coordinate with CDN and cache keys.

Content-Type and 415 Unsupported Media Type

For POST/PATCH, unsupported combinations deserve a clear 415.

Compression and conditional GET

Pair Accept-Encoding: gzip, br with ETag / Last-Modified and If-None-Match to drive 304 Not Modified and save bandwidth.


API versioning strategies

Versioning is more than a string: it encodes compatible vs breaking changes, release and sunset policy, and consumer communication.

URL path (/v1/...)

  • Pros: visibility; easy routing at gateways and in metrics.
  • Caveat: keep stable domain identifiers separate from marketing/version strings where possible.

Header / Accept-based

Example: Accept: application/vnd.example.v2+json.

  • Pros: stable URIs.
  • Caveats: Vary: Accept for caches; harder to debug without discipline.

Query parameter (?apiVersion=2)

Simple but easy to mix with other parameters in logs and proxies—often less favored at scale.

Compatibility rules (examples)

  • Non-breaking: optional fields, additive enum values (clients ignore unknowns), richer errors.
  • Breaking: removing fields, changing meaning, adding required fields → new version or new media type/profile.

Deprecation

Use Deprecation, Sunset, and sunset links (RFC 8594) to announce timelines and replacements. Drive removal from metrics on old versions.


Production REST patterns

Observability

Correlate logs, metrics, and traces with X-Request-Id or W3C traceparent. Structure fields like method, route_template, status, latency_ms, tenant.

Errors

RFC 7807 Problem Details (application/problem+json) with type, title, status, detail, instance enables machine-readable branching. Never leak raw stack traces.

AuthN, AuthZ, tenancy

Document OAuth 2.x flows, Bearer tokens, mTLS for partners, and JWKS rotation. Map RBAC/ABAC to resources in OpenAPI or a policy engine.

Rate limits and quotas

Return 429 Too Many Requests with Retry-After and RateLimit-* (de facto) headers so clients can back off.

Caching and privacy

Support conditional GET with ETag/Last-Modified for read-heavy APIs. Mark personal data Cache-Control: private or no-store.

Gateways, BFFs, deadlines

  • Gateway: authentication, rate limits, WAF, routing, canaries.
  • BFF: compose responses per client; separate internal domain APIs from public contracts.
  • Deadlines: per-upstream timeouts and retry budgets, plus end-to-end deadlines to limit failure propagation.

Idempotency stores and deduplication

Beyond POST idempotency keys, at-least-once consumers may need natural-key upserts, outbox patterns, or message deduplication keys to approach exactly-once effects.

Contract testing

Use consumer-driven contracts (e.g. Pact) and OpenAPI snapshot tests; replay staging traffic to catch regressions.

Pagination and partial failure

Prefer keyset (cursor) pagination over large offset scans at scale. For long jobs, 202 Accepted with a status URL models async work cleanly.

Security basics

TLS, input validation, mass-assignment controls, upload limits, SSRF defenses, and PII redaction in logs apply to every HTTP API style.


Summary

  • The Richardson model helps you talk about HTTP usage; most teams combine level-2 semantics with OpenAPI and versioning, adding hypermedia where it pays.
  • Align methods with safety and idempotency; mitigate POST non-idempotency with idempotency keys where business rules require it.
  • HATEOAS can be delivered via HAL, JSON:API, Siren, or lightweight relation maps + Problem Details + OpenAPI.
  • Negotiation spans Accept, Content-Type, language, and compression—design with Vary and conditional requests.
  • Versioning strategies differ; what matters is a shared definition of breaking change and a clear sunset path.
  • Production quality ties REST design to observability, error standards, rate limits, caching, gateways, deadlines, and contract tests.

Aim for a contract that clients, operators, and security reviewers share—that matters more than the label on the tin.