Quick Answer: What are the most common mistakes when using rest apis in production?
The most common mistakes fall into a handful of buckets: retrying requests without idempotency keys, ignoring rate-limit responses, skipping timeouts and backoff caps, shipping breaking changes without versioning, leaking internal details in error messages, and leaving object-level authorization unchecked. Most of these show up in client code, not the server, which is why they survive code review for months.
Reliability errors and security errors are different problems with different fixes. A duplicate-charge bug and a broken object-level authorization flaw can both live in the same endpoint, but one needs retry discipline and the other needs a permission check.
Before shipping, teams should check retry policy, timeout values, backoff caps, versioning scheme, error format, authorization checks per object, pagination limits, cache headers, and per-endpoint metrics.
In 2012, the engineers behind HTTP added a status code that most API clients still ignore. RFC 6585 introduced 429 Too Many Requests and the `Retry-After` header, giving servers a polite way to say “slow down.” More than a decade later, a huge share of production incidents still trace back to clients that treat a 429 as a temporary glitch and hammer the endpoint again immediately. That single pattern captures the strange truth about REST APIs: the protocol is simple, the spec is public, and the mistakes are still remarkably consistent across teams of every size. (Vietnam APIs Breach Unknown Operator 2026: What We Know) (HTTP documentation)
This guide walks through the errors that show up again and again in production systems, why they happen, and what actually works when you fix them.
Retrying Without Idempotency Keys

