A Missing ID Doesn't 404 — It Fetches the Whole Collection A developer discovered a bug in the REST API of AI Change Watch, a project tracking AI model changes, where a missing ID in a URL path silently degrades to the collection endpoint, returning a 200 OK with an array instead of a 404. This caused pages like /event/google- to return 500 errors, and the issue was invisible in request logs because the worker outcome was 'ok'. The developer fixed it by adding guards in the data layer to treat empty IDs and non-DTO responses as not-found. I run AI Change Watch https://aichangewatch.com , a small independent project that Keeping it running turned up a bug I think a lot of REST clients have and nobody notices, because it doesn't look like a bug from either side. Here it is in one line: GET /v1/events/{id} with an empty id becomes GET /v1/events/ which is not a 404. It's the list endpoint. It returns 200 OK . Every change on my site lives at a URL like: /event/openai-gpt-4-deprecated-bf 20260723143458 0003 Slug for humans, id after the last hyphen for the lookup. The parser is exactly what you'd expect: js export function eventIdFromParam param: string : string { const i = param.lastIndexOf '-' ; return i === -1 ? param : param.slice i + 1 ; } Now consider /event/google- . A trailing hyphen, no id. Crawlers generate these. So do chat clients and mail readers that break a long URL across lines and leave the tail behind. lastIndexOf '-' finds the final character, slice i + 1 returns "" , and the fetch goes out as /v1/events/ . The API answers 200 with { data: …every recent event… } . My mapper then did what mappers do — it mapped: if j return fromApi j.data ; // j.data is an ARRAY here fromApi read .title , .providerName , .severity off an array. All undefined . No error yet: reading a missing property off an array is perfectly legal. The page got an object shaped like an event whose every field was empty, rendered happily down the tree until it reached: providerName.charAt 0 and threw. So the URL returned 500 where a 404 was owed . /event/google- , /event/aws- , /ja/event/groq-groq- — all 500, live, for as long as they had existed. Two reasons, and the second is the interesting one. It is invisible in the request logs. I found this by querying Cloudflare's observability API for 5xx responses, and the field I would naturally have filtered on was useless: $workers.outcome = "ok" ← for every single one of them A rendered 500 is a successful worker invocation. The worker ran, produced a response, returned it. That the response was an error page is not the worker's problem. The signal lives in $metadata.error , not in the outcome. If you filter your edge logs by outcome, application-level 500s are simply not in your dataset. And a sibling route accidentally hid it. The same data layer serves /pricing/history/