Recommended resources
The freely available resources this lesson will direct you to.
Hello! In the previous lesson, you separated an incident’s status-transition rule from the workflow that saves and reports the change. You now have a domain decision a caller can rely on: some requested transitions succeed, while others raise InvalidStatusTransition.
This begins the FastAPI module. Before writing endpoints, you need to decide what the API promises its clients. In this lesson, you’ll map incidents and their timelines to routes, choose HTTP methods for the operations clients need, and assign status codes to their outcomes. The result will be a small contract that you can implement in FastAPI later.
Start with resources, not functions
A Python application may contain functions such as change_status() and save_incident(). An API client should not need to know that internal structure. It needs to know what it can address: an incident, the collection of incidents, or the timeline belonging to an incident.
A resource-oriented design gives those concepts stable paths:
| Resource | Proposed path | What the path identifies |
|---|---|---|
| Incident collection | /incidents |
The collection, including a place to submit new incidents |
| One incident | /incidents/{incident_id} |
An incident identified by its ID |
| Incident timeline | /incidents/{incident_id}/timeline |
The entries associated with that incident |
The path identifies the target; the HTTP method expresses what the client wants to do with it. That is why /incidents/{incident_id} is more reusable than separate paths such as /get-incident/{incident_id} and /update-incident/{incident_id}.
Watch the resource-design portion of Rest API - Best Practices - Design by Code with Lucian. Treat its method-to-CRUD comparison as an introduction rather than an exhaustive rule: for example, POST can also submit data for processing.
Rest API - Best Practices - Design
Watch Rest API - Best Practices - Design by Code with Lucian for the distinction between a resource path and an HTTP method.
In resources and methods, focus on why a collection gets a noun-based path rather than a “create” path. Then watch paths and queries: an identifier in the path selects one resource, while query parameters can refine a collection request.
That distinction gives us a useful incident example. GET /incidents/42 targets one incident; GET /incidents?status=new still targets the collection, but requests a filtered view of it. Filtering does not require a separate /new-incidents resource. We will implement and constrain collection filters later; for now, we are choosing where they belong in the contract.
Likewise, a timeline is worth naming because a client may want to retrieve it independently of the incident’s current representation. Its path conveys the relationship without exposing how either resource is stored. A resource path is an API design choice, not a requirement to mirror database tables or Python modules.
Match each operation to an HTTP method
For an initial incident API, the useful operations are listing, creating, retrieving, and changing incidents, plus viewing their timelines. Read the “HTTP request methods” subsection of Microsoft’s Web API Design Best Practices before examining the proposed contract. Its collection-versus-item distinction is more important here than memorizing every possible route in its example table.
Web API Design Best Practices - Azure Architecture Center
Read the method overview in Microsoft’s Web API Design Best Practices to see why the same HTTP method can have a different purpose on a collection and on an individual item.
Under “Define RESTful web API methods” > “HTTP request methods,” read the method overview, then inspect the “Resource / POST / GET / PUT / DELETE” table immediately below it. Compare the /customers row with the /customers/1 row. In the adjacent “GET requests,” “POST requests,” “PUT request,” and “PATCH requests” subsections, use their opening descriptions to distinguish retrieval, creation, complete replacement, and partial update; stop before the detailed JSON patch-format discussion.
Here is a coherent first version of our contract. These are proposed behaviors, not endpoints we have already implemented:
| Client’s intent | Request | Successful response |
|---|---|---|
| List incidents | GET /incidents |
200 OK, with a collection |
| Create an incident | POST /incidents |
201 Created, with a Location header identifying the new incident |
| Retrieve an incident | GET /incidents/{incident_id} |
200 OK, with that incident |
| Partially change an incident | PATCH /incidents/{incident_id} |
200 OK, with the updated incident |
| Retrieve its timeline | GET /incidents/{incident_id}/timeline |
200 OK, with a collection of entries |
Why POST /incidents rather than POST /incidents/42? In this design, the server assigns the new incident’s ID. The client submits a proposed incident to the collection; after creation, the server tells the client where the new resource can be found.
Why PATCH for a status change? The client usually wants to change one aspect of an existing incident, not send a complete replacement for every field. A request might provide a new status while leaving the title and severity alone. PUT is conventionally used for replacing the representation at an identified resource path; if you offer it, you must define what a complete replacement means and preserve its idempotent behavior. We do not need PUT merely to claim CRUD coverage.
The timeline is different. In this design, entries describing status changes are recorded by the application when changes succeed. Clients can read those entries, but they cannot directly rewrite the audit history. It would be misleading to add POST, PATCH, and DELETE timeline routes simply because those methods exist.
There is an important operational distinction behind these methods. Safe requests, such as GET, should not be used to change incident state. Idempotent requests have the same intended effect if repeated: PUT and DELETE are defined to be idempotent, although repeated requests may receive different response codes. POST is not generally idempotent, and PATCH is not guaranteed to be. This matters when a client retries after a timeout: sending the same create request twice might create two incidents unless the API adds a specific safeguard. We will address repeatable creation later in the course.
Choose codes for outcomes, not just routes
An endpoint has more than one possible result. The status code tells a client whether its request succeeded, whether it needs to change the request, or whether the service failed. Continue with the method-specific response guidance in Microsoft’s article. Pay particular attention to its 201 Created and Location pairing, and to the distinction between a missing resource and a conflict with an existing resource’s state.
Web API Design Best Practices - Azure Architecture Center
Continue in Web API Design Best Practices for the response codes associated with the methods we selected.
In “GET requests,” read GET outcomes, including its status table. In “POST requests,” read from creation behavior through the following status table, especially its 201 row and Location header. In “PUT request,” read replacement and retries; you can skip its bulk-update tip. In “PATCH requests,” read the opening description and the outcome table, skipping the intervening patch-format examples. Finish with DELETE outcomes.
For our incident contract, the most useful responses are:
| Situation | Code | Why it fits |
|---|---|---|
| An incident or collection is returned | 200 OK |
The response contains the requested representation |
| A new incident is created | 201 Created |
The server created a resource; Location can identify it |
| A successful operation returns no body | 204 No Content |
For example, a future deletion operation, if the product permits deletion |
| The request is malformed | 400 Bad Request |
The server cannot use the request as sent |
| An identified incident does not exist | 404 Not Found |
There is no resource at that incident path |
| The path exists but does not support that method | 405 Method Not Allowed |
For example, attempting DELETE when incident deletion is not offered |
| A change conflicts with current incident state | 409 Conflict |
The resource exists, but the requested operation cannot be applied under the stated policy |
| Request data fails validation | 422 Unprocessable Content |
A useful FastAPI convention for data that fails declared input validation |
This is a design vocabulary, not a demand to return every code from every route. An empty incident list, for instance, can naturally be 200 OK with []: the collection exists even when it contains no items. By contrast, GET /incidents/999 should return 404 Not Found if incident 999 does not exist. GET /incidents/999/timeline should also be treated as missing if its parent incident does not exist, rather than silently presenting an empty timeline.
The status rule from the previous lesson makes 409 particularly concrete. Suppose incident 42 is currently new, and our example policy permits only new to in_progress. A well-formed request to set it directly to resolved identifies a real incident and a recognizable status; it fails because the requested change conflicts with that incident’s current state. 409 Conflict communicates something different from a malformed request or a nonexistent incident. The later lesson on client errors will handle the implementation and response-body consistency.
FastAPI’s generated documentation can display response codes beside each operation. The image below is an example for an items endpoint, not documentation for our proposed incident API. Notice that it shows a configured 201 success and a 422 validation response; an implementation still needs its success code set deliberately rather than assuming every POST automatically returns 201.

