In a REST API interface, should I explicitly check that the client used only the parameters used by the API, and return a HTTP 403 if a parameter the API doesn’t know about was included in the request?
A bit of context. I’m working on a API which minifies JavaScript and LESS files. Originally, JavaScript files were minified using Google Closure Compiler. Since Closure Compiler has multiple optimization levels (whitespace only, simple optimizations and advanced optimizations), the API also made it possible for the caller to specify (optionally) the level, so:
POST http://example.com/api/v1/js?level=whitespace
and:
POST http://example.com/api/v1/js?level=advanced
would in most cases give different results.
Recently, I had to add support for ES6. Given the lack of proper support for ES6 by Closure Compiler, I added YUI Compressor. Since this one doesn’t have optimization levels, the call is now simply:
POST http://example.com/api/v1/es6
Following POLA, I have an impression that I can’t let the callers use the API like this:
POST http://example.com/api/v1/es6?level=advanced
because they would expect a specific result, but get a different one. The solution would be to check for the presence of level
parameter in the URI and return a error message indicating that the parameter is not supported.
But I check for this specific parameter, why not checking for lecel
(a typo), or optimize
(a guess), or anything else?
Therefore, is the API expects parameters a
, b
and c
to be possibly included in the URI, what should be the response if the URI also contains d
or e
?
I would argue that your current approach is the one that follows the principle of least astonishment.
RESTfully-speaking, you defined two different resources under v1: js and es6. There’s no reason why these should accept the same query parameters, which aid in the representation of the resource.
And yet, you’re thinking that because all of these are js-minimizers, they would accept the same parameters. And it is actually not a surprise that different minimizers will require different parameters.
Another take at it is that API consumers are very well accustomed to different routes requiring different parameters, they usually don’t have a transparency of changing resources and keeping the same parameters. The exception to this rule might be some authentication parameter or pagination parameter for most APIs, but this is an exception because it deals with cross-cutting concerns that are application-wide responsibilities.
In your case, none of these apply directly. I think your current design is the one that feels more natural and astonishes less.
As you already have version for the API (meaning, no backward/forward compatibility concerns), it would be most logical to validate input and on unexpected parameters give 400
(Bad request, very generic), 422
(Unprocessable entity), which some argue is better than 400
, see below. Less applicable are 404
(Not found) – because es6
is found, or maybe in very rare cases even 501
(Not implemented).
Using 4xx HTTP code will save your users’ time troubleshooting the API access.
See also here (422
suggested as well) or here, which explains why 422
may be more appropriate. See also this question.
Personally, I’d sticked with 400
as well-understood and wide-spectrum HTTP code.
Depending on the use case, some query string parameters may be ignorable, for instance, underscore-prefixed ones, for cases when the only way to prevent caching is changing URL to include “fingerprints”.
0
level=… seems to be a backwards compatibility issue. In order to not frustrate the current users, I would check for level as it actually did something in previous version, but does not do anything in the current version and give a warning message (“level is deprecated and currently does nothing”) – but not stop the processing. I would give the error as part of the JSON response as opposed to the http error. I generally reserve those for errors that prevent processing.
If what you are supporting has the potential to release personal date (healthcare, or credit), then in the case of extra or misspelled variables I would just not return anything. If they are persistent enough, then they will figure it out.
If there is no chance of confidential information to leak out, then give as specific of error/warning as you want. In this case, I would put the warning messages in the JSON response as it would not affect processing to occur.
In either case, you should be checking all of the variables and values being passed in to confirm that there is no cross-site scripting or sql injections or any other security concern.
7
If you’re willing to spend the time and effort to validate against unused parameters then I’d suggest making such validation optional.
For example, you could accept a strict=true
flag (as a parameter or request header) that would result in an error if the client sends an unknown (or unused) parameter. Those who prefer tool-based safety nets (e.g. those preferring a compiler) will probably use it all the time whereas those who have a different preference can choose to limit its use to dev and/or test phases.
Some web frameworks (e.g. Rails) ignore unknown parameters by default. This can help with forward compatibility as your clients can start asking for a feature even before your server supports it. For example, you may choose to enable support for the level
parameter later and your clients wouldn’t need to be changed.