Free learning / 45-minute REST API lab
REST API 101: read a resource, change it deliberately
Define resources, representations and HTTP methods. Run seven local request sequences, inspect repeated operations and keep an older edit from overwriting newer work.
No account needed. Use your own copy for writing and keep the original as a reference.
Engineering lab library / Run locally
See what the request actually changes
Predict statuses and state changes, then inspect the actual exchanges. Change one cause at a time and explain why a new version tag does not fix an old intent.
Real HTTP on a temporary loopback port, with fictional in-memory tickets. No model, authentication, external service or persistent storage; this is a local teaching server.
Start here / Python 3.11+ · Standard library
REST resource lifecycle · local HTTP
Compare actual reads, creations, replacements and deletions; inspect stale and missing conditions.
Python 3.11+ standard library · Loopback only · Fictional tickets
An original 45-minute lesson for engineers who know functions and records but are new to web API design. Define the pieces, follow the diagram, predict seven exchanges, then inspect actual HTTP and state changes. All tickets are fictional.
1. Give each piece a different job
An API is a software interface: the operations, inputs, outputs and errors that a caller can rely on. REST, Representational State Transfer, is an architectural style for networked applications. It organizes interactions around resources and a uniform interface. It is not a programming language, product or synonym for returning JSON.
Think of a ticket as a record behind a service. /tickets/T-104 identifies it. The JSON you receive is a representation of that resource. It need not expose every internal database field. HTTP carries the request and response; JSON is the data format chosen here. FastAPI is one Python framework for implementing such a service. This lab uses Python's standard library so you can inspect the exchange without installing a framework.
| Piece | In our fictional service | Familiar comparison |
|---|---|---|
| Resource | Ticket T-104 | A record with an identity |
| Address | /tickets/T-104 | The reference used to find the record |
| Representation | {"title":"Pump check","status":"open"} | A public view of the record |
| Method | GET, POST, PUT or DELETE | The requested operation |
| Response | Status, headers and sometimes a body | A result with explicit metadata |
The function-call analogy has a limit: a remote call can fail after the service has performed work. A caller must reason about what it observed, not assume that a missing response means nothing happened. An FDE designing an agent tool needs to distinguish reading a ticket from creating or replacing one before giving a model access to that tool.
REST also includes client/server separation, stateless requests, cache rules, layering and a uniform interface, including hypermedia controls; code on demand is optional. This small lab teaches resource interactions and HTTP semantics, not every REST constraint. Stateless requests do not forbid a server from storing tickets. The server still needs authentication and authorization in a real system; a method, identifier or version tag supplies none of those.
2. Read the method before repeating the call
| Method | Our contract | What to predict on repetition |
|---|---|---|
GET /tickets/T-104 | Read the current representation | No requested business mutation |
POST /tickets | Create a new ticket; return its address in Location | Two submissions can create two tickets |
PUT /tickets/T-104 | Replace the complete writable representation, if its version still matches | Same intended effect; a repeated condition may now fail |
DELETE /tickets/T-104 | Remove this resource | It stays absent; response statuses can differ |
Safe describes a method whose requested semantics are read-only. Logging a GET is possible; silently dispatching a technician on GET violates the read contract. Idempotent means identical requests have the same intended effect as one request. It does not promise identical responses or that replaying an old intent is always appropriate after another person's edit. GET, PUT and DELETE are idempotent by HTTP semantics; POST has no general replay guarantee.
The lab's PUT replaces title and status. Sending just status is refused instead of silently treating PUT as a partial update. PATCH is another HTTP method for a defined patch document; it is not implemented here, and whether a particular patch is idempotent depends on its operation.
An ETag, or entity tag, identifies a representation version chosen by the service. It is an opaque value: copy it exactly rather than deriving one from an integer you guessed. The If-Match request header says to perform this change only if the current representation matches the supplied tag. Our narrow contract accepts one strong tag returned by GET. It does not implement tag lists, wildcards, cache validation or the rest of HTTP's conditional syntax.
UNDERSTAND THE SYSTEM
One resource, different operations
REST is an architectural style; HTTP carries the exchange. An ETag labels a representation version. If-Match asks the service to change it only if that version still matches. Follow fictional tickets through the local lab cases; this diagram explains the exchanges, rather than running a server in your browser.
See what moves and why.
Read the connections in plain English
- 01Calling application → HTTP request
Select the operation and the exact representation the caller intends to send.
- 02HTTP request → Service checks
The local client sends actual HTTP to the service.
- 03Service checks → Resource state
Only accepted operations alter the ticket. Rejected writes preserve the existing state.
- 04Resource state → HTTP response
The service reports the result of the read, mutation or refusal.
- 05HTTP response → Client decision
The client compares the response to its intended work and the documented contract.
- 06Client decision → Calling application
Before another edit, inspect the current representation and reconcile the intended change.
Check your understanding: Does fetching a fresh ETag automatically make an old edit correct?
No. The tag guards a version comparison. The caller must inspect the current content and reconcile the intended change. In the lab's second edit, refreshing only the tag lets an old body overwrite the other editor's title.
3. Predict and run seven exchanges
Download fde-rest-resource-lab.py into your practice folder. Python 3.11+ is enough; there are no packages, accounts or credentials to configure. The program starts a short-lived service on a randomly assigned 127.0.0.1 port, makes real local HTTP requests, and closes it. It never binds to a public interface, contacts an external service or writes ticket data to disk. Use fictional data. Python's teaching HTTP server is not a production deployment template.
python3 --version
python3 fde-rest-resource-lab.py compare
python3 fde-rest-resource-lab.py stale-editBefore running, write the statuses and final ticket state you expect. Each case starts from its own T-104 with title Pump check, status open and ETag "T-104-v1". Each trace records the request, response and a teaching-only inspection of server state. A real client normally cannot inspect that last field; it needs the service's documented read or reconciliation interface.
| Case | Expected status sequence | State to inspect |
|---|---|---|
read | 200, 200 | Same one ticket and same ETag |
create-repeat | 201, 201 | New T-105 and T-106, each with its own Location |
replace-repeat | 200, 200, 412 | GET, then two identical conditional PUTs; T-104 stays queued at v2 |
delete-repeat | 204, 404 | T-104 is absent after both DELETEs |
stale-edit | 200, 200, 412, 200 | Another editor's title and queued status survive the stale PUT |
missing-precondition | 428 | No change; the required If-Match header was absent |
partial-replacement | 200, 400 | No change; the complete writable representation was not supplied |
200 means this request succeeded. 201 identifies creation; inspect Location. 204 has no response body. 404 says this resource was not found in this lab. 400 rejects our malformed representation. 412 means the condition did not hold. 428 means this service requires a condition; not all services enforce that rule. Read the specific contract rather than assuming every error is retryable.
4. Follow the edit that arrived too late
The stale-edit case models two callers in a deliberate sequential order. It is not a concurrent-load test:
- Editor A reads T-104 at v1 and prepares to close it with the original title.
- Editor B replaces it with
Pump check: reviewed scope, statusqueued, using v1 in If-Match. The response is 200 and the new representation is v2. - Editor A sends the older full representation with v1. The server compares the tag before mutation, returns 412 and keeps B's representation.
- A fresh GET shows B's title and queued status. A must compare the changes and decide what is still intended before sending another update.
Read dispatch in the program. Find the validation, missing-condition and mismatched-condition branches before row["body"] = raw. The exact submitted JSON bytes are stored and returned; the ETag sits in a response header, not in a field the caller can overwrite. There is no login or permission check in this fictional local service. Version checking and authorization are different jobs.
In replace-repeat, the first PUT changes v1 to v2. The second identical request still carries v1, so it receives 412 while the resource remains queued. Different statuses do not disprove idempotency. They also do not prove who performed an earlier change. After uncertain delivery, read/reconcile according to the service contract instead of declaring the operation failed or blindly resending with a new tag. This lab does not simulate a network timeout or implement transactional idempotency keys.
5. Change one cause at a time
Make two separate copies. Keep the reference file unchanged.
Experiment A — count creations. In one copy, change CREATE_ATTEMPTS = 2 to CREATE_ATTEMPTS = 1. Run create-repeat. There should be one 201, one Location and just one new ticket. The original two creations were actual server mutations, not repeated display of the same result. A caller-generated key would need an explicit server-side deduplication contract; merely adding an undocumented header cannot establish one.
Experiment B — expose a misleading repair. In the other copy, set REFRESH_BEFORE_SECOND_EDIT = True and run stale-edit. The code gets a fresh ETag but resends A's old body without reconciling it. Expect statuses 200, 200, 200, 200, 200 and final title Pump check, status closed, v3. B's reviewed title was overwritten. This is a deliberately flawed client strategy in the local exercise, not the recommended repair.
Explain why the server's condition passed. It verifies that the submitted tag matches now; it cannot tell whether A actually reviewed the current content. Write a better client sequence: read current content, compare with the original, reconcile the intended change, then submit the reviewed representation with the matching tag. A new 412 should restart that review instead of forcing a write.
6. Turn the API into an agent tool contract
An agent proposes closing T-104 after reading v1. While it is thinking, a human changes the scope. Sketch the components: model proposal → application policy → HTTP client → resource service → observed result. State who owns each check.
Produce a short contract with:
- A read-only tool separate from the write tool, with explicit allowed fields.
- Trusted caller and resource authorization outside model-generated arguments.
- A version tied to the representation that was actually reviewed.
- A 412 path that returns for reconciliation, a 428 configuration repair and an uncertain-delivery path that preserves an unknown outcome.
- A distinction between receiving a 2xx response and completing the customer's intended work. Changing a ticket record does not prove a technician was sent.
Discuss one new deployment boundary: TLS, identity, durable storage, atomic compare-and-update, auditing, request limits or crash recovery. The sequential in-memory lab has not established these. Choose the smallest test that would show the boundary you are proposing, using an isolated environment.
7. Keep the evidence and go deeper
Keep seven predictions and observed traces, both controlled edits, your repaired client sequence and the agent tool contract. A useful explanation names the resource, representation, operation, observed state and remaining uncertainty.
Continue with the first FastAPI request to see a framework route and validate the input. Use networking 101 for connection boundaries, and action recovery for the harder case where a response is lost after an external effect.
Primary references checked 21 October 2026: