Capstone: Designing an API for a Real Scenario

API Types & Design — 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.