previously, https://cohost.org/tef/post/219290-api-design-mistakes
with programming, there isn't so much a right way to do things, but there are an awful lot of bad ways, and the best you can do is try and fall into a new mistake than repeat the same ones, over and over.
then again, when you make the same mistake over and over, you call it a best practice, and sometimes doing the right thing ends up being the wrong choice, because no-one understands why you're doing it.
welcome to hell, no matter what you do, someone will tell you it's wrong.
this is yet another post where i present my opinions and tradeoffs in design as near absolutes, but hopefully i'll explain the reasoning behind them. i don't expect you to follow my dogma, but i do hope that you'll understand the consequences of your own api choices a little better.
honestly, most of this just comes down to taste
just put a prefix in. a date's a good prefix
i've witnessed a number of apis that start off with "/api/action" or "/api/resource" and it often feels like a good start, as the calls to the api are split off from the regular website pages.
the problem? when you need another api (and you will, trust me), you end up going "/api/new_resources" or perhaps passing in a parameter "/api/resource?include=.." or just going "/api/2/resource" for the new things, and "/api/resource" for the old things.
when you create a new api endpoint, you aren't exactly thinking about replacing it down the line, but odds are you'll end up doing that anyway.
it's very much a small detail, but i would strongly recommend using a proper prefix: not just "/api", not just "/api/v1/" but something like "/api/2022-12/"
as to why a date? it's a little bit more useful than a version number. you can see how old something is at a glance.
don't share endpoints
another temptation to avoid "duplicating code" is to re-use urls. instead of creating all new api endpoints under "/api-2023/resource", it's less work to just let clients call the 2023 things for the new stuff, and the 2022 things for the old stuff.
i would politely suggest that this is a terrible idea.
the main reason? it makes it hard to deprecate endpoints. you don't exactly know in the codebase which old ones are part of the new api. it can also make access control a little more frustrating, too.
if really isn't that much more code to add in a "/api/2022/resource" that calls the older version of code, and it is a lot clearer.
don't share internal apis
if you're running a website, you might have some api calls already created for your website, and it can be very tempting to just expose and document them for third party clients.
on one hand, it's a lot less work, but on the other, you will end up breaking third party clients when you change your internal software. if you have an internal api, don't make your life harder by letting the unwashed masses depend on it.
once again, having "/api/web/resource" call the same code behind the scenes isn't really that much work.
and the payoff is huge.
don't share apis
sometimes it's best to have one api endpoint per client, for the same reasons you benefit from having an internal and external api. coupling.
if you have "/api/vendor/...." for some specific piece of software, you can then work around problems in that one particular client as and when they appear.
there's at least one large website that does streaming video, with lots of first-party applications for different platforms. they decided to go with a unique namespace for each platform so they could do last minute changes without breaking every other application that used the api.
namespaces are honestly pretty great.
tell your clients to wait
let's say you have an api endpoint that creates a thing, but the thing takes a while to create.
you might write an api where you POST "/api/2022/make_thing" to get an id, then poll GET "/api/2022/things/id" until the status appears, and frankly, it isn't that nasty, but one thing you might consider is having a special "Waiter" response.
you POST to create, and get back some json {"url":...., "wait_seconds":30, "state": "waiting"}, then you do a GET on the url it gives you, after the wait time it gives you, in a loop, until you get the response you want.
something like this:
x = api.action(....)
while not x.ready():
time.sleep(x.wait_seconds)
x = get(x.url)
the nice thing is, you can put this boilerplate in your client library, and use it on every api call.
if you were feeling fancy, you could force busy clients to wait after exceeding rate limits. if you're feeling opulent, you could even adjust the wait time to reflect your system load.
sometimes, a list is just a list
another temptation is to use unique response types for every api action. the tools make this easy, but it isn't always a benefit to the people consuming your api.
like with the generic "waiter" above, you can also implement generic container responses. i won't go into too much detail here, hopefully you get the idea.
having standard shared container types, response types, well, it makes the client developer's life a lot easier.
that's all for now
i've designed a lot of apis, made a lot of mistakes, but if anything i've learned the most from suffering under other people's choices. api design is absolutely one of those things where the right thing is impossible, but the wrong thing will haunt you for a lifetime.