Networks fail. Connections drop mid-request. A client sends a POST to create an order, the response never arrives, and the client retries. Now there are two orders. The customer is charged twice, and support spends the afternoon issuing refunds.
Idempotency keys solve this. The client generates a unique key per logical operation and sends it with every retry. The server records the key and the result, so a repeated request returns the original response instead of creating a second resource. Stripe popularized this pattern for payments, and it generalizes to any non-idempotent operation: account creation, emails, job submission, inventory reservation.
The practical mistake is subtler than “forgot the key.” Teams often generate a new key on each retry attempt, which defeats the entire mechanism. The key must be stable across retries of the same operation and unique across different operations. Store it before the first attempt, not after a failure.
A related trap: treating GET, PUT, and DELETE as automatically safe. GET and DELETE are idempotent by definition, but PUT only stays idempotent if the payload is a full replacement. Partial updates sent via PUT can produce different results depending on how many times they land.
Ignoring Rate Limits and Retry-After
When a server returns 429 with `Retry-After: 30`, it is telling the client exactly how long to wait. Clients that ignore this header and retry in a tight loop turn a brief throttle into an outage. The server’s queue fills, legitimate traffic gets starved, and the whole system degrades.
What works in practice is capped exponential backoff with jitter. Wait a short interval, double it on each failure, stop at a ceiling, and add a random offset so that a thousand clients don’t all retry on the same millisecond. Respect `Retry-After` when it’s present, and treat it as a floor rather than a suggestion.
Common mistakes here include:
- Retrying only on 5xx and ignoring 429 entirely
- Retrying indefinitely with no cap, so a dead dependency pins threads forever
- Using fixed-interval retries that synchronize across the fleet
- Retrying non-retryable errors like 400 or 401, which will never succeed
No Timeouts, No Backoff Caps, Cascading Failures
A client without a timeout will wait forever for a response that may never come. Multiply that by a few hundred concurrent requests and the connection pool is exhausted. Now every other call to that dependency fails too, including the ones that would have succeeded. This is the classic cascading failure, and it usually starts with one missing timeout setting. (Representational state transfer)
The fix is boring and effective: set connect and read timeouts on every client, cap total retry duration, use circuit breakers to stop calling a dependency that is clearly down, and fail fast with a clear error rather than hanging. The Google SRE guidance on handling overload covers this territory well, and the core idea is simple. A slow dependency should degrade one feature, not the whole product.
The most dangerous timeout is the one nobody set because the default looked reasonable. Many HTTP libraries default to no timeout at all.
Breaking Changes Shipped Without Versioning
An API is a contract. When a field is renamed, a type changes, or an endpoint disappears, every consumer that depended on the old shape breaks. Teams that ship these changes without a versioning scheme create a slow-motion outage that surfaces days later in someone else’s logs.
Two approaches dominate. URL path versioning (`/v1/`, `/v2/`) is explicit and easy to route. Media-type or header versioning keeps URLs stable but hides the version in request headers, which makes debugging harder. Date-based versions, where each API key is pinned to a snapshot, offer finer control but demand more infrastructure.
Whichever scheme you pick, the practical rules are the same. Additive changes (new optional fields, new endpoints) are safe. Removals, renames, and type changes are not. Deprecate loudly, publish timelines, and monitor which consumers still call old versions before you turn them off. A versioning scheme nobody follows is worse than none, because it creates false confidence.
Leaking Internal Details in Error Responses
A stack trace in a JSON error body is a gift to an attacker. It reveals framework versions, file paths, database names, and sometimes query fragments. The OWASP API Security Top 10 flags security misconfiguration as a top risk, and verbose error output is one of the most common forms of it.
Good error handling returns a stable, documented shape: an error code, a human-readable message, and a correlation ID that support can use to find the full details in internal logs. The correlation ID is the key move. It lets you debug without exposing anything to the caller. RFC 9457 standardizes this as Problem Details for HTTP APIs, and adopting it saves you from inventing a new error schema for every service. (Taco Tuesday Deals: Find Local Restaurant Specials)
Watch for these leaks in particular:
- Full exception messages passed straight through to the response
- Debug endpoints left enabled in production
- Database constraint names or SQL fragments in validation errors
- Internal hostnames or IP addresses in redirect and error payloads
Broken Object-Level Authorization
The single most common API security flaw is broken object-level authorization, often shortened to BOLA. It happens when an endpoint accepts an object ID and returns the object without checking whether the caller is allowed to see it. `GET /invoices/1234` returns invoice 1234 to anyone who asks, because the code checked that the user was logged in but never checked that the invoice belonged to them.
The fix is unglamorous: authorize every object access against the authenticated principal, and do not limit that check to the route. Do it in one place, such as a data-access layer, rather than scattering checks across handlers where one will eventually be forgotten. Write tests that attempt cross-tenant access and expect a 403 or 404. OWASP ranks BOLA as the number one API security risk; it earns that spot because it is easy to introduce and hard to spot in review.
Missing or Broken Pagination
An endpoint that returns every row in a table works fine in development with fifty records. In production with five million, it times out, exhausts memory, and takes the database down with it. Cursor-based pagination with a hard maximum page size prevents this. Offset pagination works for small datasets but degrades badly at scale and produces inconsistent results when rows are inserted mid-scroll.
The mistake to avoid is making the limit optional with no default. Set a sane default (50 or 100), enforce a maximum, and document how clients should follow the cursor. If you’re building anything that lists resources, this belongs in the first draft, not in a later hardening pass. Teams that get this wrong often discover it during a traffic spike, which is the worst possible time.
Caching Mistakes That Bite Later
HTTP caching is powerful and easy to misconfigure. Two failures dominate. The first is missing `Cache-Control` and `ETag` headers, so every request hits the origin even when nothing changed. The second is worse: caching authenticated responses in a shared cache, which can serve one user’s data to another.
RFC 9111 defines the caching semantics, and the practical rules are short. Mark private data `Cache-Control: private, no-store`. Use `ETag` for conditional requests so clients can revalidate cheaply. Never cache responses keyed on a session cookie in a shared layer. And be explicit about `Vary` headers when content depends on `Accept` or `Accept-Encoding`.
Caching bugs are quiet. They don’t throw errors; they just serve stale or wrong data until someone notices.
No Observability on API Calls
When an endpoint slows down at 2 a.m., the team needs to answer three questions fast: which endpoint, which caller, and how slow. Without request IDs, structured logs, and per-endpoint latency and error-rate metrics, that investigation turns into guesswork. Attach a correlation ID to every request, propagate it across service boundaries, and log it on both success and failure. Track p50, p95, and p99 latency per route, plus error rates broken out by status code. The difference between a ten-minute fix and a two-hour outage is usually whether that data existed before the incident started. (Openai employee arrested hidden listening device 2026)
FAQ
How do I know if my API retries are safe?
Check whether every non-idempotent operation accepts an idempotency key, whether the client reuses the same key across retries of one operation, and whether retries are capped. If a duplicate request could create a second order, charge, or email, the retry logic is not safe yet.
Is versioning in the URL path better than headers?
Path versioning is easier to route, cache, and debug because the version is visible in logs. Header versioning keeps URLs stable but hides the version from casual inspection. Either works if it’s applied consistently; the failure mode is having a scheme that consumers ignore.
What status code should an API return for a permission failure?
Return 403 when the caller is authenticated but lacks permission, and 404 when revealing the object’s existence would itself leak information. For cross-tenant access attempts, many teams return 404 deliberately so an attacker can’t enumerate valid IDs.
How large should a default page size be?
Most production APIs default to somewhere between 20 and 100 items per page, with a hard maximum that the server enforces regardless of what the client requests. The right number depends on payload size; a page of large documents should be smaller than a page of IDs.
Should error responses include the exception message?
No. Return a stable error code, a short user-facing message, and a correlation ID. Keep the exception details in internal logs where they can be retrieved by that ID. Stack traces and database errors should never reach the client.
How often should API metrics be reviewed?
Latency and error-rate dashboards should be checked continuously with alerting on thresholds, and reviewed as a team weekly. Per-endpoint breakdowns matter most, because aggregate numbers hide the one route that is quietly failing for a subset of callers.
Conclusion
The mistakes that hurt most in production are rarely exotic. They are missing idempotency keys, ignored `Retry-After` headers, absent timeouts, unversioned breaking changes, verbose errors, unchecked object authorization, unbounded pagination, sloppy caching, and blind spots in observability. Each one is well documented, each has a known fix, and each still shows up in codebases that ship every day.
The pattern behind all of them is the same: the happy path works in development, and the failure path was never designed. A useful exercise is to walk every endpoint and ask what happens when the network drops, the dependency slows, the caller retries, and the user asks for someone else’s data. Teams that answer those four questions before launch spend far less time answering them at 2 a.m. afterward. For related coverage of how API incidents play out in the real world, the analysis of the Vietnam APIs breach involving an unknown operator is worth a read, as is the report on the OpenAI employee arrested over a hidden listening device.


