Skip to content

Instantly share code, notes, and snippets.

@loreanvictor
Last active October 27, 2024 09:31
Show Gist options
  • Save loreanvictor/db72f2b1b916705bd5e770914a1e9809 to your computer and use it in GitHub Desktop.
Save loreanvictor/db72f2b1b916705bd5e770914a1e9809 to your computer and use it in GitHub Desktop.

these are some guidelines I find useful for designing good APIs, specifically for third-party consumers.


(ab-)use existing standards

don't reinvent the wheel. don't create new standards. for any new problem, that requires establishing kind of a standard between you and your clients, do this:

  • you might not need a new standard. see if those you use already have a solution.
  • ONLY IF NOT, look for a complementary standard.
    • look for a web or internet standard, if possible.
    • old and boring is good, trendy and shiny is bad.
    • check it if has good tooling and reading material.

implementing older standards can be a hassle, but you are saving time and effort by deferring concerns to people who have already thought a lot about it (security, performance, interoperability, tooling, documentation, etc.).


minimise API surface

each endpoint is going to cost you recurringly. you need to doc it, test it, maintain it, and update it in when the inevitable v-next time comes. keep their number to a minimum, make them composable, so people can do lots of stuff with a few APIs.

  • for new use cases, try to abuse existing APIs for solving them.
  • see which aspects remain unsolved, see if they are worth extending your API surface

this of course increases design cost, but thats an upfront cost vs a running cost of maintaining a larger surface.


keep it crazy composable

minimise how much you limit use of a particular endpoint, whether in terms of context, requirements, etc. don't just think of one use case and provide a solution only for that. cherish giving your users new tools and be amazed what they can do with them.

combined with a minimal API surface, this also neccesitates making your endpoints as atomic as possible. each of them should do a really specific and relatively small thing, and they all should combine to allow building greater stuff.


provide least power

your API consumers are evil idiots. they'll shoot themselves (and your users) in the foot at every possible turn. don't give them the gun when they need a screwdriver. keep their power / privileges / authority at the minimum level they need for what they need to do.

  • keep your authorisation levels granular. use granular OAuth scopes for example.
  • expire your keys and tokens routinely.
  • make your endpoints atomic. they should do one simple thing, and that's it.

bake in security and privacy

don't trust your API consumers, they'll tweet keys, tokens, etc. and don't even notice. give them data storage, and they'll store super sensitive private data of people you don't even want to know how they gathered. don't let them.

  • expire keys and tokens periodically.
  • generally revoke access periodically, check if someone is still behind the wheel.
  • as with the rule of least power: keep access to resources granular, and give everyone the least access they need at any time, only for that time.
  • don't accept data unless you somehow clean it (for example, hash it).

mind what you promise

your APIs are like promises you make to their consumers. keep these promises minimal. more importantly, you might be promising things implicitly without knowing: if a field always looks like a phone number, consumers will assume it will always be a phone number. if two fields on two different endpoints are the same, consumers will assume they are the same thing.

each promise is a coupling. its an increase in API surface, withou you even realising. always double-check what you are promising, and don't make promises you don't really need to.


make it elegant

your endpoints are a user interface. look at them, see if you'd enjoy or would you dread using them. don't make something people will dread, make something people would be delighted to use.

  • make them curl-friendly as possible.
    • are they long and hard to type? simplify.
    • are they confusing and not human readable? redisgn.
    • are they mistake prone because of weird stuff I have to add to them? remove them.
    • do I need to send massively complicated JSON bodies that is tough to type in the terminal? minimise them.
  • provide stable endpoints
    • I should (ideally) get the same results when I call the same endpoint with the same parameters
    • this means keep them idempotent as possible
    • this means avoid state as much as you can
  • think of debugging
    • don't hide critical info in request headers, its harder to access
    • provide good and concise errors. help me fix my mistake
  • don't create footguns
    • use simple and distinct names for various endpoints
    • keep parameters minimal and simple as possible
    • keep functionality of each endpoint clear and simple
    • avoid unintended or weird consequences / side effects

don't change things

you can't change your APIs every day if you expect third-parties to consume them and build useful things on top of them. you can change how your endpoints and APIs work in the background, but not their surface.

  • design in a way that you can keep the surface
  • try to predict some near / mid term changes. be a cynic on this. then design against it.
  • establish a clear and concise deprecation process.
    • if something is experimental, make it so obvious that no consumer can miss it.
    • if something is experimental, make it so that the experimental version can't be used when you remove it or make it stable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment