Status Code and Header Strategy
Strategi status code dan header untuk API production: semantic correctness, operational clarity, retry signal, correlation, deprecation, compatibility, dan PR review
Part 021 — Status Code and Header Strategy
Fokus part ini: membangun strategi status code dan HTTP header yang benar secara semantik, stabil untuk consumer, mudah diobservasi, dan aman untuk sistem enterprise production.
Di part sebelumnya, kita membahas resource modeling.
Sekarang kita masuk ke kontrak response.
Endpoint yang baik tidak hanya mengembalikan JSON yang benar. Endpoint yang baik juga memberi sinyal yang benar melalui:
- HTTP status code
- response headers
- error code
- retryability signal
- cache signal
- correlation signal
- deprecation signal
- security signal
- compatibility signal
Dalam sistem enterprise seperti CPQ/quote/order management, salah memilih status code atau header bukan sekadar masalah estetika API. Ia bisa memicu:
- client retry yang salah
- workflow salah cabang
- duplicate order
- false alert
- bad customer-facing error
- lost observability
- broken generated client
- backward compatibility break
- API gateway policy mismatch
Kita tetap hati-hati terhadap konteks CSG: materi ini menjelaskan prinsip umum production API. Detail aktual internal harus diverifikasi di codebase, API guideline, gateway policy, dan review process.
1. Status Code Is Part of the Contract
Bad mental model:
Status code is just a wrapper around JSON response.
Better mental model:
Status code is the first machine-readable classification of the result.
Sebelum client membaca body, ia biasanya sudah mengambil keputusan dari status code:
2xx -> request succeeded at HTTP/API boundary
3xx -> caller may need redirection/cache-aware handling
4xx -> caller/client/request problem
5xx -> server/dependency/system problem
Status code mempengaruhi:
- retry decision
- SDK/generated client behavior
- API gateway behavior
- circuit breaker metrics
- alerting
- synthetic monitoring
- client error handling
- UI message mapping
- workflow continuation
Karena itu, status code harus stabil dan predictable.
2. Status Code Strategy vs Per-Endpoint Guessing
Tanpa strategi, tiap engineer akan memilih status code berdasarkan feeling.
Contoh inconsistency:
Case A: validation error -> 400
Case B: validation error -> 422
Case C: validation error -> 200 with { success: false }
Case D: validation error -> 500 because exception leaked
Problem:
- client harus menangani banyak bentuk error
- generated client tidak bisa dipercaya
- monitoring tidak akurat
- alert 5xx bisa noisy
- business error bercampur dengan technical error
API production perlu status code taxonomy.
Minimal taxonomy:
Success
Client/request problem
Authentication/authorization problem
Conflict/state problem
Not found/visibility problem
Rate limit/quota problem
Dependency/system problem
Temporary overload
3. Practical 2xx Strategy
200 OK
Gunakan saat request berhasil dan response body berisi representation.
Contoh:
GET /quotes/Q-1001 HTTP/1.1
Response:
HTTP/1.1 200 OK
Content-Type: application/json
Cocok untuk:
- GET resource detail
- search/list result
- command endpoint yang sengaja mengembalikan resource/result detail
Hindari memakai 200 OK untuk menyembunyikan error bisnis.
Bad:
{
"success": false,
"error": "QUOTE_EXPIRED"
}
dengan:
HTTP/1.1 200 OK
Ini membuat client, metrics, gateway, dan monitoring salah memahami hasil request.
201 Created
Gunakan saat request membuat resource baru yang dapat direferensikan.
Contoh:
POST /quotes HTTP/1.1
Response:
HTTP/1.1 201 Created
Location: /quotes/Q-1001
Content-Type: application/json
Gunakan Location bila resource baru punya URI stabil.
JAX-RS style:
return Response
.created(uriInfo.getAbsolutePathBuilder().path(quoteId).build())
.entity(response)
.build();
Review question:
Apakah operasi benar-benar membuat resource baru?
Apakah resource punya identifier stabil?
Apakah Location header menunjuk URI canonical?
202 Accepted
Gunakan saat request diterima, tetapi processing belum selesai.
Cocok untuk:
- async quote validation
- async pricing calculation
- order submission yang diproses workflow
- downstream orchestration
- long-running job
Response ideal:
HTTP/1.1 202 Accepted
Location: /operations/op-123
Retry-After: 5
Content-Type: application/json
Body:
{
"operationId": "op-123",
"status": "ACCEPTED",
"statusUrl": "/operations/op-123"
}
Important invariant:
202 means accepted for processing, not completed.
Failure mode:
Client treats 202 as business success.
UI shows order complete while backend has not completed downstream orchestration.
PR review question:
Is there a status resource or callback/event contract for completion?
204 No Content
Gunakan saat request berhasil dan tidak ada response body.
Cocok untuk:
- DELETE success
- update command with no response body
- idempotent state transition with no representation returned
Do not send body with 204.
Bad:
HTTP/1.1 204 No Content
Content-Type: application/json
{"status":"deleted"}
Jika ingin mengembalikan state/result, gunakan 200 OK.
4. Practical 3xx Strategy
Dalam API service-to-service enterprise, 3xx biasanya jarang dipakai langsung oleh application service.
Sering kali redirection dikelola oleh:
- API gateway
- ingress
- load balancer
- CDN
- auth gateway
Namun engineer tetap perlu memahami dampaknya.
Failure mode:
HTTP client tidak follow redirect.
Authorization header hilang saat redirect.
POST berubah menjadi GET karena redirect handling buruk.
Signed request invalid karena host/path berubah.
Internal verification checklist:
- Apakah API service pernah mengembalikan 3xx sendiri?
- Apakah redirect dilakukan di gateway?
- Apakah internal HTTP client follow redirect?
- Apakah Authorization header dipertahankan saat redirect?
5. Practical 4xx Strategy
4xx berarti request tidak dapat diproses karena masalah di sisi caller, request, identity, permission, state, atau rate limit.
4xx bukan berarti sistem sehat sepenuhnya. 4xx tetap bisa menjadi indikator bug client, bug UI, integration mismatch, atau abuse.
400 Bad Request
Gunakan untuk malformed request atau request yang tidak bisa dipahami di API boundary.
Contoh:
- invalid JSON
- invalid query parameter type
- missing required parameter
- invalid enum value at API boundary
- malformed date
JAX-RS/Jersey failure examples:
- MessageBodyReader gagal deserialize JSON
- ParamConverter gagal convert query/path parameter
- Bean Validation gagal pada request DTO, jika strategy internal memakai 400
PR review question:
Apakah error ini request-shape error, bukan domain-state error?
401 Unauthorized
Nama status ini historis membingungkan.
Praktisnya:
401 = caller belum terautentikasi atau token tidak valid.
Gunakan untuk:
- missing token
- invalid token
- expired token
- invalid signature
- invalid issuer/audience when treated as authentication failure
Biasanya perlu header:
WWW-Authenticate: Bearer
Internal verification:
Cek apakah auth gateway atau service sendiri yang menghasilkan 401.
403 Forbidden
Gunakan saat identity valid, tetapi tidak punya permission.
Contoh:
- user authenticated but lacks quote:approve
- service identity valid but not allowed to submit order
- tenant valid but resource not accessible under policy
Important distinction:
401 = who are you?
403 = you are known, but not allowed.
Security nuance:
Kadang sistem sengaja mengembalikan 404 bukan 403 untuk menyembunyikan existence resource. Itu harus menjadi policy eksplisit, bukan keputusan random endpoint.
404 Not Found
Gunakan saat resource tidak ditemukan atau sengaja tidak terlihat bagi caller.
Cocok untuk:
GET /quotes/Q-unknown
Tetapi hati-hati:
Not found because ID does not exist?
Not found because wrong tenant?
Not found because user lacks permission?
Not found because route mismatch?
Untuk debugging production, bedakan di log internal tanpa membocorkan detail ke client.
client response: 404 NOT_FOUND
internal log reason: RESOURCE_NOT_FOUND | TENANT_MISMATCH | POLICY_HIDDEN
405 Method Not Allowed
Biasanya dihasilkan oleh framework/container saat path cocok tetapi method tidak.
Contoh:
POST /quotes/Q-1001
padahal hanya ada:
@GET
@Path("/quotes/{id}")
Response seharusnya menyertakan Allow header.
Internal verification:
- Apakah JAX-RS runtime mengembalikan 405 otomatis?
- Apakah gateway mengubah 405 menjadi 404?
- Apakah OPTIONS/preflight diperlakukan benar?
406 Not Acceptable
Terjadi saat server tidak bisa menghasilkan representation yang diminta Accept header.
Example:
Accept: application/xml
sementara endpoint hanya menghasilkan JSON.
JAX-RS related:
@Produces(MediaType.APPLICATION_JSON)
Failure mode:
Client library default Accept header tidak sesuai.
Endpoint lupa @Produces.
Provider XML/JSON tidak terdaftar.
409 Conflict
Gunakan saat request valid secara format, tetapi konflik dengan state resource saat ini.
Cocok untuk:
- quote already approved cannot be edited
- order already submitted cannot be cancelled through this endpoint
- optimistic locking version mismatch
- duplicate idempotency conflict
- state transition conflict
Important distinction:
400 = request malformed/invalid at boundary
409 = request understandable but conflicts with current state
Dalam quote/order system, 409 Conflict sangat penting untuk state-machine correctness.
412 Precondition Failed
Gunakan saat conditional request gagal.
Contoh:
PUT /quotes/Q-1001
If-Match: "v7"
Tetapi current ETag adalah "v8".
Response:
HTTP/1.1 412 Precondition Failed
Cocok untuk optimistic concurrency via HTTP.
Internal verification:
Apakah API memakai ETag/If-Match atau version field di body?
415 Unsupported Media Type
Terjadi saat request body media type tidak didukung.
Example:
Content-Type: text/plain
ke endpoint:
@Consumes(MediaType.APPLICATION_JSON)
Failure mode:
Client lupa Content-Type.
Multipart endpoint tidak register provider.
Gateway mengubah Content-Type.
422 Unprocessable Content
Beberapa API memakai 422 untuk semantic validation error.
Contoh:
- JSON valid
- DTO valid secara shape
- tetapi business validation gagal
Example:
Quote line has incompatible product combination.
Pricing effective date is outside valid catalog window.
Namun tidak semua organisasi memakai 422. Ada yang tetap memakai 400 untuk semua validation error.
Rule penting:
Pilih satu policy internal dan konsisten.
Internal verification:
- Apakah internal API guideline membedakan 400 vs 422?
- Apakah generated client dan error mapper mendukung 422?
429 Too Many Requests
Gunakan untuk rate limit/quota/throttle.
Biasanya disertai:
Retry-After: 30
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1720000000
Header rate limit mungkin distandardisasi internal atau dikelola gateway.
Failure mode:
Client retry immediately -> thundering herd.
Server returns 500 instead of 429 -> false incident classification.
6. Practical 5xx Strategy
5xx berarti server gagal memenuhi request yang secara umum bukan kesalahan caller.
5xx harus meaningful bagi:
- alerting
- SLO/error budget
- retry policy
- circuit breaker
- incident triage
500 Internal Server Error
Gunakan untuk unexpected server error.
Tetapi jangan jadikan 500 sebagai tempat semua error.
Bad taxonomy:
Any exception -> 500
Better taxonomy:
ValidationException -> 400/422
AuthException -> 401/403
NotFoundException -> 404
StateConflictException -> 409
OptimisticLockException -> 409/412
DownstreamTimeoutException -> 504/503 depending boundary
UnexpectedNullPointerException -> 500
500 yang terlalu banyak biasanya tanda error mapper kurang matang.
502 Bad Gateway
Biasanya dihasilkan gateway/proxy ketika upstream invalid.
Application service jarang mengembalikan 502 sendiri, kecuali ia bertindak sebagai gateway/proxy.
Internal verification:
Apakah 502 berasal dari service, ingress, load balancer, API gateway, atau service mesh?
503 Service Unavailable
Gunakan saat service sementara tidak tersedia.
Cocok untuk:
- overload/load shedding
- dependency critical unavailable
- maintenance
- circuit open and no fallback
Biasanya disertai:
Retry-After: 10
Failure mode:
503 tanpa Retry-After membuat client retry aggressive.
504 Gateway Timeout
Biasanya dihasilkan gateway saat upstream timeout.
Namun application service bisa memetakan downstream timeout menjadi 504 jika ia bertindak sebagai boundary proxy terhadap downstream.
Important question:
Timeout terjadi di mana?
- client -> gateway
- gateway -> service
- service -> DB
- service -> downstream API
- service -> Kafka/queue
Tanpa boundary clarity, status code bisa menyesatkan.
7. Domain Error vs HTTP Status
Dalam enterprise system, satu HTTP status bisa berisi banyak domain error code.
Example:
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"errorCode": "QUOTE_ALREADY_APPROVED",
"message": "Quote cannot be modified after approval.",
"correlationId": "01J...",
"details": {
"quoteId": "Q-1001",
"currentState": "APPROVED"
}
}
Pattern:
HTTP status = coarse protocol-level classification
errorCode = stable business/technical classification
message = human-readable explanation
correlation = operational traceability
Do not encode all semantics only in message text.
Bad:
{
"message": "Oops quote already approved"
}
Better:
{
"errorCode": "QUOTE_ALREADY_APPROVED",
"message": "Quote cannot be modified after approval."
}
8. Header Strategy: Why Headers Matter
Headers are metadata for machines.
Common header categories:
- content negotiation
- cache behavior
- correlation/tracing
- authentication/authorization
- idempotency
- concurrency control
- pagination/linking
- rate limiting
- deprecation/versioning
- security hardening
- gateway/proxy behavior
Header strategy prevents accidental inconsistency.
9. Content Headers
Core content headers:
Content-Type: application/json
Accept: application/json
Content-Length: 1234
Content-Encoding: gzip
Rules:
- Request with body should send Content-Type.
- Response with body should send Content-Type.
- Accept should influence representation selection.
- Compression may be handled by service, gateway, or proxy.
JAX-RS link:
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
But remember:
@Produces does not guarantee gateway will preserve headers unchanged.
Internal verification:
- Are content headers set by JAX-RS, servlet container, gateway, or platform?
- Are default media types defined internally?
10. Correlation and Trace Headers
Common patterns:
X-Correlation-ID: 01JABC...
X-Request-ID: req-123
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: vendor=value
Modern tracing should prefer W3C trace context (traceparent) when OpenTelemetry is used.
Correlation ID remains useful as business/operational identifier.
Distinction:
Trace ID -> distributed tracing graph
Span ID -> one operation within trace
Correlation ID -> application/log search identifier
Causation ID -> why this operation happened, often command/event lineage
Failure mode:
A request crosses HTTP -> Kafka -> worker, but correlation headers are not propagated.
Logs cannot connect API call to event consumer.
PR review:
Does this endpoint read, create, validate, and propagate correlation context?
11. Idempotency Headers
For unsafe operations that may be retried, idempotency is often explicit.
Example:
POST /orders HTTP/1.1
Idempotency-Key: 01JABC...
Server behavior:
- first request stores key + result
- duplicate request with same key returns same result or conflict
- same key with different payload should be rejected
Possible responses:
201 Created -> first successful creation
200 OK -> duplicate request returns previous result
409 Conflict -> key reused for different semantic request
425/429/503 -> depending platform and policy
Internal verification:
- Is Idempotency-Key supported?
- Is it required for POST order submit?
- Where is key stored: DB, Redis, gateway?
- What is key TTL?
12. Concurrency Headers
HTTP provides conditional request headers:
ETag: "v7"
If-Match: "v7"
If-None-Match: "v7"
Last-Modified: Wed, 10 Jul 2026 10:00:00 GMT
If-Modified-Since: Wed, 10 Jul 2026 10:00:00 GMT
Use cases:
- optimistic concurrency control
- cache validation
- lost update prevention
Example update:
PUT /quotes/Q-1001
If-Match: "v7"
If quote changed to v8:
HTTP/1.1 412 Precondition Failed
Alternative common enterprise pattern:
{
"quoteId": "Q-1001",
"version": 7,
"changes": {}
}
Both can work, but they must be consistent.
Internal verification:
- Does internal API use ETag or body version field?
- Is optimistic locking exposed to clients?
- Are stale updates rejected deterministically?
13. Pagination and Link Headers
Pagination metadata can be in body, headers, or both.
Header options:
Link: </quotes?cursor=abc>; rel="next"
X-Total-Count: 1402
Body option:
{
"items": [],
"pageInfo": {
"nextCursor": "abc",
"hasMore": true
}
}
Senior review point:
Body metadata is often easier for generated clients.
Link headers align with web standards.
Choose and standardize.
Internal verification:
- Where does pagination metadata live?
- Are total counts allowed or too expensive?
- Is cursor opaque?
14. Rate Limit Headers
Potential headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1720000000
Retry-After: 30
Governance questions:
- Are rate limits enforced by gateway, service, Redis, or platform?
- Are headers standardized?
- Are tenant-specific limits possible?
- Are internal service-to-service calls rate limited?
Failure mode:
Service returns 429 but does not tell client when to retry.
Client retries immediately.
Rate limiter becomes part of outage amplification.
15. Deprecation and Versioning Headers
Useful headers:
Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
Link: </docs/api-migration>; rel="deprecation"
Use for:
- endpoint deprecation
- field deprecation
- old API version retirement
- migration communication
But headers alone are not enough.
Need:
- docs
- consumer inventory
- monitoring of usage
- migration window
- support process
Internal verification:
- Does the organization use Deprecation/Sunset headers?
- Is there an API deprecation policy?
- Are deprecated endpoints monitored by consumer?
16. Security Headers
For browser-facing APIs, security headers may matter more.
Examples:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'none'
Referrer-Policy: no-referrer
For service-to-service APIs, some may be handled by gateway.
Internal verification:
- Are security headers required at service layer or gateway layer?
- Are APIs browser-accessible?
- Does CORS policy exist?
- Are headers overwritten by ingress/API gateway?
17. JAX-RS Response Construction Patterns
Simple entity:
@GET
@Path("/{quoteId}")
@Produces(MediaType.APPLICATION_JSON)
public Response getQuote(@PathParam("quoteId") String quoteId) {
QuoteResponse response = quoteService.getQuote(quoteId);
return Response.ok(response).build();
}
Created resource:
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createQuote(CreateQuoteRequest request, @Context UriInfo uriInfo) {
QuoteResponse created = quoteService.createQuote(request);
URI location = uriInfo
.getAbsolutePathBuilder()
.path(created.quoteId())
.build();
return Response
.created(location)
.entity(created)
.header("X-Correlation-ID", Correlation.currentId())
.build();
}
Accepted async operation:
@POST
@Path("/{quoteId}/pricing-runs")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response startPricing(
@PathParam("quoteId") String quoteId,
PricingRunRequest request,
@Context UriInfo uriInfo
) {
OperationResponse operation = pricingService.startPricing(quoteId, request);
URI location = uriInfo
.getBaseUriBuilder()
.path("operations")
.path(operation.operationId())
.build();
return Response
.accepted(operation)
.location(location)
.header("Retry-After", "5")
.build();
}
No content:
@DELETE
@Path("/{quoteId}")
public Response deleteQuote(@PathParam("quoteId") String quoteId) {
quoteService.deleteDraftQuote(quoteId);
return Response.noContent().build();
}
18. ExceptionMapper and Status Strategy
Status strategy should be centralized.
Bad pattern:
throw new RuntimeException("Quote expired");
and every endpoint manually catches it.
Better pattern:
public final class QuoteExpiredException extends DomainException {
public QuoteExpiredException(String quoteId) {
super("QUOTE_EXPIRED", "Quote has expired", quoteId);
}
}
Mapper:
@Provider
public class DomainExceptionMapper implements ExceptionMapper<DomainException> {
@Override
public Response toResponse(DomainException exception) {
ErrorResponse body = ErrorResponse.from(exception, Correlation.currentId());
return Response
.status(mapStatus(exception))
.type(MediaType.APPLICATION_JSON)
.entity(body)
.build();
}
}
Mapping example:
QUOTE_EXPIRED -> 409 Conflict or 422 depending policy
QUOTE_NOT_FOUND -> 404 Not Found
QUOTE_ALREADY_APPROVED -> 409 Conflict
VALIDATION_FAILED -> 400 or 422 depending policy
AUTHENTICATION_FAILED -> 401 Unauthorized
PERMISSION_DENIED -> 403 Forbidden
DOWNSTREAM_TIMEOUT -> 504 Gateway Timeout or 503 depending boundary
UNEXPECTED_ERROR -> 500 Internal Server Error
The exact mapping must be governed internally.
19. Common Anti-Patterns
Anti-pattern 1: Always return 200
HTTP/1.1 200 OK
{
"success": false,
"error": "ORDER_FAILED"
}
Why bad:
- monitoring sees success
- client retry logic breaks
- generated clients cannot classify error
- API semantics are lost
Anti-pattern 2: Throw everything as 500
Why bad:
- client errors page operators
- SLO becomes noisy
- domain conflicts look like outage
- real 500 becomes harder to detect
Anti-pattern 3: Header names invented per endpoint
Bad:
X-Trace
X-TraceId
X-Request-Trace
X-Correlation
CorrelationId
Better:
Use platform standard consistently.
Prefer W3C trace context where tracing is involved.
Anti-pattern 4: Status code differs between success and duplicate retry
Example:
First POST /orders -> 201 Created
Duplicate retry -> 409 Conflict
This may be wrong if duplicate is same semantic request with same idempotency key.
Better:
Same idempotency key + same payload -> return previous successful result.
Same idempotency key + different payload -> 409 Conflict.
Anti-pattern 5: Leaking internal dependency error
Bad:
{
"message": "org.postgresql.util.PSQLException: deadlock detected"
}
Better:
{
"errorCode": "TEMPORARY_DATABASE_CONFLICT",
"message": "The request could not be completed due to a temporary conflict.",
"correlationId": "01J..."
}
Internal logs can contain technical detail with proper redaction.
20. Failure Modes
| Failure mode | Symptom | Likely cause | Detection |
|---|---|---|---|
| Validation error returns 500 | noisy alert | mapper missing | 5xx dashboard, logs |
| Conflict returns 400 | client cannot distinguish state issue | weak taxonomy | contract tests |
| Async operation returns 200 | client assumes completion | wrong status semantics | workflow bugs |
| Missing Location on 201 | client cannot locate resource | response construction gap | API tests |
| Missing Retry-After on 429/503 | retry storm | poor header strategy | traffic spike |
| Missing correlation header | hard debugging | context not propagated | log/tracing gap |
| Gateway overwrites status | service/gateway mismatch | platform policy | ingress/gateway logs |
| Wrong Content-Type | client decode failure | provider/header bug | integration tests |
21. Debugging Workflow
When an API response looks wrong, debug in layers.
1. What did the resource method return?
2. Did ExceptionMapper transform the response?
3. Did filter/interceptor add/remove headers?
4. Did Servlet container modify response?
5. Did gateway/ingress/API management modify status or headers?
6. Did client library reinterpret response?
7. Did generated client map status incorrectly?
Useful evidence:
- access logs
- application logs with correlation ID
- JAX-RS filter logs
- gateway logs
- distributed trace
- raw curl output
- OpenAPI contract
- integration test
Use raw HTTP inspection:
curl -i \
-H 'Accept: application/json' \
-H 'X-Correlation-ID: debug-123' \
https://example.internal/quotes/Q-1001
Do not debug only from browser/client SDK abstraction.
22. PR Review Checklist
For every endpoint PR, review:
Status code
- Is success status semantically correct?
- Are create/update/delete/async cases distinguished?
- Are validation/domain/technical errors mapped correctly?
- Are retryable vs non-retryable errors clear?
Headers
- Is Content-Type correct?
- Is Location present for 201 when applicable?
- Is Retry-After present for 429/503/202 polling when applicable?
- Are correlation/trace headers propagated?
- Are ETag/If-Match headers used or intentionally not used?
- Are deprecation/version headers needed?
Compatibility
- Does OpenAPI reflect status codes and headers?
- Do generated clients handle these statuses?
- Are new errors additive and documented?
- Is behavior consistent with existing endpoints?
Observability
- Are status/error metrics meaningful?
- Is error code logged?
- Is correlation ID present in response/log/trace?
Security
- Are 401/403/404 choices deliberate?
- Are sensitive details hidden from response?
- Are security headers handled by service or gateway?
23. Internal Verification Checklist
Verify in CSG/internal context:
API governance
- Is there an internal HTTP/API style guide?
- Is there an official status code matrix?
- Is 400 vs 422 standardized?
- Is 409 vs 412 standardized?
- Is async command expected to return 202?
Headers
- Which correlation/request/trace headers are standard?
- Is W3C trace context used?
- Are custom X-* headers allowed?
- Are Location, Retry-After, ETag, Deprecation, Sunset used?
Gateway/platform
- Which headers are added/removed by gateway?
- Does gateway normalize errors?
- Does ingress/API gateway rewrite status codes?
- Are CORS/security headers service-owned or gateway-owned?
JAX-RS implementation
- Which ExceptionMappers exist?
- Which filters add headers?
- Are status codes manually built or centralized?
- Are response envelopes standardized?
Testing/governance
- Are status codes represented in OpenAPI?
- Is API linting enforced in CI?
- Are generated clients used?
- Are contract tests checking headers/status?
Operations
- Which status codes count against SLO?
- Are 4xx/5xx dashboards separated?
- Are 409/422 monitored for product/business issues?
24. Senior Engineer Heuristics
Use these heuristics:
If the caller can fix the request -> probably 4xx.
If the system/dependency failed -> probably 5xx.
If the operation was accepted but not completed -> 202.
If a new resource was created -> 201 + Location.
If current resource state prevents operation -> 409.
If conditional header/version failed -> 412.
If request body shape is invalid -> 400.
If semantic validation failed -> 400 or 422 depending policy.
If authenticated but not permitted -> 403.
If not authenticated -> 401.
If throttled -> 429 + Retry-After.
If temporarily unavailable -> 503 + Retry-After.
But never use heuristics as replacement for internal governance.
25. Key Takeaways
Status code and header strategy is production engineering.
A senior engineer should ensure:
- HTTP status is semantically correct
- headers carry useful machine-readable metadata
- errors are classified consistently
- retry behavior is safe
- correlation is preserved
- security information is not leaked
- OpenAPI reflects actual behavior
- gateway/service responsibility is explicit
The goal is not to memorize every status code.
The goal is to make API behavior predictable under success, failure, retry, concurrency, and evolution.
You just completed lesson 21 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.