So far we have a set of actions that users can perform along with a set of schema that define our collections. The next step in designing an API server is to define the endpoints that the client will send requests to when a user wishes to perform an action. We will be designing our API server to support requests using the Hypertext Transfer Protocol (HTTP) as defined in the HTTP Semantics specification.
When designing an endpoint we need to specify the following:
1. An HTTP method: typically POST, PATCH, DELETE, or GET.
2. A path which denotes a resource or set of resources that is utilized or an action that is taken.
3. An optional set of query parameters that can be used to identify resources and specify search and sort parameters.
4. Any required metadata. For example, an authorization token, a content-type specification, or a custom header.
5. A specification of the data that is expected in the request body (possibly none).
6. A specification of the response(s) to be sent to back to the client. Included in the specification are status codes and the data to be returned for each status code.
7. Example requests and responses
Below we discuss each of these requirements.
HTTP Methods
The HTTP Semantics specification defines a set of methods that indicate what sort of action the client expects the server to perform. The methods that you’ll see use most often by API servers are POST, PATCH, DELETE, and GET. You may notice that PATCH is actually not included in the HTTP Semantics specification, but rather included in its own specification titled PATCH Method for HTTP.
Below are heuristics that I use to decide which method to use for an endpoint.
1. POST: use when creating a new document, subdocument, or other resource.
2. PATCH: use when modifying a resource.
3. DELETE: use when deleting a document, subdocument, or other resource.
4. GET: use when only retrieving data.
Path
A path typically describes a set of resources or a specific resource, but can also indicate an action to be performed.
A path is a string that contains a sequence of segments where each segments starts with forward-slash (/) and is followed by a string or a parameter. A string typically describes an entity (like the name of a schema) or an action.
For example, the paths below might be used to specify an endpoint that attempts to log a user into the app.
POST /login POST /user/login POST /user/session
Parameters are used to indicate a specific resource and consist of a colon (:) followed by a word that describes a information that must be included in place of the parameter when a request is made.
For example, the path below might be used to specify an endpoints that deletes a particular message from a specific chat session.
| Method | Path | |
| DELETE | /chat-session/:sessionId/message/:messageId | |
| Parameter Name | Type | Constraints |
| sessionId | ObjectId | Valid chat session Id |
| messageId | ObjectId | Valid message Id |
When a client sends a request to this endpoint it replaces :sessionId with an identifier (e.g. ObjectId) for a chat session and replace :messageId with an identifier for a specific message.
When you define your paths, be consistent. For example, don’t use underscores (_) , dashes (-), and camel-casing when writing segment names. Stick to one form. Also, avoid using verbs as segment names. For example POST /group/enroll should be rewritten as POST /group/enrollment.
Query Parameters
Query parameters are optional key=value strings that are appended to a path to identify specific resources or to specify search and sorting criteria. Query parameters are usually only defined for GET requests.
For each key/value pair that is permitted by an endpoint, the specification should specify the name of the key, the set of allowable values, whether or not it is required, the default value, and any constraints.
When a client wishes to send a request that includes query parameters it appends a question mark (?) after the path and then appends the key=value strings (query parameters) separated by ampersands (&).
For example, suppose we have an endpoint that allows the client to specify how many documents to return by using a query parameter key named limit. Here the specification might show:
| Query Key | Type | Required | Default Value | Constraints |
| limit | Number | No | 20 | positive value |
The client might then send a request with the following path and query parameter:
/chat-session/1234/messages?limit=10
Metadata
Some endpoints require the client to pass additional information via the request’s header. This metadata can, for example, describe the sender or describe the content supplied in the body.
If an endpoint requires metadata be supplied in a header field, the specification for the endpoint should specify the header field name and the format of the allowable values.
One common example is for an endpoint to require a Bearer token in the Authorization field in the header of a request.
For an endpoint that requires a bearer token the specification for the endpoint might include the following:
| Header Field Name | Value |
| Authorization | ‘Bearer {token}’ |
Request Body
We’ve seen that we can pass information to an API server via path parameters and query parameters. We can also pass information to an endpoint via the request’s body. We can pass data using a request’s body to endpoints that use the POST, PATCH, and DELETE methods. We cannot pass data using a requests body to an endpoint that uses the GET method.
Data that is passed to an endpoint in the request’s body is typically passed in JSON format. A specification should list all of the allowable fields in the JSON object, and for each field, the specification should indicate the type of value that is allowed, any constraints on the values, whether or not the field is required, and default values for non-required fields. Constraints can be viewed as the checks that the client should perform else risk getting an unsuccessful response.
In the example below we show the specification for an endpoint that creates a new user account.
| Body Property | Type | Constraints | Default Value | Required |
| username | String | Yes | ||
| password | String |
Length >= 8 |
Yes | |
| phone | String | Must be a valid US phone number with area code: (xxx)xxx-xxxx | undefined | No |
Response Data
An API specification must also define the HTTP status codes that can be returned in a response and the data (if any) that is returned for each status code.
HTTP status codes are defined in the following classes:
- 1xx (Informational)
- 2xx (Successful)
- 3xx (Redirection)
- 4xx (Client error)
- 5xx (Server error)
Some commonly used status code are listed below:
| Status Code |
| 200 OK |
| 201 Created |
| 400 Bad Request |
| 401 Unauthorized (unauthenticated) |
| 403 Forbidden (unauthorized) |
| 404 Not Found |
| 409 Conflict |
| 500 Internal Server Error |
A specification should also contain a description of the data that is returned for each status code that is returned.
Below is an example showing the response specification for the POST /user endpoint. Note that most endpoints can potentially produce a 500 status code.
| Status Code | Response Data |
| 201 Created | User Object |
| 400 Bad Request | ‘Validation Error: invalid username’ |
| ‘Validation Error: invalid password’ | |
| 409 Conflict | ‘Invalid Data: username already exists’ |
| 500 Internal Server Error | ‘Internal Server Error: { reason }’ |
Example Requests and Responses
If we recognize that the primary audience of our API specification are client developers then we should also recognize that by providing them with examples of request data and the resulting response data will help the client developers understand the information flow that the endpoints provide. Examples can include scenarios that produces successful responses and scenarios that triggers unsuccessful responses.