I work on OnePassApps, so this is our own API host, and every number below was measured today from outside the browser, signed out, over plain HTTPS, on 10 November 2026.
We have written about the method contract of our document host, where a write is refused with 405 and an empty body, and about the JSON envelope our API host hands back. This post is the part neither one covered: what the API host actually answers to a write, across a whole matrix of methods and routes.
What I measured
- 7 public API routes plus 1 route that does not exist, 8 request shapes on each: 64 requests against api.onepassapps.com
- 3 document URLs for contrast, the same 8 shapes: 24 requests against app.onepassapps.com
- 88 requests in total, one server, one day, no session, no cookies
The seven API routes are /api/products, /api/runtime, /api/auth/config, /api/health, /api/config/feature-flags, /api/plans and /api/voting. Their GET answers ranged from 371 bytes (/api/auth/config) to 15,650 bytes (/api/products).
The eight request shapes were GET, HEAD, OPTIONS, POST, POST again with a deliberately broken JSON body, PUT, PATCH and DELETE.
Every write answers 404
On the API host, POST, PUT, PATCH and DELETE answer 404 with a 124-byte envelope:
{"ok":false,"error":{"code":"NOT_FOUND","message":"That route does not exist."},"meta":{"requestId":"req_..."}}
That is the whole body, and it is the same on every route and for every one of those methods - 40 write-shaped requests, 40 answers that are byte-identical once the per-request id is masked. A POST carrying a deliberately broken JSON body gets the same 124 bytes, so the body is not even parsed before the route is refused.
The message is the point. That route does not exist is exactly what the API host says about a path it has never heard of: GET /api/nope-not-a-route returns the same 124 bytes. Mask the fresh requestId and the answer to POST /api/products is byte-for-byte the answer to GET /api/nope-not-a-route.
In 64 requests the API host never answered 405, and it never sent an Allow header. RFC 9110 lets a server that answers 405 name the methods the resource does support, so a client that wants to know which verb to try instead has to get that from somewhere else.
The preflight says the opposite
OPTIONS on the API host answers 204 with an empty body and a fixed policy:
access-control-allow-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
access-control-max-age: 600
The same list on every route, including /api/nope-not-a-route, which does not exist, and the same list whether the request asked for GET, POST, PATCH or DELETE.
So the preflight advertises four write methods - POST, PUT, PATCH and DELETE - and none of the seven routes we probed accepts one: all four answer 404 there, while the document host answers 405 to the same four. A browser that preflights a write passes the preflight and then gets the same not-found envelope a typo would get.
On the document host the same OPTIONS request answers 405 with an empty body and no allow-methods header at all, which is why a browser never preflights a write against our pages.
The two hosts side by side
- GET: 200 on the API routes, 404 for the route that does not exist; 200 on the document URLs, 404 for the URL that does not exist
- HEAD: 200 with a zero-byte body and content-type application/json on the API routes, 404 with a zero-byte body for the missing route
- OPTIONS: 204 with the full method list on the API host, 405 on the document host
- POST, PUT, PATCH and DELETE: 404 with the 124-byte envelope on the API host, 405 with an empty body on the document host
The document host checks the method before it looks the route up, so a POST to a URL that does not exist still answers 405. The API host does it the other way round and reports the wrong method as a missing route: on the API host a wrong method and a wrong path are the same answer, and nothing in the response tells the two apart.
What this means for a client
If you call our API and get 404 with That route does not exist, the route may well exist and the verb may be wrong. The status code alone will not tell you which. And if you gate a write on a CORS preflight, the allow-methods list will pass a write the route will refuse.
What I did not do
No account, no subscription, no session, no cookie - every answer above came from an unauthenticated request, so an authenticated route may answer differently. I sent no body on the plain POST, and only small JSON bodies on the malformed POST, the PUT and the PATCH. We probed 7 public routes of the API host, not every route it serves, so nothing here is a claim about routes we did not touch. One build, one region, one day, and an edge sits in front of the origin in every measurement, so a different point of the network may answer differently.
Earlier posts on this blog covered the document host's 405 layer and the API host's origin allow-list, so this one is the write-method layer in between:
https://dev.to/onepassapps/the-method-contract-of-apponepassappscom-56-urls-one-405-53km
The live app:
The guides, which walk through what each app needs:
I work on OnePassApps, so this is our own service and our own copy, measured today rather than described from memory.
Top comments (0)