Quick answer: A 404 that looks like a missing resource is often a routing problem in disguise: the client dropped a base path prefix, or a trailing slash changed which route the server matched. The request is well-formed, the host is reachable, and the resource exists, but the URL string the server receives does not line up with any registered route. The fastest way to diagnose this is to stop guessing and compare the exact request line on the wire against the exact route the server registered, including the prefix, the trailing slash, and any redirect in between. Most of these "mystery 404s" collapse the moment you look at the literal path instead of the path you intended to send.
This is the kind of bug that wastes an hour because every layer looks healthy. DNS resolves, TLS handshakes, the API returns a clean JSON 404 rather than a connection error, and the same endpoint works in a browser or another tool. The difference is hiding in how the path is assembled. A hardcoded base URL in one environment, a gateway that mounts the service under a prefix, and a framework that treats /users and /users/ differently are the usual suspects.
Where the path goes wrong
A few concrete mechanisms account for the majority of these reports, and they are worth checking in order.
| Mechanism | What the client sends | What the server expects |
|---|---|---|
| Dropped base path | /users/12 |
/api/v1/users/12 |
| Double prefix | /api/v1/api/v1/users |
/api/v1/users |
| Trailing slash mismatch | /users/12/ |
/users/12 |
| Slash eaten by join | /api/v1users |
/api/v1/users |
| Redirect strips the method | POST to slashless route | 301 to slashed route |
The double-prefix and the eaten slash are easy to introduce when a base URL already includes a path and the client adds another. The trailing-slash case is subtler because some frameworks transparently redirect, and a redirect on a POST can be downgraded to a GET, silently dropping the request body and producing a confusing failure rather than a clean 404.
Capture the literal request line
Stop reasoning from the variable that holds the URL and capture what actually leaves the machine. A verbose client, a local proxy, or server access logs give you the exact path. Seeing the literal request line is the single most diagnostic step in this whole investigation.
Using new URL() instead of string concatenation removes a whole class of missing-slash bugs, because the constructor normalizes the join. Logging the resulting pathname makes the prefix and trailing slash visible at the point where the request is built.
Pin the base path in one place
The durable fix is configuration hygiene: define the base URL once per environment, including its prefix, and have every request build on it rather than hardcoding fragments. If an API gateway or reverse proxy mounts the service under a prefix, that prefix belongs in the environment configuration, not scattered through individual call sites.
Keeping the base prefix-free of duplicate fragments and passing bare resource paths to the client eliminates both the dropped-prefix and double-prefix failures. The next layer of defense is deciding the trailing-slash policy once and enforcing it at the framework or proxy level so the application does not depend on whatever default a given router happens to choose.
Normalize the trailing slash deliberately
Pick one canonical form and make the server normalize the other, rather than letting mismatches 404. If the framework redirects, ensure it preserves the method and body, or normalize internally without a redirect so POST requests are not downgraded. Document the decision so clients know which form to send.
When the prefix and the slash policy are both pinned down and verified against real request lines, a routing 404 stops being a recurring mystery and becomes a configuration error you can detect in review. The same habit applies to broader contract work: if the OpenAPI spec records the full path including the prefix, generated clients and mocks inherit the correct route and cannot silently drift to a different base.
Frequently asked questions
Why does the same endpoint work in a browser but fail in my client?
A browser often hits a fully qualified URL you typed or followed, while the client builds the path from a configured base. If the base omits the prefix or the joining logic drops a slash, the two produce different paths even though they point at the same host.
How do I tell a routing 404 from a genuinely missing record?
Check the literal path against the registered route. If the route itself is not matched, the same 404 happens for every identifier, including ones you know exist. A missing record returns 404 only for that specific id while the route matches for others.
Can a trailing slash cause something other than a 404?
Yes. If the server issues a redirect, a POST can be downgraded to a GET and lose its body, which surfaces as a method error or an empty request rather than a 404. That is why redirect behavior on write routes needs as much attention as the slash itself.
Should I just add a catch-all redirect to mask the problem?
A blanket rewrite can hide new, real mistakes and makes routing harder to reason about. Prefer fixing the base configuration and one explicit normalization rule, then let genuinely unknown paths return 404 so they remain visible.
How does the OpenAPI spec help prevent this?
Because the spec declares the complete path, including any gateway prefix, generated clients and mock servers use the correct route automatically. Keeping the spec as the source of truth means a base-path change is made once and propagated everywhere.
This is part of the API Debugging Guide — start there for a systematic way to work through failing API requests instead of chasing symptoms.
Top comments (0)