What Is a Query Parameter? REST API Best Practices

What Is a Query Parameter? REST API Best Practices

When we are designing APIs, the goal is to give our users some amount of power over the service we provide. While HTTP verbs and resource URLs allow for some basic interaction, oftentimes it is necessary to provide additional functionality, or else the system becomes too cumbersome to work with.

An example of this is pagination: we cannot send every article to a client in one response if we have millions in our database.

A way to get this done is with parametrization.

Learn More About Moesif Monitor REST APIs With Moesif 14 day free trial. No credit card required. Try for Free

What is a query parameter?

A query parameter is a key-value pair attached to a URL after the question mark, used to modify or refine a request. In a URL like https://api.example.com/articles?author=kay&limit=10, the query parameters are author=kay and limit=10.

Query parameters are the most common way to pass optional information to an API endpoint without changing the underlying resource being requested. They are typically used for filtering, sorting, paginating, and selecting fields, and they keep the URL path itself focused on identifying the resource.

What is parametrization?

Generally speaking, parametrization is a kind of request configuration.

In a programming language, we can request a return value from a function. If the function does not take any parameters, we cannot directly affect this return value.

The same goes with APIs, especially stateless ones like REST APIs. Roy Fielding said this eloquently:

All REST interactions are stateless. That is, each request contains all of the information necessary for a connector to understand the request, independent of any requests that may have preceded it.

There are many ways in HTTP to add parameters to our request: the query string, the body of POST, PUT and PATCH requests, and the header. Each has its own use cases and rules.

The simplest way to add in all parameter data is to put everything in the body. Many APIs work this way. Every endpoint uses POST, and all parameters are in the body. This is especially true in legacy APIs that accumulated more and more parameters over a decade or so, such that they no longer fit in the query string.

While this is more often the case than not, I would consider it an edge case in API design. If we ask the right questions up front, we can prevent such a result.

What kind of parameter do we want to add?

The first question we should ask ourselves is what kind of parameter we want to add? This is one of those design decisions that compounds: once a parameter is in the URL, every consumer integrating with the API has to deal with it forever. Our broader API design principles guide covers the decision framework in full.

Maybe it is a parameter that is a header field already standardized in the HTTP specification.

There are many standardized fields. Sometimes we can reinvent the wheel and add the information to another place. I am not saying we cannot do it differently. GraphQL, for example, did what I would consider crazy things from a REST perspective, but it still works. Sometimes it is just simpler to use what is already there.

Take, for example, the Accept header. This allows us to define the format, or media type, the response should take. We can use this to tell the API that we need JSON or XML. We can also use this to get the version of the API.

There is also a Cache-Control header we could use to prevent the API from sending us a cached response with no-cache, instead of using a query string as cache buster (?cb=<RANDOM_STRING>).

Authorization could be seen as a parameter as well. Depending on the authorization detail of the API, different responses could result from authorized or unauthorized requests. HTTP defines an Authorization header for this purpose.

After we check all the default header fields, the next step is to evaluate whether we should create a custom header field for our parameter, or put it into the query string of our URL.

When should we use the query string?

If we know the parameters we want to add do not belong in a default header field, and are not sensitive, we should see if the query string is a good place for them.

Historically the use of the query string was, as the name implies, to query data. There was an <isindex> HTML element that could be used to send some keywords to a server, and the server would respond with a list of pages that matched the keywords.

Later, the query string was repurposed for web forms to send data to a server via a GET request.

The main use case of the query string is filtering, and specifically two special cases of filtering: searching and pagination.

But as repurposing for web forms shows, it can also be used for different types of parameters. A RESTful API could use a POST or PUT request with a body to send form data to a server.

One example would be a parameter for nested representations. By default, we return a plain representation of an article. When a ?withComments query string is added to the endpoint, we return the comments of that article inline, so only one request is needed.

Whether such a parameter goes into a custom header or the query string is mostly a question of developer experience.

The HTTP specification states that header fields are kind of like function parameters, so they are indeed thought of as the parameters we want to use. However, adding a query string to a URL is quickly done and more obvious than creating a custom header in this case.

These fields act as request modifiers, with semantics equivalent to the parameters on a programming language method invocation.

Parameters that stay the same on all endpoints are better suited for headers. For example, authentication tokens get sent on every request.

Parameters that are highly dynamic, especially when they are only valid for a few endpoints, should go in the query string. For example, filter parameters are different for every endpoint.

