Sending request data
Understanding how to properly structure and handle request data is crucial for building robust APIs. This guide covers best practices and common patterns for working with API request data.
URL structure
Every API request starts with a URL that identifies the resource and any query parameters:
GET /api/v1/places?lat=40.759211&lon=-73.984638 HTTP/1.1
Host: api.example.orgKey components:
- Resource path: Identifies what you’re interacting with.
- Query parameters: Filter, sort, or modify the request.
- API version: Often included in the path.
HTTP request bodies
Requests can also have a “request body”, which is a payload of data being sent
the API for processing. It is very frowned upon to use a request body for the
HTTP method GET, but expected for POST, PUT, PATCH, and QUERY.
The request body can be in a variety of formats, but the most common are JSON, XML, and form data.
POST /places HTTP/1.1
Host: api.example.org
Content-Type: application/json
{
"name": "High Wood",
"lat": 50.464569783,
"lon": -4.486597585
}This POST request to the /places endpoint is trying to add a new place to
the API, once again using a JSON body. The Content-Type header lets the server
know to expect JSON data, so it can parse the body and create a new place with
the name “High Wood” and the coordinates 50.464569783, -4.486597585.
So far the examples of HTTP requests and responses have been using text, but in reality they are just a series of bytes. The text is just a human-readable representation of the bytes, and the tools that interact with the API will convert the text to bytes before sending it, and convert the bytes back to text when receiving it.
Most of you will interact with APIs using a programming language, and the code to send a request will look something like this:
HTTP tooling is essentially the same thing no matter the language. It’s all about URLs, methods, body, and headers. This makes REST API design a lot easier, as you have a “uniform interface” to work with, whether everything is following all these set conventions already.
Requests like POST, PUT, and PATCH typically include data in their body.
POST /places HTTP/1.1
Host: api.example.org
Content-Type: application/json
{
"name": "High Wood",
"lat": 50.464569783,
"lon": -4.486597585
}An example of a PATCH request following the RFC 7386: JSON Merge
Patch
PATCH /places/123 HTTP/1.1
Host: api.example.org
Content-Type: application/merge-patch+json
{
"name": "Highwood"
}This request body is yours to do with as you want. There’s a lot of freedom in how you structure your data, but there are some best practices to follow which you can learn more about in the [API Collections & Resources](/api-design api-collections) guide.
Data formats
This is generally where all of the domain-specific modelling will happen for data and workflows the API is meant to handle, but generally its not just spitting raw data around. The understand more about structuring request bodies, its important to understand data formats.
File uploads
Instead of sending JSON data, files can be sent in the request body.
Learn more about file uploads in the File Uploads guide.
Best Practices
1. Keep request & response data consistent
Maintain the same structure for data going in and out of your API. You want to
strive for predictability and consistency in your API design. When a user sends
a POST request to create a new resource, they should get back a response that
looks like the resource they just created. If a user updates a resource, the
response should return the new state of the updated resource.
// POST Request
{
"name": "High Wood",
"lat": 50.464569783,
"lon": -4.486597585
}
// Response
{
"id": 123,
"name": "High Wood",
"lat": 50.464569783,
"lon": -4.486597585,
"created_at": "2024-10-24T12:00:00Z"
}More on this in the API Responses guide.
2. Do not use request bodies for DELETE requests
Some API designers use a request body for a DELETE request to capture more information about the deletion of a resource.
Doing so is recommended against in both RFC9110: HTTP
Semantics
DELETE /drinks/1 HTTP/1.1
Host: api.example.org
Content-Type: application/json
{
"reason": "Weird combination of ingredients that proved unpopular with... everyone."
}Instead it would be more appropriate to create a new resource called POST /drinks/1/cancellation and essentially create a DrinkCancellation resource.
This resource can then be used to track the reason for the cancellation, and any
other information that might be useful, as well as avoiding the need for a
request body on the DELETE request.
3. Use the correct content type
The Content-Type header is used to indicate the media type of the resource
being sent in the request body. The most common content types are:
application/json: JSON dataapplication/xml: XML dataapplication/x-www-form-urlencoded: Form datamultipart/form-data: File uploads
Make sure to remind the user to set the Content-Type header in their request
to the correct value, and if they send the wrong thing then return a 415 Unsupported Media Type response.
4. Avoid optional request bodies
When designing your API, avoid making request bodies optional. It’s a little confusing, and harder for the user to understand what they need to send, harder to document, harder to validate, harder to generate code for, and harder to test.
For example, if you have a POST /loans/{id}/renewal endpoint with an optional renewal date, instead of making the whole request body optional make it required and make the renewal date property optional.
POST /loans/123/renewal HTTP/1.1
Host: api.example.org
Content-Type: application/json
{
"renewal_date": "2024-10-24"
}This way, the user always knows what to expect in the request body, and you can validate the request body more easily. If the user doesn’t send a renewal date, you can just use the current date as the default value. This is a lot easier to understand and work with than having to check if the request body is present or not, and then check if the renewal date is present or not.
Last updated on