REST APIs in Depth · Capstone
Capstone: Design an Orders API
Design a production-grade REST API for e-commerce orders.
Scenario
An online store is replacing its legacy order endpoints. Web, mobile and partner (B2B) clients must create orders, list a customer's orders, cancel orders before shipping, and download invoices. Partners import orders in bulk. Clients retry aggressively on flaky mobile networks.
Requirements
- Create, read, list, cancel orders; download invoice PDFs
- Bulk import for partners (up to 10,000 orders per file)
- Safe retries for order creation
- Concurrent edits (support staff and customer) must not lose updates
- Consistent errors and pagination across endpoints
Constraints
- p95 < 300 ms for reads
- Partners rate-limited per API key
- Order IDs must not be enumerable
- Mobile clients on older app versions for 12+ months
Deliverables
- List the resources, URLs and methods for the Orders API.
- How do you make order creation safe to retry, and how do you prevent lost updates?
- Define the error format and three concrete error examples.
Hints
- Separate the interactive API from the bulk import path.
- Think about what mobile apps released a year ago will still send.
Draft each deliverable below before revealing the model answer and rubric.
Further reading: Google API Improvement Proposals
Deliverables
List the resources, URLs and methods for the Orders API.
Model answer
POST /v1/orders(Idempotency-Key required) → 201 + LocationGET /v1/orders/{order_id}→ 200 + ETag;?expand=customer,items.productGET /v1/orders?customer_id=&status=paid,shipped&created_after=&sort=-created_at&limit=&cursor=PATCH /v1/orders/{order_id}(If-Match required; shipping address/notes only)POST /v1/orders/{order_id}:cancel→ 200 or 409 if already shippedGET /v1/orders/{order_id}/invoice→application/pdf(supports Range), or 302 to a signed URLPOST /v1/order-imports(upload file or pre-signed upload) → 202 +/v1/operations/{id}- IDs: opaque prefixed ULIDs (
ord_01J…)
Rubric
Strong answers: noun-based resources with shallow nesting; explicit cancel action instead of GET; cursor pagination with allow-listed filters; async import job; invoice via signed URL. Common misses: no Idempotency-Key on create, offset pagination for a large table, sequential IDs, cancel modeled as DELETE (loses audit/refund semantics).
How do you make order creation safe to retry, and how do you prevent lost updates?
Model answer
- Require
Idempotency-Keyon POST; store (client, key) → request fingerprint + response; replay the stored 201 on retries, 409 while in progress, 422 if the same key is reused with a different body; expire keys after 24–72 h. - Pass the same key to the payment provider.
- Return
ETag(version) on GET; requireIf-Matchon PATCH; 412 on mismatch, 428 when missing.
Rubric
Look for: key scoping per client, fingerprinting, behavior for concurrent duplicates, expiry, downstream key propagation, ETag/If-Match with 412/428. Misses: relying on 'check if exists' races, last-write-wins updates.
Define the error format and three concrete error examples.
Model answer
application/problem+json (RFC 9457) with stable type URIs and request_id:
- 422 validation:
type=/problems/validation,errors[]withpointer,code,detail - 409 conflict:
type=/problems/order-not-cancellable,detail="Order ord_1 already shipped" - 429:
type=/problems/rate-limited, headersRetry-After,RateLimit/RateLimit-Policy
Rubric
Strong answers use one format everywhere, machine-readable codes, and correct status codes. Misses: 200-with-error bodies, leaking stack traces, clients forced to parse messages.
Open in the app to mark lessons complete, take notes and track your progress.