Modern APIs follow this split consistently. Stripe puts the auth token in the Authorization header and the pagination cursor in the query string (?starting_after=cust_123). OpenAI does the same. The pattern is consistent enough across major APIs that following it is a reasonable default, and breaking it is the design decision that requires justification.

Array and map parameters

One question that often crops up is what to do about array parameters inside the query string?

For example, if we have multiple names we want to search.

One solution is the use of square brackets:

/authors?name[]=kay&name[]=xing

But the HTTP specification states:

A host identified by an Internet Protocol literal address, version 6 [RFC3513] or later, is distinguished by enclosing the IP literal within square brackets (“[” and “]”). This is the only place where square bracket characters are allowed in the URI syntax.

Many implementations of HTTP servers and clients do not care about this fact, but it should be kept in mind.

Another solution that is offered is simply using one parameter name multiple times:

/authors?name=kay&name=xing

This is a valid solution but can lead to a decrease in developer experience. Oftentimes clients just use a map-like data structure that goes through a simple string conversion before being added to the URL, potentially leading to overriding the following values. A more complex conversion is needed before the request can be sent.

Another way is to separate the values with , characters, which are allowed unencoded inside URLs:

/authors?name=kay,xing

For map-like data structures, we can use the . character, which is also allowed unencoded:

/articles?age.gt=21&age.lt=40

It is also possible to URL-encode the whole query string so that it can use whatever characters or format we want. It should be kept in mind that this can also decrease developer experience quite a bit.

When shouldn’t we use the query string?

The query string is part of our URL, and our URL can be read by everyone sitting between the clients and the API, so we should not put sensitive data like passwords or access tokens into the query string. URLs leak into server logs, browser history, and Referer headers.

Developer experience also suffers greatly if we do not take URL design and length seriously. Sure, most HTTP clients will allow a five-figure length of characters in a URL, but debugging such kinds of strings is not very pleasant.

Since anything can be defined as a resource, sometimes it can make more sense to use a POST endpoint for heavy parameter usage. This lets us send all the data in the body to the API.

Instead of sending a GET request to a resource with multiple parameters in the query string that could lead to a really long, undebuggable URL, we could design it as a resource (for example, a search-resource). Depending on what our API needs to do to satisfy the request, we could even use this to cache our computation results.

We would POST a new request to our /searches endpoint that holds our search configuration in the body. A search ID is returned, which we can use later to GET the results of that search.

Conclusion

As with all best practices, our job as API designers and architects is not to follow one approach as “the best solution” but to find out how our APIs are used.

The most frequent use cases should be the simplest to accomplish, and it should be really difficult for a user to do something wrong.

It is always important to analyze our API usage patterns right from the start. The earlier we have data, the easier it is to implement changes if we got our design wrong. Moesif’s API analytics is one way to do that.

If we go one way because it is simpler to grasp or easier to implement, we have to look at what we get out of it. Nested resources can be used to make URLs more readable, but they can also become too long and unreadable if we nest too many. The same goes for parameters. If we find ourselves creating one endpoint with a huge query string, it might be better to extract another resource and send the parameters inside the body.

Start a 14-day Moesif free trial to see how your API’s query parameters are actually being used in production, per customer and per endpoint. No credit card required.

Frequently asked questions

What is a query parameter in a URL? It is a key-value pair appended to the URL after a question mark (?), used to modify or refine a request. Multiple parameters are joined with &, as in ?author=kay&limit=10.

What is the difference between a query parameter and a path parameter? A path parameter is part of the URL path itself and identifies a specific resource (the 123 in /users/123). A query parameter comes after the ? and modifies the request without changing which resource is being requested.

Are query parameters case-sensitive? The keys are case-sensitive by HTTP convention, though many APIs treat them case-insensitively. The values are always case-sensitive. Treat both as case-sensitive to be safe.

How many query parameters can a URL have? There is no hard limit in the HTTP specification, but most servers and proxies enforce a URL length cap (commonly in the 4–8 KB range, depending on the server). Past that, switch to a POST body.

Should I put authentication in a query parameter? No. Authentication belongs in a header (Authorization: Bearer ...). Query parameters leak into logs and browser history.

Learn More About Moesif Deep API Observability with Moesif 14 day free trial. No credit card required. Try for Free
Monitor REST APIs With Moesif Monitor REST APIs With Moesif

Monitor REST APIs With Moesif

Learn More