Build a Terrible API for People You Hate (and What We Can Learn From It)
Like you, I’ve probably worked with people that I hate. You’ve done this, I’ve done this, we’ve all done this. Whether it’s that colleague who microwaves fish in the break room or who takes over a social media company, fills it with unpleasant content, and lays off half the staff, we’ve all encountered people at work we’d rather not have to deal with. It’s just part of professional life.
Now, imagine a scenario: you’re in charge of a team building a ticketing system, and someone you truly despise comes to you and says, “We need an API.” You have no choice. You must build an API for this person you hate. You could be kind, create something well-structured and pleasant, but why do that? Since you hate them, let’s just be mean. Let’s build a truly terrible API for this person.
In this article, we will walk through six steps to build the worst possible API for someone you can’t stand. Ironically, along the way, we might also discover some lessons that can help us build better APIs for the people we like. Let’s dive right in.
Step 1: The Best API Docs are Screenshots Embedded in an Excel Spreadsheet
Yes, you read that right. Forget about elegant, machine-readable specifications. For a truly terrible API, take screenshots of the API specification, embed them inside an Excel spreadsheet, and send that over. Naturally, these screenshots must be wrong, outdated, and completely misleading.
This may sound too absurd to be real, but I’ve witnessed it in the global financial industry—an environment where developers know nothing about the business and business people know nothing about development. In that confusing middle, business analysts who know nothing about either tend to distribute documentation as static images. Screenshots in Word documents or spreadsheets that are never updated are shockingly common. This ensures your hapless consumer will be forever confused.
What would “Nice Jim” do? If I were building an API for someone I didn’t hate, I’d provide an OpenAPI specification. This industry-standard format can be used to generate up-to-date documentation and even client SDKs. You define your endpoints, requests, and responses once, and the docs stay aligned with the code. Easy, maintainable, and helpful—none of which we’re aiming for here.
Step 2: Use GET for Everything
The next step in creating a dreadful API is to ignore HTTP methods’ intended purposes. HTTP verbs like GET, POST, PUT, PATCH, and DELETE exist to communicate meaning. GET retrieves a resource, POST creates one, PUT updates a whole resource, PATCH updates part of a resource, and DELETE removes it. This is standard, well-known stuff.
But we want terrible, so let’s just use GET for everything. Want to create a user? Make a GET request with a body. Update a ticket? GET with a body. Delete something? GET with a body. This completely ignores industry standards and forces anyone using your API to guess what’s happening. Perfect for causing frustration.
If I were being nice, I’d match the request methods to the actions they represent—POST for creating, GET for retrieving, PUT or PATCH for updating, DELETE for removing. That way, developers instantly understand how to interact with the API. But that’s not what we want here.
Step 3: Naming is Hard, So Don’t Bother Doing it Well
Naming things is famously one of the hardest problems in computer science. To make things worse for your hated user, just give your endpoints nonsensical or inconsistent names. How about /ADD_ticket
to add a ticket, /uTicket
for updating a ticket, /AllTickets
in camel case to get all tickets, and then /tikcet
(misspelled) to get a single ticket. Spell them differently in the documentation and in the code. Make sure that no naming convention emerges and that nothing aligns with standard best practices.
Nice Jim wouldn’t do this. Instead, I’d use sensible naming combined with correct HTTP methods so that a single endpoint like /ticket
can handle multiple actions—POST /ticket
to create, GET /ticket/{id}
to retrieve a ticket, etc. That logical consistency makes life easier for everyone, which is exactly what we’re not aiming for here.
Step 4: Put Everything in the Request Body
When retrieving or manipulating resources, it’s standard to use path and query parameters. For example, to get ticket number 14, you’d typically call GET /ticket/14
. To search for tickets, you might do GET /tickets?status=open&assignedTo=me
.
But for our terrible API, let’s ignore all that. Just throw everything into the request body—even the parameters that identify the resource. Want ticket 14? Call GET /ticket
and put ticket_id=14
in the body. Searching for something? Just cram all your search parameters into the body as well. This breaks caching, breaks the mental model of a RESTful API, and confuses everyone.
If I wanted to be nice, I’d use path parameters for IDs and query parameters for filters. That’s standard practice and makes the API predictable. But we’re not being nice.
Step 5: Always Return 200 OK, Even on Errors
HTTP status codes convey important information. 2xx codes mean success, 4xx mean you did something wrong, and 5xx mean the server did something wrong. 404 Not Found, 400 Bad Request, 401 Unauthorized—these are useful indicators that help the client handle errors gracefully.
To really frustrate someone, just return 200 OK for every request. Even if the requested resource doesn’t exist, return 200 and let the user figure out from the body that something’s wrong. Maybe return a JSON body that says {"status": "404", "message": "Ticket not found"}
while still sending a 200 OK response code. This guarantees the client’s code won’t handle errors correctly without a lot of special parsing.
Nice Jim would do the opposite: return proper status codes and define them in the OpenAPI spec. That way, clients know exactly what went wrong. But we’re sticking to terrible.
Step 6: Forget About Atomic Updates
Atomic updates ensure that if something goes wrong partway through a request, the system reverts to the previous stable state. That’s too nice. Instead, let’s break partial updates in the most inconvenient way possible.
Imagine creating a user that requires writing to multiple database tables—User
and UserDetails
. Insert the record into the User
table first. If the request is missing a field needed for UserDetails
, fail the request right after the first insert. Now the User
table has a half-created user without details. When they try again with the correct data, they can’t recreate this user because it’s already partially there, yet incomplete. This might require them to call in a database administrator over the weekend to fix the issue. Perfect chaos.
Nice Jim would do atomic updates: either everything succeeds, or nothing changes. This ensures that errors don’t leave the system in a broken state. But we’re not nice, remember?
What If We’re Actually Nice?
We’ve looked at six ways to build a terrible API for people we hate, but what if we don’t hate them? With a few exceptions, I don’t really hate people. I want to build great APIs. So, let’s turn our six terrible steps into best practices:
- Documentation: Provide an OpenAPI spec and generate helpful docs and SDKs. Keep them up-to-date.
- HTTP Methods: Use the right HTTP methods for each action—GET to retrieve, POST to create, PUT or PATCH to update, DELETE to remove.
- Naming Conventions: Choose clear, meaningful, and consistent names. Let the request methods handle the CRUD logic, rather than creating multiple strangely named endpoints.
- Path and Query Parameters: Use path parameters to identify resources and query parameters for filtering or searching. Don’t put everything in the request body.
- Status Codes: Return the correct status codes. 200 for success, 404 for not found, 400 for bad requests, 401 for unauthorized, 500 for server errors, etc.
- Atomicity: Ensure atomic updates so that partial failures don’t leave the system in a half-baked state.
If we follow these best practices, we’ll end up with an API that’s easy to use, understand, and maintain. That’s what we really want for the people we work with—at least the ones we don’t hate.
About the Author:
I’m Jim Bennett, a principal developer advocate at a company that builds client SDKs for your APIs all over the internet. If you have any questions, feel free to reach out. Just remember: don’t be that person who microwaves fish in the break room.