01 logo

Don’t Fix the Misspelling: The Ten Sticky Notes That Taught Me API Design

Alex Carter removed a deprecated field and learned the hard way that the best engineering is invisible—and never breaks userspace.

By JinPublished 4 months ago • 7 min read

Alex Carter slammed his coffee mug onto the desk, hard enough to make the keyboard membrane jump. Seventeen unread messages hung on Slack, all from the same client, Orion, a legacy logistics company. Screenshots of error after error. Their ERP system had collapsed that morning because the new version of ShipTrack API Alex had shipped last week quietly removed a field that “nobody ever used”: legacy_carrier_code.

He thought nobody used it. The docs clearly marked it “deprecated.” Three rounds of emails had gone out. But Orion’s integration code was written eight years ago, and that field sat at the bottom layer of their accounting module. Removing it was like pulling a brick from an old building’s wall; the whole wall came down.

  1. Boring Is a Virtue

Three years earlier, when Alex first took over ShipTrack, he kept thinking the API was too “boring.” Endpoint names stated exactly what they did: /shipments, /track, /labels. Field names read like a checklist: shipment_id, status, created_at. No surprises. An intern could fire their first successful request in under two hours.

Back then, he believed good design should be clever, elegant, and forward-looking. So he started “optimizing”: changing shipment_id to shipmentId, expanding status into a nested object, merging several low-traffic endpoints into a single “all-powerful” /query. At an internal tech talk, he said, “We’re going to modernize the API.”

Then Orion’s architect, Mark Hendricks, spent twenty minutes cursing him out over the phone. Mark’s exact words: “You know what, Alex? We don’t want modernization. We want the thing that still worked this morning.”

That night Alex pulled out an old book on the Linux kernel and found a line highlighted in yellow: WE DO NOT BREAK USERSPACE.

He copied the sentence onto a sticky note and pressed it to the edge of his monitor. The first virtue of an API is familiarity. So familiar that a developer, without opening the docs, can write their first call with their eyes closed. That kind of “boring” isn’t poverty. It’s a promise.

  1. Versioning Is the Last Bullet

After the Orion incident, Alex added legacy_carrier_code back exactly as it was, and even wrote a unit test with a comment that said: “This field is misspelled. Do not fix.”

His colleague Maya Patel asked, “If you really want to clean up the historical baggage, why not release a v2? Old users stay on v1, new users onboard with v2. Best of both worlds.”

Alex thought it over. Versioning was indeed the industry’s respectable approach—Stripe put version numbers in request headers, OpenAI hung /v1 on its URLs. But Maya maintained thirty endpoints. Thirty endpoints times two versions meant sixty—sixty test suites, sixty sets of documentation, sixty variants that would drive the support team insane.

“Versioning is a necessary evil,” he said, “but not now. What we owe right now isn’t a new version. It’s fixing v1 until no one wants to leave it.”

Never add a version unless you have no other choice. He wrote that line into the team wiki and drew two red lines underneath.

  1. Nobody Pays for a Pretty Interface

The following month, at a company strategy meeting, the VP asked a question that silenced the room: “Our competitor Flexport’s API is a train wreck. Why do they still have more customers than us?”

Alex almost blurted out, “Because their core product is better”—and then he realized that was the answer.

Nobody pays for an elegant API. Developers choose Twilio because of message delivery rate; Stripe, because settlements don’t lose a cent; OpenAI, because the model can reason. The API is just the pipe. As long as the water is clean and steady, no one cares how many bends the pipe takes. Conversely, if the product logic is a mess, a beautiful API is just a ribbon tied around a tangled knot.

He went back and canceled the sprint planned for “interface aesthetic improvements,” pouring all that time into fixing the occasional sync delay on customs declaration forms. Product value always runs ahead of API design.

  1. The Key Has to Be Light

That same week, a salesperson named Sophie Martinez came to his desk. Sophie wasn’t an engineer, but she wanted to write her own script to pull ShipTrack data into Google Sheets for a client demo.

“How do I handle authentication?” she asked.

Maya started in: “We have an OAuth 2.0 flow. You register an app, get a client_id and client_secret, and then—”

Sophie’s eyes had already glazed over.

Alex pulled up a chair. “Here,” he said. “Go to the settings page, click ‘Generate API Key.’ Save this string, put it in your request header. Done.”

Sophie had her first successful request in fifteen minutes. The look on her face was that of someone who had never written code in her life, seeing a machine answer her for the first time.

That night, Alex added a line to the team docs: API Key authentication must be supported. Not optional. Mandatory. OAuth is for the production systems that need it, but the first key should always be light enough to pick up with one hand.

  1. One Payment, Never Twice

A week before Christmas, disaster struck. A payment endpoint, /invoices/pay, timed out on the network side. The client retried three times. The backend had no idempotency handling, and the same freight charge was deducted four times on the same morning. The client’s finance department phoned the CEO’s office directly.

Alex didn’t sleep that night. He and Maya built an idempotency-key table in Redis. The mechanism was simple: every client request carries an Idempotency-Key. The server checks whether that key has already been processed. Processed? Return the original success result, don’t execute again.

The next day, they shipped the patch. Maya wrote in the release notes: “From now on, the same request retried however many times deducts only once. On timeout, retry without fear.”

GET is safe by nature. DELETE once or twice yields the same result. Only POST is dangerous. And /invoices/pay was a POST. Alex added another line to his sticky note: Every write operation carries an idempotency key.

  1. The Mad Script Users

In January, a third-party logistics platform started hammering /shipments/list at two in the morning—every thirty seconds, no caching, no throttling, polling all night long. Database CPU spiked to ninety-nine percent.

Alex called the platform’s CTO. The man said, “Oh, we wrote a health-check script to see if there are new orders every half minute.”

“You’re using a list endpoint as a heartbeat?” Alex nearly crushed his coffee mug.

He hung up, turned the rate-limiting switch to ON. He put a hard cap of one hundred requests per minute on /shipments/list, stuffed X-RateLimit-Remaining into the response headers, and built an emergency circuit-breaker kill switch into the admin panel for every high-cost endpoint.

“Once an API is released,” he said at the postmortem, “people will use it to do anything you never dreamed of. Your job isn’t to get angry. It’s to build the fence before they start running.”

  1. Beyond the Hundred-Thousandth Page

Mark Hendricks from Orion emailed again. This time it wasn’t an error—it was a request. Their historical data had grown to tens of millions of records, and pulling the /shipments list was unbearably slow.

Alex switched from offset pagination to cursor-based pagination. Instead of forcing the database to “skip the first hundred thousand rows,” the server remembered the id of the last record and continued from there. The response body always carried next_cursor, so the client never had to hand-assemble the next page URL.

On test day, Mark replied on Slack with a single word: “Faster.”

Alex screenshotted the reply and saved it in a folder on his desktop called “Wins.”

  1. Don’t Stuff Everything in at Once

When the team started designing a new /carriers endpoint, Maya proposed embedding every carrier’s compliance documents, insurance contracts, and historical performance metrics into the default response. Alex stopped her.

“Those fields are expensive to compute,” he said. “How many users need compliance docs every time they fetch the carrier list? Five percent? Make it off by default. Load it only when they pass ?include=compliance.”

They ended up with an optional fields array: includes[]=compliance&includes[]=performance. Sophie’s sales script wouldn’t waste a single extra millisecond because of it.

  1. Internal Walls

In February, an internal admin tool needed to call an internal API to batch-update shipment statuses. The internal team asked, “Can we skip auth and rate limiting? It’s just us.”

Alex refused, flat out. “An internal API is still an API. Idempotency, rate limiting, circuit breakers—not one missing.” He knew too well that internal scripts run even more recklessly than external ones. Someone would write a call with no backoff inside a for loop, then walk out to lunch.

The only difference: internal users could yell at him directly on Slack. He wrote the internal API docs just as thoroughly as the external ones, with one extra line: “If something breaks, come to me. Don’t change the code.”

  1. Back to the Beginning

By spring, ShipTrack’s average API uptime had climbed to 99.97%. Orion hadn’t reported a single new failure; Mark sent a bottle of whiskey to the office. Sophie’s Google Sheets script had been running for six months without a hitch. The platform that had been polling at 2 a.m. had gone quiet—they’d finally learned to read the rate-limit headers.

Alex sat at his desk. The sticky notes on his monitor had multiplied from one to five:

Never break userspace.

Versioning is the last bullet.

If the product is broken, no pretty API saves it.

Write operations carry an idempotency key, so retries are safe.

Build the fence before the running starts.

Maya leaned over. “What do you think a good API is now?”

Alex thought for a moment. “It’s a road the other person can walk with their eyes closed. No potholes, no sudden turns, streetlights always on. It’s so boring no one notices it, but once they’ve walked it, they never want to switch.”

Outside the window, the first spring rain began to fall. He set his desktop background to a screenshot of that HTTP header misspelled twenty years ago that nobody had dared fix: Referer. Not a joke; a monument. A monument to a promise.

startuptech newsapps

About the Creator

Jin

Writer of reamstories

https://reamstories.com/jin

Enjoyed the story? Support the Creator.

Subscribe for free to receive all their stories in your feed. You could also become a paid subscriber, letting them know you appreciate their work.

Subscribe For Free

Reader insights

Comments

There are no comments for this story

Be the first to respond and start the conversation.

Sign in to comment
    Written by Jin