A 5xx response has a different meaning: the service failed to fulfill an otherwise valid request. A missing ID or disallowed transition should not accidentally become 500 Internal Server Error because an ordinary domain condition escaped as an unhandled exception. This distinction also matters for operational monitoring: a rise in genuine server failures is more actionable when expected client errors are classified separately.
Read the proposed contract as a client would
Consider a client reporting an incident. It submits data to the collection and receives the new resource’s location:
POST /incidents HTTP/1.1
Content-Type: application/json
{"title": "Deployment is failing", "severity": "high"}
HTTP/1.1 201 Created
Location: /incidents/42
Content-Type: application/json
{"id": 42, "title": "Deployment is failing", "severity": "high", "status": "new"}
The client can subsequently request GET /incidents/42 without knowing which Python function handles it or which database record stores it. This is the practical meaning of a stable resource contract: implementation details can change while the client-facing address and behavior remain understandable.
Now consider an attempted status update:
PATCH /incidents/42 HTTP/1.1
Content-Type: application/json
{"status": "resolved"}
If incident 42 is still new, our example transition policy rejects that jump. The response is 409 Conflict; the incident should remain unchanged, and no successful status-change timeline entry should be recorded. If 42 does not exist, the relevant response is instead 404 Not Found. Those two cases may arrive at the same route, but they do not mean the same thing to the client.
Finally, note what this contract deliberately leaves open. It does not yet specify the full request schema, response schema, authentication rules, or whether incidents may be deleted. Making those decisions when their lessons arrive is better than inventing routes now that imply unsupported behavior. The route and method choices already give the implementation a clear starting point.
Takeaways
An incident API can begin with a small resource map: /incidents for the collection, /incidents/{incident_id} for one incident, and a related timeline path. HTTP methods express operations on those resources; query parameters can refine a collection request without creating new paths. Status codes complete the contract by distinguishing creation, retrieval, missing resources, invalid input, and conflicts with incident state.
In the next lesson, you’ll create and run a FastAPI application with an ASGI development server. That will give these design choices their first executable routes.