Everything I know about good API design
59 comments
·August 24, 2025dwattttt
The reminder to "never break userspace" is good, but people never bring up the other half of that statement: "we can and will break kernel APIs without warning".
It illustrates that the reminder isn't "never change an API in a way that breaks someone", it's the more nuanced "declare what's stable, and never break those".
delta_p_delta_x
Even if the kernel doesn't break userspace, GNU libc does, all the time, so the net effect is that Linux userspace is broken regardless of the kernel maintainers' efforts. Put simply, programs and libraries compiled on/for newer libc are ABI-incompatible or straight-up do not run on older libc, so everything needs to be upgraded in lockstep.
It is a bit ironic and a little funny that Windows solved this problem a couple decades ago with redistributables.
Retr0id
otoh staticly-linked executables are incredibly stable - it's nice to have that option.
delta_p_delta_x
From what I understand, statically linking in GNU's libc.a without releasing source code is a violation of LGPL. Which would break maybe 95% of companies out there running proprietary software on Linux.
musl libc has a more permissive licence, but I hear it performs worse than GNU libc. One can hope for LLVM libc[1] so the entire toolchain would become Clang/LLVM, from the compiler driver to the C/C++ standard libraries. And then it'd be nice to whole-program-optimise from user code all the way to the libc implementation, rip through dead code, and collapse binary sizes.
chubot
Yeah, famously there is no stable public driver API for Linux, which I believe was the motivation for Google’s Fuschia OS
So Linux is opinionated in both directions - towards user space and toward hardware - but in the opposite way
pixl97
While the author doesn't seem to like version based APIs very much, I always recommend baking them in from the very start of your application.
You cannot predict the future and chances are there will be some breaking change forced upon you by someone or something out of your control.
paulhodge
I have to agree with the author about not adding "v1" since it's rarely useful.
What actually happens as the API grows-
First, the team extends the existing endpoints as much as possible, adding new fields/options without breaking compatibility.
Then, once they need to have backwards-incompatible operations, it's more likely that they will also want to revisit the endpoint naming too, so they'll just create new endpoints with new names. (instead of naming anything "v2").
Then, if the entire API needs to be reworked, it's more likely that the team will just decide to deprecate the entire service/API, and then launch a new and better service with a different name to replace it.
So in the end, it's really rare that any endpoints ever have "/v2" in the name. I've been in the industry 25 years and only once have I seen a service that had a "/v2" to go with its "/v1".
ks2048
> So in the end, it's really rare that any endpoints ever have "/v2" in the name.
This is an interesting empirical question - take the 100 most used HTTP APIs and see what they do for backward-incompatible changes and see what versions are available. Maybe an LLM could figure this out.
I've been just using the Dropbox API and it is, sure enough, on "v2". (although they save you a character in the URL by prefixing "/2/").
Interesting to see some of the choices in v1->v2,
https://www.dropbox.com/developers/reference/migration-guide
They use a spec language they developed called stone (https://github.com/dropbox/stone).
andix
I don't see any harm in adding versioning later. Let's say your api is /api/posts, then the next version is simply /api/v2/posts.
choult
It's a problem downstream. Integrators weren't forced to include a version number for v1, so the rework overhead to use v2 will be higher than if it was present in your scheme to begin.
pixl97
This here, it's way easier to grep a file for /v1/ and show all the api endpoints then ensure you haven't missed something.
claw-el
If there is a breaking change forced upon in the future, can’t we use a different name for the function?
ks2048
If you only break one or two functions, it seems ok. But, some change in a core data type could break everything, so adding a prefix "/v2/" would probably be cleaner.
Bjartr
See the many "Ex" variations of many functions in the Win32 API for examples of exactly that!
pixl97
Discoverability.
/v1/downloadFile
/v2/downloadFile
Is much easier to check for a v3 then
/api/downloadFile
/api/downloadFileOver2gb
/api/downloadSignedFile
Etc. Etc.
soulofmischief
A versioned API allows for you to ensure a given version has one way to do things and not 5, 4 of which are no longer supported but can't be removed. You can drop old weight without messing up legacy systems.
CharlesW
You could, but it just radically increases complexity in comparison to "version" knob in a URI, media type, or header.
jahewson
/api/postsFinalFinalV2Copy1-2025(1)ExtraFixed
null
swagasaurus-rex
Cursor based pagination was mentioned. It has another useful feature: If items have been added between when a user loads the page and hits the next button, index based pagination will give you some already viewed items from the previous page.
Cursor based pagination (using the ID of the last object on the previous page) will give you a new list of items that haven't been viewed. This is helpful for infinite scrolling.
The downside to cursor based pagination is that it's hard to build a jump to page N button.
0xbadcafebee
Most people who see "API" today only think "it's a web app I send a request to, and I pass some arguments and set some headers, then check some settings from the returned headers, then parse some returned data."
But "API" means "Application Programming Interface". It was originally for application programs, which were... programs with user interfaces! It comes from the 1940's originally, and wasn't referred to for much else until 1990. APIs have existed for over 80 years. Books and papers have been published on the subject that are older than many of the people reading this text right now.
What might've those older APIs been like? What were they working with? What was their purpose? How did those programmers solve their problems? How might that be relevant to you?
null
runroader
I think the only thing here that I don't agree with is that internal users are just users. Yes, they may be more technical - or likely other programmers, but they're busy too. Often they're building their own thing and don't have the time or ability to deal with your API churning.
If at all possible, take your time and dog-food your API before opening it up to others. Once it's opened, you're stuck and need to respect the "never break userspace" contract.
devmor
I think versioning still helps solve this problem.
There’s a lot of things you can do with internal users to prevent causing a burden though - often the most helpful one is just collaborating on the spec and making the working copy available to stakeholders. Even if it’s a living document, letting them have a frame of reference can be very helpful (as long as your office politics prevent them from causing issues for you over parts in progress they do not like.)
frabonacci
The reminder to "never break userspace" is gold and often overlooked.. ahem Spotify, Reddit and Twitter come to mind.
claw-el
> However, a technically-poor product can make it nearly impossible to build an elegant API. That’s because API design usually tracks the “basic resources” of a product (for instance, Jira’s resources would be issues, projects, users and so on). When those resources are set up awkwardly, that makes the API awkward as well.
One issue I have with weird resources are those that feel like unnecessary abstraction. It makes it hard for the human to read and understand intuitively, especially someone new to these set of APIs. Also, it makes it so much harder to troubleshoot during an incident.
wener
I still try think /v1 /v2 is a break, I don't trust you will keep v1 forever, otherwise you'll never introduce this execuse.
I'd like to introduce more fields or flags to control the behavior as params, not asking user to change the whole base url for single new API.
calrain
I like this pattern.
When an API commits to /v1 it doesn't mean it will deprecate /v1 when /v2 or /v3 come out, it just means we're committing to supporting older URI strategies and responses.
/v2 and /v3 give you that flexibility to improve without affecting existing customers.
mlhpdx
Having built a bunch of low level network APIs I think the author hits on some good, common themes.
Versioning, etc. matter (or don’t) for binary UDP APIs (aka protocols) just as much as for any web API.
zahlman
Anyone else old enough to remember when "API" also meant something that had nothing to do with sending and receiving JSON over HTTP? In some cases, you could even make something that your users would install locally, and use without needing an Internet connection.
drdaeman
I believe it’s pretty common to e.g. call libraries’ and frameworks’ user- (developer-) facing interface an API, like in “Python’s logging library has a weird-looking API”, so I don’t think API had eroded to mean only networked ones.
mettamage
I never understood why libraries also had the word API. From my understanding a library is a set of functions specific to a certain domain, such as a statistics library, for example. Then why would you need the word API? You already know it’s a library.
For end points it’s a bit different. You don’t know what are they or user facing or programmer facing.
I wonder if someone has a good take on this. I’m curious to learn.
dfee
To use code, you need an interface. One for programming. Specifically to build an application.
Why does the type of I/O boundary matter?
shortrounddev2
To me the API is the function prototypes. The DLL is the library
ivanjermakov
> sending and receiving JSON over HTTP
In my circles this is usually (perhaps incorrectly) called REST API.
chubot
Well it stands for “application programming interface”, so I think it is valid to apply it to in-process interfaces as well as between-process interfaces
Some applications live in a single process, while others span processes and machines. There are clear differences, but also enough in common to speak of “APIs” for both
null
gct
Everyone's decided that writing regular software to run locally on a computer is the weird case and so it has to be called "local first".
null
rogerthis
Things would come in SDKs, and docs were in MS Help .chm files.
j45
APIs are for providing accessibility - to provide access to interactions and data inside an application from the outside.
The format and protocol of communication was never fixed.
In addition to the rest api’s of today, soap, wsdl, web sockets could all can deliver some form of API.
> How should you store the key? I’ve seen people store it in some durable, resource-specific way (e.g. as a column on the comments table), but I don’t think that’s strictly necessary. The easiest way is to put them in Redis or some similar key/value store (with the idempotency key as the key).
I'm not sure how would storing a key in Redis achieve idempotency in all failure cases. What's the algorithm? Imagine a server handling the request is doing a conditional write (like SET key 1 NX), and sees that the key is already stored. What then, skip creating a comment? Can't assume that the comment had been created before, since the process could have been killed in-between storing the key in Redis and actually creating the comment in the database.
An attempt to store idempotency key needs to be atomically committed (and rolled back in case it's unsuccessful) together with the operation payload, i.e. it always has to be a resource-specific id. For all intents and purposes, the idempotency key is the ID of the operation (request) being executed, be it "comment creation" or "comment update".