This falls down as soon as it makes a fundamental misunderstanding of what makes a REST api into a REST api.
It gives this as a ‘bad’ example:
GET /v3/application/shops/{shop_id}/listings/{listing_id}/properties
With the justification that “The {listing_id} is globally unique; there's no reason for {shop_id} to be part of the URL. “
No the point of the API is that /v3/application/shops/{shop_id}/listings/{listing_id}/properties is a globally unique identifier. Your belief that parts of that id have global meaning outside the context of that identifier is irrelevant - that path is the identifier for the resource.
And having hierarchical paths is useful because you can do things like manage permissions on parts of the hierarchy - users might have permission to check listings in certain shops and we can characterize that as them having permission on /v3/application/shops/{shop_id}/listings/*.
Directory structures of resource identifiers are good and logical and not a ‘bad’ API design practice at all. You might as well argue the UNIX file system is a bad design because all the files have a unique inode id so paths are completely unnecessary.
No, the shop id is not in fact part of the globally unique identifier of an Etsy listing, and the properties are not dependent on the shop. Etsy listings have a 1:N relationship with Etsy shops.
The API was a mistake, which they are slowly correcting - they've already changed:
GET /v3/application/shops/{shop_id}/listings/{listing_id}
to:
GET /v3/application/listings/{listing_id}
...and I presume they will eventually change the rest of the listing-related endpoints over time.
Managing permissions using the hierarchy of a URL is silly at best, dangerous at worst. The first thing any attacker will do is plug in an alternative shop id and see if it grants access to the non-permitted listing. If permissions are attached to the shop (and for Etsy, they are) the server needs to load the listing, figure out the associated shop, and then check permissions. The client cannot be trusted to provide the correct shop id, so there's no point in asking for it.
That’s a critique of Etsy’s API not of good REST resource identification.
No plugging in a shop you have permission to doesn’t work if your resources are hierarchical any more than plugging ~/passwd let’s you read /etc/passwd because you have read access to your home directory. Those are different resources and one of them exists and is locked down and the other one doesn’t exist.
> Managing permissions using the hierarchy of a URL is silly at best, dangerous at worst.
Or perhaps what you call silly is just you being unaware of what you don't know. There are valid cases to handle permissions using the structure of URLs. As well, the danger you allude to comes from handling it naively. Even the hypothetical attack you suggest might be among the first thing any non-tech savvy person might think of trying.
The scenario you're describing above is simply one of dealing with redundant information in a situation where inferring the whole from the part is not detrimental (for the platform). A case can certainly be made that with that simplification, some optimization opportunities are also lost. Perhaps Etsy doesn't need them. Others might.
> The client cannot be trusted to provide the correct shop id.
The client cannot be trusted period. If I provide a signed cookie that contains a list of authorized shops and they return something else, good thing that cookie is signed. Also good thing the cookie contains the shops, no need to touch the disk if the URL doesn't match the list.
You apparently haven't read Fielding's paper. Etsy isn't doing it right. If you read Fielding's paper, OP's point is correct. The whole URL is a resource.
> [having shop_id in the URL] inevitably causes problems when your invariant changes down the road - say, a listing moves to a different store or can be listed in multiple stores.
Basically the choice is between having a perpetual unique URL to a listing or multiple ones, maybe valid at the same time and some of them maybe invalid in future, when a listing is removed from a shop.
A visitor with a valid unique listing id will always be able to look at the product. If there is a shop id in the URL that URL might become invalid and the visitor loses access to the product and had to search for it again, adding friction. With the global unique is the visitor will discover that the product is offered by another shop (maybe a new one from the same tenant?) which is usually not important.
Permissions for the listing could be handled by matching the shops a user has access to with the shops the listing belongs to.
I’m not arguing for purity I’m arguing that these guidelines are not good guidance for designing ‘REST APIs’.
If you are designing a ‘REST API’ you have already committed to ‘purity’. If you follow this guidance you are not designing a REST API you are designing a JSON over HTTP api with parameters in the query string.
There’s no standard. Every REST API looks different. Clients have to refer to documentation anyway, so consistent URL patterns achieve nothing. People waste large amounts of time over totally inconsequential minutiae like whether to use singular or plural words in URLs.
Separating idempotent calls from non-idempotent calls is useful, but REST overcomplicates this. All that’s needed is read and write calls, yet REST has get, post, patch, put, delete…
REST is also inefficient. Clients could read the data they need in one HTTP request, but most “RESTful” APIs force clients to make many requests for the sake of
what is essentially aesthetics.
You are supposed to do use those 3 through some kind of heavy tool, while it's well understood that you do rest with just an http library.
Rest is simpler, but comes at the cost of a lot of nice things like automatic endpoints generation and type verification. The problem is that the heavy tooling tends to not be there or not work correctly. But this is not a win for that kind of simplicity, that's a reason to improve the protocol design.
I’d just like to interject for a moment. What you’re referring to as REST, is in fact, JSON/RPC, or as I’ve recently taken to calling it, REST-less. JSON is not a hypermedia unto itself, but rather a plain data format made useful by out of band information as defined by swagger documentation or similar.
Many computer users work with a canonical version of REST every day, without realizing it. Through a peculiar turn of events, the version of REST which is widely used today is often called “The Web”, and many of its users are not aware that it is basically the REST-ful architecture, defined by Roy Fielding.
There really is a REST, and these people are using it, but it is just a part of The Web they use. REST is the network architecture: hypermedia encodes the state of resources for hypermedia clients. JSON is an essential part of Single Page Applications, but useless by itself; it can only function in the context of a complete API specification. JSON is normally used in combination with SPA libraries: the whole system is basically RPC with JSON added, or JSON/RPC. All these so-called “REST-ful” APIs are really JSON/RPC.
Found a contradiction that I don't understand. From rule 1:
# GOOD
GET /products # get all the products
GET /products/{product_id} # get one product
# BAD
GET /product/{product_id}
But then in Rule 2:
GET /shop/{shop_id}/listings # normal, expected
Shouldn't that be "/shops/{shop_id}/listings"? Or is it plural only if you can actually GET the path (i.e. there's no GET for just "/shop") and otherwise it should be singular?
This is the Etsy API, but actually was a typo on my part. They use the plural /shops (as I showed in the other Etsy example). I've corrected the original article, sorry about that!
Avoid plural nouns in English API endpoints because English is full of irregular plurals. For example:
goose -> geese
child -> children
index -> indices
vertex -> vertexes
analysis -> analyses
This makes English plurals unpredictable especially for for non-native speakers and hurts API consistency and discoverability.
Also consider that for a CRUD interface you may need the singular form anyway (POST api/student/create), and adding the plural means doubling the API route namespace.
It's cleaner and simpler to stick with singular nouns.
An API is not an essay, in OOP you write Array<Student> and not Array<Students> and yet you understand the type is about an array of students. Getting hung up on grammar in an API is probably the dumbest problem to have.
If you think `GET /student` is confusing, or more importantly, structurally restrictive as an API, you can think about it as `GET /student/filter` where the "filter" may be a specific student id, or a range of ids, or other conditions such as `GET /student/top` or `GET /student/graduated` and then all students will be just the filter "all" or: `GET /student/all`.
As for `POST /student/create`... it doesn't matter. To use one of Fielding's own examples from his blog, how'd you turn a lamp on and off via REST? Would you be like `POST /lamp`? No. It's unclear WTF is happening.
While I do agree with this in almost all cases, I have found scenarios where there are actions that don't map easily to a HTTP verb and need something more explicit.
Does “GET /students” return all the students in the system? Probably not.
So in fact you’re fetching some subset of students anyway, and the size of the returned set might be one or zero depending on your query.
Given that, “GET /student” seems just as meaningful because neither the singular nor the plural can fix the ambiguity about what you’re actually getting.
* If you’re going to forbid people changing a parameter with a PUT or PATCH request, then the schema for these shouldn’t list them as parameters. This seems to creep in to APIs constantly as people are lazy and will use the same serializer method as for POST with an additional check somewhere in the code that changes the response. Just don’t do it!
* Don’t change the response format based on query parameters. It makes it hard for typed languages to use the API because the client has to handle all of the weird response types you’ve got. Inevitably you end up with more and more getting added and it any client becomes crazily complicated. 99% of the time it’s not worth the bandwidth saving - and if there’s lots of useless information that clients don’t want, it’s worth thinking about whether the API design is right in the first place.
* Stick to one mechanism for doing things. Pagination and sorting behaviour should be the same for all endpoints. The end user doesn’t care that you’re a hip microservices company where teams don’t talk to each other - if the APIs behave weirdly and inconsistently between themselves, it will be hard to use.
I very much agree with your first and third point, from experience. As for the second one — if consuming dynamic data structures is hard in typed languages, maybe they are not the right tool for that particular job?
What I have seen is endpoints trying to corral their responses into one-size-fits-all schemas in the situation you're describing, with predictable outcomes. Lots of overhead in most situations, tricky documentation, lots of optionals.
Under that premise, I have to say that at least for generic APIs with many differing clients, the idiosyncrasies of typed-language clients would not rank too highly on my list of design considerations — not when they are in the way of simpler, easier to understand responses.
As for the second point, that's what Accept header is for. And I personally never had much trouble in Go with deserializing all those "weird response types" but it may depend on one's coding style.
> I have to say that at least for generic APIs with many differing clients, the idiosyncrasies of typed-language clients would not rank too highly on my list of design considerations
Hey, would you like to consume an exchange format that has meaningful distinction between strings and atoms? Those come from the dynamically-typed languages area!
Some good points - particularly about not returning arrays (I've made that mistake!)
But I feel 410 instead of 404 is pretty controversial:
> There are many layers of software that can return 404 to a request
Anything in your stack can return any HTTP error code - I don't see why 404 is special.
> When calling (say) GET /things/{thing_id} for a thing that doesn't exist, the response should indicate that 1) the server understood your request, and 2) the thing wasn't found. Unfortunately, a 404 response does not guarantee #1.
The server is free to return other codes for other classes of problems. The server could return 400 for a bad request, and leave 404 for "thing wasn't found", indicating it understood the request but it wasn't found.
Also surprised not to see RFC 7807 / RFC 9457 (Problem Details) not mentioned in the "structured error format" section.
> Anything in your stack can return any HTTP error code - I don't see why 404 is special.
I'm surprised you don't - in my experience 404's are by far the most common response to get when you haven't wired things up correctly. Sure anything in the stack _can_ return any code and response they want, but you're still much more unlikely to come across a 410 rather than 404. If that unlikeliness saves you support calls down the line then that's pretty good.
With REST 404s due to missing resources (non-existing ID), you should generally get corresponding error information in the body (as also described in TFA), and clients should log/display that information. That should make enough of a difference. There’s a lot of “should” here, of course, but instead of teaching developers to not use 404, it would be better to teach them to create and handle error responses appropriately.
There are two reasons behind 404 being a bad response code to use for empty results.
- Did I get a 404 on this endpoint because the endpoint doesn't exist? Or did I get that because the object I was looking for doesn't exist? Great, I need to dig into the response body to find out, indicate that I can either get a 200 or a 404 with this endpoint, and deal with the odd case where the API returns HTML regardless of the MIME type in the Accept header if the endpoint itself is not there because "fuck you, couldn't be bothered".
- Some HTTP libraries will consider anything that's not a 1/2/3xx an error. That can be annoying to deal with.
If anything that's not 1/2/3xx is a problem then 410 won't be a solution. And I doubt that your http library having issues to handle 4xx can handle 1xx correctly.
> You could use 404 but return a custom error body and demand that clients check for a correct error body. This is asking for trouble from lazy client programmers. It might or might not be "your fault" when clients see eventually inconsistent data, but the support calls they send you will be real.
Make sure that your 404 responses were always documented, then tell them to RTFM.
The problem is that the support call comes in as "your DELETE call isn't actually deleting". Sure it's not your fault, but it imposes a cost on you to investigate. And of course the first time you go directly to RTFM without checking will be the time it actually is your bug.
404 is special because it's so incredibly common. Why take the risk? There are other perfectly good error codes that - in practice - don't have this issue.
I prefer to consider 404 as protocol error and missing thing as business error. That way 404 signals wrong endpoint and 200 + error message + empty result set signals wrong id.
Or even more simple: Anything other than 200 means check infrastructure docs and if you don't like the 200 check the business requirements.
As a client I generally dislike APIs that use 200 for error conditions. The problem is that API implementors often change the structure of the response.
GET /thing/THG123
# on success:
{"id":"THG123", "name":"thingie"}
# on failure:
{"error":"no such thing"}
Working in typed languages, this requires parsing the response, determining success or failure, then reparsing the response into the appropriate type. Annoying.
Of course it's not always like that, some APIs will put both the error and data in a wrapper object and one field or the other will always be null:
This is less annoying but it's still tedious. We could eliminate the wrapper if we only had an out-of-band signal to indicate whether the client should expect a success response or an error response... like maybe an HTTP status code? I mean, it's right there, why not use it?
I highly recommend anyone to read Google's [AIP](https://google.aip.dev/). There's even a grpc schema linter for it. Put more focus on the resource data design than nitpicking on transport details. I would consider the best lessons to be:
- Optional but supported user defined identifiers, it's so frustrating to work with API that passes you back an identifier.
- String identifier (names) for resources, with some kind of type namespacing, i.e. the prefix in the author's document
- Consistent set of fields (create_time, update_time, annotations, ...)
- Avoid dynamic map (this is a JSON self-inflicted wound)
Declarative friendly makes writing scripts, pipelines so much better because of idempotency. It also pairs very naturally with resource Oriented design.
LROs are applicable to any request that runs longer than a second or a couple of seconds. Having a unified interface can be very powerful for implementing offline task workers and pipelines.
This one is probably controversial as it's makes implementing basic filtering quite a bit harder. I haven't quite seen the issues it's supposed to solve play out in practice but it's interesting nonetheless.
It gives this as a ‘bad’ example:
With the justification that “The {listing_id} is globally unique; there's no reason for {shop_id} to be part of the URL. “No the point of the API is that /v3/application/shops/{shop_id}/listings/{listing_id}/properties is a globally unique identifier. Your belief that parts of that id have global meaning outside the context of that identifier is irrelevant - that path is the identifier for the resource.
And having hierarchical paths is useful because you can do things like manage permissions on parts of the hierarchy - users might have permission to check listings in certain shops and we can characterize that as them having permission on /v3/application/shops/{shop_id}/listings/*.
Directory structures of resource identifiers are good and logical and not a ‘bad’ API design practice at all. You might as well argue the UNIX file system is a bad design because all the files have a unique inode id so paths are completely unnecessary.
No, the shop id is not in fact part of the globally unique identifier of an Etsy listing, and the properties are not dependent on the shop. Etsy listings have a 1:N relationship with Etsy shops.
The API was a mistake, which they are slowly correcting - they've already changed:
to: ...and I presume they will eventually change the rest of the listing-related endpoints over time.Managing permissions using the hierarchy of a URL is silly at best, dangerous at worst. The first thing any attacker will do is plug in an alternative shop id and see if it grants access to the non-permitted listing. If permissions are attached to the shop (and for Etsy, they are) the server needs to load the listing, figure out the associated shop, and then check permissions. The client cannot be trusted to provide the correct shop id, so there's no point in asking for it.
No plugging in a shop you have permission to doesn’t work if your resources are hierarchical any more than plugging ~/passwd let’s you read /etc/passwd because you have read access to your home directory. Those are different resources and one of them exists and is locked down and the other one doesn’t exist.
Or perhaps what you call silly is just you being unaware of what you don't know. There are valid cases to handle permissions using the structure of URLs. As well, the danger you allude to comes from handling it naively. Even the hypothetical attack you suggest might be among the first thing any non-tech savvy person might think of trying.
The scenario you're describing above is simply one of dealing with redundant information in a situation where inferring the whole from the part is not detrimental (for the platform). A case can certainly be made that with that simplification, some optimization opportunities are also lost. Perhaps Etsy doesn't need them. Others might.
> The client cannot be trusted to provide the correct shop id.
The client cannot be trusted period. If I provide a signed cookie that contains a list of authorized shops and they return something else, good thing that cookie is signed. Also good thing the cookie contains the shops, no need to touch the disk if the URL doesn't match the list.
> [having shop_id in the URL] inevitably causes problems when your invariant changes down the road - say, a listing moves to a different store or can be listed in multiple stores.
Basically the choice is between having a perpetual unique URL to a listing or multiple ones, maybe valid at the same time and some of them maybe invalid in future, when a listing is removed from a shop.
A visitor with a valid unique listing id will always be able to look at the product. If there is a shop id in the URL that URL might become invalid and the visitor loses access to the product and had to search for it again, adding friction. With the global unique is the visitor will discover that the product is offered by another shop (maybe a new one from the same tenant?) which is usually not important.
Permissions for the listing could be handled by matching the shops a user has access to with the shops the listing belongs to.
REST does not mean ‘parameters in the path not in the query string’.
If you are designing a ‘REST API’ you have already committed to ‘purity’. If you follow this guidance you are not designing a REST API you are designing a JSON over HTTP api with parameters in the query string.
There’s no standard. Every REST API looks different. Clients have to refer to documentation anyway, so consistent URL patterns achieve nothing. People waste large amounts of time over totally inconsequential minutiae like whether to use singular or plural words in URLs.
Separating idempotent calls from non-idempotent calls is useful, but REST overcomplicates this. All that’s needed is read and write calls, yet REST has get, post, patch, put, delete…
REST is also inefficient. Clients could read the data they need in one HTTP request, but most “RESTful” APIs force clients to make many requests for the sake of what is essentially aesthetics.
Rest is simpler, but comes at the cost of a lot of nice things like automatic endpoints generation and type verification. The problem is that the heavy tooling tends to not be there or not work correctly. But this is not a win for that kind of simplicity, that's a reason to improve the protocol design.
https://news.ycombinator.com/item?id=38103310#38104983
?
Many computer users work with a canonical version of REST every day, without realizing it. Through a peculiar turn of events, the version of REST which is widely used today is often called “The Web”, and many of its users are not aware that it is basically the REST-ful architecture, defined by Roy Fielding.
There really is a REST, and these people are using it, but it is just a part of The Web they use. REST is the network architecture: hypermedia encodes the state of resources for hypermedia clients. JSON is an essential part of Single Page Applications, but useless by itself; it can only function in the context of a complete API specification. JSON is normally used in combination with SPA libraries: the whole system is basically RPC with JSON added, or JSON/RPC. All these so-called “REST-ful” APIs are really JSON/RPC.
respectfully, https://htmx.org/essays/#hypermedia-and-rest
ie - like 'staff' or 'species' or 'aircraft'.
Then I can add suffix to those singular ie - 'staffList', 'speciesList' etc
Avoid plural nouns in English API endpoints because English is full of irregular plurals. For example:
goose -> geese child -> children index -> indices vertex -> vertexes analysis -> analyses
This makes English plurals unpredictable especially for for non-native speakers and hurts API consistency and discoverability.
Also consider that for a CRUD interface you may need the singular form anyway (POST api/student/create), and adding the plural means doubling the API route namespace.
It's cleaner and simpler to stick with singular nouns.
Also, you should avoid verbs in URLs (IMHO, of course). You're adding to the students collection, so post to students:
If you think `GET /student` is confusing, or more importantly, structurally restrictive as an API, you can think about it as `GET /student/filter` where the "filter" may be a specific student id, or a range of ids, or other conditions such as `GET /student/top` or `GET /student/graduated` and then all students will be just the filter "all" or: `GET /student/all`.
As for `POST /student/create`... it doesn't matter. To use one of Fielding's own examples from his blog, how'd you turn a lamp on and off via REST? Would you be like `POST /lamp`? No. It's unclear WTF is happening.
What I've generally done in these cases is pretty similar to https://cloud.google.com/apis/design/custom_methods which also explains the problem better than I can.
I'd be interested as to how you'd solve some of these problems without an explicit verb in the path.
`GET /staff`
?
So in fact you’re fetching some subset of students anyway, and the size of the returned set might be one or zero depending on your query.
Given that, “GET /student” seems just as meaningful because neither the singular nor the plural can fix the ambiguity about what you’re actually getting.
Why? What's wrong with api/students/create?
> Avoid plural nouns in English API endpoints because English is full of irregular plurals.
I don't buy this. I mean, yes, it's true, but how often do people really need to write these endpoints after initially writing the client code?
URLs refer to a resource that you can manipulate. What resource is /students/create referring to?
GET api/student
POST api/student/create
DELETE api/student
it should be
POST api/students
GET api/students
DELETE api/students
I guess it’s in support of your point, but if you’re going to pluralise index as “indices”, why wouldn’t you use “vertices” for vertex?
* If you’re going to forbid people changing a parameter with a PUT or PATCH request, then the schema for these shouldn’t list them as parameters. This seems to creep in to APIs constantly as people are lazy and will use the same serializer method as for POST with an additional check somewhere in the code that changes the response. Just don’t do it!
* Don’t change the response format based on query parameters. It makes it hard for typed languages to use the API because the client has to handle all of the weird response types you’ve got. Inevitably you end up with more and more getting added and it any client becomes crazily complicated. 99% of the time it’s not worth the bandwidth saving - and if there’s lots of useless information that clients don’t want, it’s worth thinking about whether the API design is right in the first place.
* Stick to one mechanism for doing things. Pagination and sorting behaviour should be the same for all endpoints. The end user doesn’t care that you’re a hip microservices company where teams don’t talk to each other - if the APIs behave weirdly and inconsistently between themselves, it will be hard to use.
What I have seen is endpoints trying to corral their responses into one-size-fits-all schemas in the situation you're describing, with predictable outcomes. Lots of overhead in most situations, tricky documentation, lots of optionals.
Under that premise, I have to say that at least for generic APIs with many differing clients, the idiosyncrasies of typed-language clients would not rank too highly on my list of design considerations — not when they are in the way of simpler, easier to understand responses.
> I have to say that at least for generic APIs with many differing clients, the idiosyncrasies of typed-language clients would not rank too highly on my list of design considerations
Hey, would you like to consume an exchange format that has meaningful distinction between strings and atoms? Those come from the dynamically-typed languages area!
But I feel 410 instead of 404 is pretty controversial:
> There are many layers of software that can return 404 to a request
Anything in your stack can return any HTTP error code - I don't see why 404 is special.
> When calling (say) GET /things/{thing_id} for a thing that doesn't exist, the response should indicate that 1) the server understood your request, and 2) the thing wasn't found. Unfortunately, a 404 response does not guarantee #1.
The server is free to return other codes for other classes of problems. The server could return 400 for a bad request, and leave 404 for "thing wasn't found", indicating it understood the request but it wasn't found.
Also surprised not to see RFC 7807 / RFC 9457 (Problem Details) not mentioned in the "structured error format" section.
I'm surprised you don't - in my experience 404's are by far the most common response to get when you haven't wired things up correctly. Sure anything in the stack _can_ return any code and response they want, but you're still much more unlikely to come across a 410 rather than 404. If that unlikeliness saves you support calls down the line then that's pretty good.
- Did I get a 404 on this endpoint because the endpoint doesn't exist? Or did I get that because the object I was looking for doesn't exist? Great, I need to dig into the response body to find out, indicate that I can either get a 200 or a 404 with this endpoint, and deal with the odd case where the API returns HTML regardless of the MIME type in the Accept header if the endpoint itself is not there because "fuck you, couldn't be bothered".
- Some HTTP libraries will consider anything that's not a 1/2/3xx an error. That can be annoying to deal with.
> You could use 404 but return a custom error body and demand that clients check for a correct error body. This is asking for trouble from lazy client programmers. It might or might not be "your fault" when clients see eventually inconsistent data, but the support calls they send you will be real.
Make sure that your 404 responses were always documented, then tell them to RTFM.
404 is special because it's so incredibly common. Why take the risk? There are other perfectly good error codes that - in practice - don't have this issue.
Or even more simple: Anything other than 200 means check infrastructure docs and if you don't like the 200 check the business requirements.
Of course it's not always like that, some APIs will put both the error and data in a wrapper object and one field or the other will always be null:
This is less annoying but it's still tedious. We could eliminate the wrapper if we only had an out-of-band signal to indicate whether the client should expect a success response or an error response... like maybe an HTTP status code? I mean, it's right there, why not use it?I don't get that one; why is an object with an array property more evolution friendly than an array of objects?
- Optional but supported user defined identifiers, it's so frustrating to work with API that passes you back an identifier.
- String identifier (names) for resources, with some kind of type namespacing, i.e. the prefix in the author's document - Consistent set of fields (create_time, update_time, annotations, ...)
- Avoid dynamic map (this is a JSON self-inflicted wound)
Resource Oriented Design: https://google.aip.dev/121
Declarative Friendly APIs: https://google.aip.dev/128
Declarative friendly makes writing scripts, pipelines so much better because of idempotency. It also pairs very naturally with resource Oriented design.
Long Running Operations: https://google.aip.dev/151
LROs are applicable to any request that runs longer than a second or a couple of seconds. Having a unified interface can be very powerful for implementing offline task workers and pipelines.
Filtering: https://google.aip.dev/160
This one is probably controversial as it's makes implementing basic filtering quite a bit harder. I haven't quite seen the issues it's supposed to solve play out in practice but it's interesting nonetheless.