An API contract answers the questions a caller should not have to settle by trial and error. What path accepts the request? Which fields are required? What happens when the record is missing? For a small lesson catalogue, these decisions fit on one page before you choose a server framework.
The example here is a design exercise. The HTTP workbench uses simulated responses for lesson routes; it is not a live backend and does not save lessons in a production database. Use it to inspect requests and failure states, then implement and test your own contract separately.
Describe one resource clearly
Start with a lesson that has an identifier and a title. Keep the identifier stable when the title changes. The following proposed response gives a frontend enough information to draw a list item:
{
"id": 1,
"title": "HTML document structure"
}
Decide whether id is a number or string and keep that decision consistent. Decide whether a collection is a bare array or an object containing an array. An envelope such as {"items": [...]} leaves room for pagination metadata, but it also changes the shape callers must read. Write down the chosen shape rather than assuming every API uses the same convention.
For this proposed contract, use GET /lessons for a collection, GET /lessons/1 for a single item and POST /lessons to request creation. Successful reads return 200. Creation returns 201 and the created representation; a real service can also supply a Location header identifying the new resource. A missing single item returns 404. These are design choices grounded in HTTP status semantics, not proof that any given provider implements them.
Specify the unsuccessful request
For POST, require a JSON object with a title string. In your own implementation, trim surrounding whitespace, reject an empty result and document a maximum length. Choose an explicit limit for that project; the simulator's displayed validation rules govern the simulator. A frontend validation message helps the user, while the backend must independently enforce its contract.
{
"error": {
"code": "invalid_title",
"message": "Provide a non-empty title.",
"field": "title"
}
}
This error shape is a suggested design, not an exact promise about the workbench response. Give software a stable code to handle and people a message they can understand. Do not return stack traces, database credentials or raw internal exceptions.
A malformed JSON document and a well-formed document with an invalid field are different problems. Your contract might use 400 for malformed syntax and 422 for a validation failure, provided you document the distinction and apply it consistently. Avoid returning 200 for every failure just because the body contains an error field.
Give the caller a recovery path
Authentication failures, throttling and server failures require different reactions. A 401 response indicates missing or invalid authentication credentials. A 429 response signals too many requests; follow the provider's retry guidance, including Retry-After when supplied. A 500 response indicates an unexpected server condition. These statuses should produce different interface messages rather than one permanent loading spinner.
Be careful with automatic retries of creation requests. If a response disappears after the server saves the lesson, repeating the POST can create a duplicate. A production API may offer an idempotency mechanism, but that mechanism must be part of its documented contract. A simulated success screen cannot prove this behavior.
Browser callers also need to check HTTP results explicitly. As MDN's Fetch guide explains, fetch can resolve with a Response even for HTTP errors. Check response.ok or the expected status before treating the body as successful data.
Turn the draft into something testable
OpenAPI provides a standard way to describe HTTP paths, operations, schemas and expected responses. You do not need to write a large specification on day one. A small table of routes plus example bodies is a useful starting point, then an OpenAPI document can make the agreement machine-readable.
- In the workbench, inspect GET /lessons and GET /lessons/1. Record the actual response shapes.
- Try POST /lessons with a title, then with an empty title.
- Explore the available 401, 429 and 500 scenarios.
- Write one sentence describing what your interface should do for each result.
The expected result is a response table that distinguishes successful data, invalid input and temporary failure. If your table only lists 200, the contract needs more work. The next step is an implementation test against a real local service, where authorization, persistence and concurrency can actually be checked.