Capstone: Designing an API for a Real Scenario

API Types & Design
Chapter 9 ยท Capstone: Designing an API for a Real Scenario

๐ŸŽ“ Capstone: Designing an API for a Real Scenario

Every chapter so far has worked in isolated pieces. This capstone puts them all together โ€” one real scenario, walked through end to end, from picking a style with Chapter 8's decision framework all the way to a concrete endpoint and webhook design.

The Scenario

A task-management SaaS company wants to let outside developers build integrations against their platform โ€” a Slack bot that creates tasks from messages, a calendar sync tool, a reporting dashboard. Those integrators need to read and create tasks, and be notified the moment a task's status changes, without hammering the platform with constant polling.

Applying the Decision Framework

Question (from Chapter 8)Answer for This Scenario
Who's the client?A broad, external, uncoordinated audience of third-party developers โ†’ points toward REST
Request/response, or something else?Reading/creating tasks is request/response; "notify me when status changes" is not โ†’ that piece needs Webhooks
How complex are the client's data needs?Mostly uniform CRUD access to tasks, not wildly varying nested shapes per integrator โ†’ plain REST is enough, GraphQL's extra complexity isn't earned here
What guarantees are needed?No enterprise/transactional guarantees needed (ruling out SOAP); no internal extreme-performance requirement (ruling out gRPC) for this external-facing part

Conclusion: REST for the main API, Webhooks for change notifications โ€” exactly the kind of two-styles-together answer Chapter 8's worked example arrived at.

Designing the REST Endpoints

MethodPathPurpose
GET/v1/tasksList tasks (paginated, filterable)
GET/v1/tasks/{id}Retrieve one task
POST/v1/tasksCreate a new task
PATCH/v1/tasks/{id}Partially update a task (e.g. just its status)
DELETE/v1/tasks/{id}Delete a task

The /v1/ prefix applies Chapter 4's URL versioning strategy โ€” the right call here since the audience is broad and external, exactly the case Chapter 4 recommended it for. PATCH rather than PUT for updates reflects Chapter 2's distinction: most real updates here (like changing just a status field) are partial, not full replacements.

Handling Creation Safely: Idempotency

Creating a Task, Safely Retryable

POST /v1/tasks HTTP/1.1 Authorization: Bearer <token> Idempotency-Key: 3a1f9c2e-... Content-Type: application/json { "title": "Follow up with client", "dueDate": "2026-08-01" }

If an integrator's request times out and their code retries automatically, the same Idempotency-Key (Chapter 4) ensures the platform creates exactly one task, not two.

Pagination for Task Lists

Since tasks are added and completed constantly by many users at once, cursor-based pagination (Chapter 4) is the right fit โ€” offset-based pagination would risk exactly the skipped/duplicated-row problem Chapter 4's gotcha described:

Paginating /v1/tasks

GET /v1/tasks?after=eyJpZCI6NTIwfQ==&limit=20

Authentication & Rate Limiting

Each request authenticates with a bearer token in the Authorization header โ€” the token-based approach covered in depth in the Authentication & Session Security course (bc1-7), not re-taught here. Requests are capped per integrator using the X-RateLimit-* headers and 429 status introduced in Chapter 4, protecting the platform from any single integrator's runaway retry loop.

Adding Webhooks for Real-Time Notification

Rather than making integrators poll GET /v1/tasks/{id} repeatedly to detect a status change, each integrator registers a webhook URL once. When a task's status changes, the platform sends a signed event:

A task.updated Webhook

POST https://integrator-app.com/webhooks HTTP/1.1 Content-Type: application/json X-Signature: t=1717000000,v1=8a41c... { "id": "evt_88f2", "type": "task.updated", "data": { "taskId": 501, "status": "completed" } }

The integrator verifies X-Signature (Chapter 7's HMAC pattern) before trusting the payload, and deduplicates on evt_88f2 in case the same event is delivered more than once.

What About Internal Services?

If the platform's own task service needed to talk to its own internal search-indexing service at very low latency, gRPC (Chapter 6, with its own full course) would be the right internal choice โ€” but that's a separate concern from this external-facing design, and folding it in here would repeat exactly the mistake Chapter 8's gotcha warned against: reaching for whichever style is already in front of you rather than asking what each specific piece of the system actually needs.

Course Recap

Across nine chapters: the client-server model and what "API" really means (Ch.1), HTTP's methods/status codes/headers (Ch.2), REST's architectural constraints (Ch.3), REST's practical design patterns (Ch.4), SOAP's formal contracts (Ch.5), the RPC lineage into gRPC (Ch.6), Webhooks (Ch.7), a decision framework (Ch.8), and this capstone applying all of it to one real design. From here, the site's API Testing & Tooling course covers how to actually test a design like this one with Postman, cURL, and more, and the dedicated gRPC course goes deep on the internal-services piece only briefly touched on above.

๐Ÿ’ป Coding Challenges

Challenge 1: Add a Projects Resource

Tasks belong to projects. Design the REST endpoints for a new /v1/projects resource, following the same conventions (versioning, method choices, pagination) established in this capstone.

Goal: Practice extending a design consistently rather than inventing new conventions per resource.

โ†’ Solution

Challenge 2: Design a task.completed Webhook

Design the webhook payload for a new task.completed event, including a plausible event id and signature header, following this chapter's task.updated example.

Goal: Practice applying the webhook payload shape and signature pattern to a new event type.

โ†’ Solution

Challenge 3: Justify Cursor Pagination for /v1/tasks

Explain specifically why cursor-based pagination was chosen for /v1/tasks instead of offset/limit, referencing what would concretely go wrong with offset-based pagination given how this platform's tasks change over time.

Goal: Practice justifying a design decision with a concrete failure mode, not just a general preference.

โ†’ Solution

โš ๏ธ Gotcha: Designing Only the Happy Path

It's easy to design an API around what happens when everything goes right โ€” a valid request, a successful create, a clean 200. A real design also has to account for what happens when a task id doesn't exist (404), the bearer token is missing or expired (401), a required field is missing from the request body (400), or the integrator is sending requests too fast (429) โ€” all status codes and concepts this course already covered, but easy to forget to actually design for once the "main" functionality is working. A design that only handles success is an incomplete design, however good the happy path looks.

๐ŸŽ‰ Course Complete

That's the full API Types & Design course โ€” from "what is an API?" through REST, SOAP, RPC/gRPC, Webhooks, a decision framework, and this final end-to-end design. Next steps from here: the API Testing & Tooling course to learn how to actually exercise an API like the one designed above, or the dedicated gRPC course to go deep on the internal-services piece this capstone only touched on briefly.