Imagine the website of a clinic where patients can book an appointment with a doctor. You need to develop an API so that third-party developers can integrate with the booking system of this clinic. The API should be able to change as new business requirements emerge.

For this post, let's ignore authentication and only focus on business requirements.

Nowadays, a common solution is to create an end-point that accepts an HTTP POST request in a /booking end-point. The request takes two parameters indicating the patient username and the intended doctor. Also, it takes another two parameters to represent the time they want to book and the contents of that booking.

Here's how the code looks like for a request to book an appointment at 10:20 in the morning:

A pseudo-code that shows an HTTP request using the JavaScript “fetch” API to the URL "clinic.example.com/booking" using the method "post." The request body contains the field "username" as "mary.doe," the field "date time" as "May 1st, 2018 at 10:20 AM" in the ISO8601 format, the field "intended doctor" as "jane," and the field "booking" as "Consultation with Doctor Jane."

One week later, business rules change. Now the doctor "Jane" only works in 15 minutes time slots instead of 30 minutes and the working day starts at 10 AM. The time of 10:20 AM is now invalid.

One common solution to handle this change is to introduce a new version to the end-point. If clients try to book an invalid date, the new version responds with a validation status code 422 Unprocessable Entity without breaking clients using the old version.

Another alternative is to create a GET /booking end-point. The response returns the time slots the doctor Jane has available so that clients can analyze and pick the ones they prefer.

Here's how the client code looks like for the second approach:

A pseudo-code that shows an HTTP request using the JavaScript "fetch" API to the URL "clinic.example.com/booking" using the method "get." The query string has the parameter "intended doctor" as "jane." The code extracts the "list of available time slots for the week" from the response. In the next statement, there's the same "post" HTTP request from the previous example, only now the "date time" field is the result of a function call. The name of the function is "choose available time for." The function receives the "date time" of “May 1st, 2018 at 10:20 AM” in the ISO8601 format as the first argument and the list of available time slots as the last argument.

A diagram that shows the client in the left and the server in the right. The client makes a GET request to the "/booking" end-point, looks at the response and then synchronously makes a POST request to the same end-point.

This example shows some critical aspects people get wrong with APIs:

The client code owns the URL and the methods. The server doesn’t have control over where the client makes the next request. Therefore, the server can’t change their URLs or methods because those changes can break somebody else.

Business logic changes in the server are very likely to cause breaking changes in the clients. That forces the server to create a rigid response body and implement URL versioning to remain backward compatible.

The client needs to find out exactly which parameters the server accepts on every end-point. They need to search those parameters either through Swagger docs or code examples in the "Developer" section of the website.

The main problem here is that most of the changes in the business requirements have a higher chance to break everybody.

Instead of forcing clients to have prior knowledge of all URLs, fields, and HTTP methods, the client can ask the server what is required to complete an operation, and the server can provide that. The only code the client needs to write is the code to interpret the message.

With that in mind, let's reimagine the same booking system.

Instead of starting with the code, though, let's first understand how people book an appointment without the technology:

*Customer arrives at the clinic* Customer: Hi, I want to make an appointment. Receptionist: What’s your name? Customer: Mary. Receptionist: What sort of appointment would you like to make? Customer: I want to consult with doctor Jane. Receptionist: We have these time slots available, which one do you prefer? Customer: That one. *Conversation ends*

As a server, you can ask the clients which information they need. Exactly how you would do if you were a receptionist in real life. After you understand how people book an appointment without the technology, you'll learn very early some subtle rules from the business. For example, each doctor has a different set of working hours; therefore, the receptionist needs to know the intended doctor before providing the available time slots, not after.

The server doesn’t know what you want; it only knows the description of the fields, which are empty by default. The client who wants to book an appointment has the intelligence to fill those fields and provide the necessary information to the server:

The example of two responses from the server. The server returns the fields "user name" and "intended doctor" in the first response. Each of those fields has a property with the name "read-only" and the value of "false." The server returns the fields "user name" and "intended doctor" with their respective values in the second response and the "read-only" property as "true." Additionally, the server returns the fields "available time slots" and "booking" in the second response with their "read-only" properties containing the value of "false."

The /start-booking path is the "entry point." The "entry point" is the only URL the client needs to initiate a conversation. That makes sense because in real life you also need to know which clinic and receptionist to contact; you need to know the host and the path. The client understands when the server asks for those questions and has some logic to fill the value of each field accordingly.

In the previous example, look at the value for the fields where the read-only property is true . The client puts the fields in the request body, and the server returns them in the next response. You can develop a contract telling clients not to fill the values for the fields that have a read-only property with the value of true .

That is how you store the state in the network.

Neither the client or the server needs to be the permanent canonical source for the state. The state is in the conversation. You can write some code that sends the payload back to each subsequent request using a recursive strategy like this:

A pseudo-code that shows the implementation of a client for the API. The name of the function is "run." The code calls the function at the end and logs the return value as the "exit point." The function has 3 arguments. One argument with the name "method" and the default value as "get," one argument with the name "url" and the default value as "clinic.example.com/start-booking," and one argument with the name "fields" without a default value; the last argument is optional. The code inside the "run" function executes an HTTP request using the arguments "method" and "url." Then it queries for an "action" with the id "required-fields" from the response body. If it has that action, it calls a function with the name "fill fields" to set the value of the fields for that action. Then, the code recursively executes the function "run" passing the fields to the next request until the "required-fields action" is not there anymore. If the HTTP request returns a response with no action that the client supports, then the function returns with whatever response body the server returns.

This way, the server can change the time slots, and the client code won't break. In the code example above, there's a function with the name "fill fields." That function represents the brain of the client, the "driver." It has the domain logic to consume the fields and select the time slots. That brain can have the intelligence to pick the best time slot available for their needs exactly how a human would do.