Recently at work, while designing an API, we faced the question “should we put the user preferred language in the header or body“?
One of the comments was “If this piece of data will be used in multiple endpoints, then maybe we should put into the header.” That sounded at first a compelling argument, but since this was not the first time such discussion came up, I thought it was worth investigating further and writing about it.
First of all, let’s check out what the HTTP protocol says about headers and body.
After reading through the header’s section, I found the following two parts particularly interesting:
Entity-header fields define metainformation about the entity-body
The extension-header mechanism allows additional entity-header fields to be defined without changing the protocol, but these fields cannot be assumed to be recognizable by the recipient. Unrecognized header fields SHOULD be ignored by the recipient and MUST be forwarded by transparent proxies.
This tells us a couple of things:
- headers are metadata about the body
- the client is allowed to add custom headers
- the server shouldn’t reject the request if it doesn’t recognize the header
- proxies must forward custom headers — so in theory we don’t have to worry that they will be dropped somehow in the path from client to the server.
The body section of the HTTP specification does not specify what should and should not be inside a request body.
To sum up: header is about metadata, custom headers are allowed and body can be anything.
How does that help us answering the initial question? In theory, it should be easy: is the information metadata? If yes, then set it as a header. If not, set in the body.
Often the answer is indeed quite obvious. For instance, a correlation id meant for tracing is clearly metadata. That is not always the case though: there are cases when it’s not clear whether a piece of data can be considered metadata or not.
For those cases, I found the following three questions to be helpful when defining whether some given data should be in the header or in the body.
- Does the data fits a header already specified in the protocol?
HTTP defines a set of reserved headers (e.g. ETag, Authorization). If the data in question fits one of them, then you should use it for the sake of interoperability. Any device that implements the HTTP protocol will understand it out of the box. The most common case is sending a token (e.g. Bearer, JWT) in the Authorization header.
2. Can the data be used for caching, auth, rate limiting, routing by proxies?
Then the header is a good place, since proxies don’t usually inspect the body. Imagine you need to process user data in a specific geographical location due to data privacy regulations. In those cases, a possible solution is to route the traffic to a specific data center according to the user’s legal entity. That can be done in proxy or gateway using a custom header.
3. Is the data used in most endpoints of your API?
In that case, passing it in the header allows you to write a request interceptor to extract the header in a consistent way for all endpoints. Most web frameworks provide that as a built-in feature. An example for that would be the user ID.
In case the answer for the 3 questions is no, then very likely the data in question is not metadata and should be passed in the body.
What about the question in the beginning of this article?
In our case, the user’s preferred language:
- did not fit any exiting header. It’s worth mentioning the accept language header , which has a similar semantic. However, if we look at the specification below, we see that header is related to the response of the request. In our particular case, the preferred language was not used for the response.
The Accept-Language request-header field is similar to Accept, but restricts the set of natural languages that are preferred as a response to the request
- not useful for caching or routing
- used only in one endpoint
Since all three questions were answered with no, we set it in the body.
According to the HTTP protocol specification, the header is about metadata and the body can be anything. The definition of metadata leaves room for discussion. When you are not sure whether something is metadata, keep in mind the following points:
- Does the data fit in a header specified in the protocol? Then use it.
- Can this data be used in the path from client to server for e.g. routing? Define as a header.
- Is the data used my multiple endpoints? Define as header and write an interceptor.
- Otherwise use the body.
- Last but not least: be consistent. Whatever path (header or body) you choose, stick with it.
What about you?
How do you approach this question at your job? Did I miss or misinterpret something in specification?
Please drop a comment below, I would love to see how this could be approached differently.
Feedback to the text itself, any typos? Please don’t hesitate to point out.