← Systems Analysis: APIs, Integrations and Data
Lesson
Status codes: choose a code the client will understand
Choose a status code for each outcome of an endpoint — success, request error, no permission, state conflict, overload, failure — and describe the error body so that the client can react to it.
The status code is part of the contract
The status code is part of the contract
Code classes:
• 2xx — success;
• 3xx — redirection;
• 4xx — client error: retrying unchanged won't help, except 429 after the Retry-After pause;
• 5xx — server error: a later retry may help.
A refresher from the testing course; the analyst picks the contract's code for each outcome.
401 or 403? Ask: would logging in again help? If yes, 401: the client is unidentified (no token, or it expired). If no, 403: the client is known but lacks permissions. If even the resource's existence is secret, answer 404 instead of 403, as the order service does for someone else's order.
400, 409 and 422:
• 400 — the request can't be parsed: broken JSON, “two” instead of a number;
• 422 — parsed, but the data breaks a rule: an empty cart, an address outside the zone;
• 409 — the resource's current state blocks it: cancelling a delivered order.
APIs draw the 400/422 line differently, so the contract records it.
Anti-pattern: “200 with an error inside”: code 200, body {"success": false}. Retries, monitoring and caches look at the code and see success.
Error bodies use one format, e.g. Problem Details (RFC 9457, application/problem+json): type, title, status, detail plus your own fields. A 409 for cancelling an order in status in_delivery:
{"type": "https://api.romashka.example/problems/order-not-cancellable", "title": "Order cannot be cancelled", "status": 409, "currentStatus": "in_delivery"}
Status codes most often written into a contract
| Code | When | Romashka example |
|---|---|---|
| 200 OK | success with a body | GET an order |
| 201 Created | resource created, its address is in Location | POST /orders |
| 202 Accepted | accepted, will be done later | starting a refund |
| 204 No Content | success without a body | saved address deleted |
| 400 Bad Request | request cannot be parsed | "quantity": "two" |
| 401 Unauthorized | token missing or invalid | token expired |
| 403 Forbidden | identity known, no permission | refund without the “finance” role |
| 404 Not Found | no such resource (or it is hidden) | a nonexistent order or someone else's |
| 409 Conflict | the current state gets in the way | cancelling a delivered order |
| 422 Unprocessable Content | the data breaks a rule | empty cart |
| 429 Too Many Requests | limit exceeded, pause given in Retry-After | 300 requests per second with a limit of 100 |
| 500 Internal Server Error | failure on our side | a bug in the order service code |
| 502 Bad Gateway | the called system returned an invalid response | the provider sent a broken response |
| 503 Service Unavailable | overload or maintenance | order service under maintenance |
| 504 Gateway Timeout | the called system did not respond in time | the provider is silent |
Lesson notes
The status code is part of the contract
Code classes:
• 2xx — success;
• 3xx — redirection;
• 4xx — client error: retrying unchanged won't help, except 429 after the Retry-After pause;
• 5xx — server error: a later retry may help.
A refresher from the testing course; the analyst picks the contract's code for each outcome.
401 or 403? Ask: would logging in again help? If yes, 401: the client is unidentified (no token, or it expired). If no, 403: the client is known but lacks permissions. If even the resource's existence is secret, answer 404 instead of 403, as the order service does for someone else's order.
400, 409 and 422:
• 400 — the request can't be parsed: broken JSON, “two” instead of a number;
• 422 — parsed, but the data breaks a rule: an empty cart, an address outside the zone;
• 409 — the resource's current state blocks it: cancelling a delivered order.
APIs draw the 400/422 line differently, so the contract records it.
Anti-pattern: “200 with an error inside”: code 200, body {"success": false}. Retries, monitoring and caches look at the code and see success.
Error bodies use one format, e.g. Problem Details (RFC 9457, application/problem+json): type, title, status, detail plus your own fields. A 409 for cancelling an order in status in_delivery:
{"type": "https://api.romashka.example/problems/order-not-cancellable", "title": "Order cannot be cancelled", "status": 409, "currentStatus": "in_delivery"}