An AI invented an endpoint for our API. We shipped it.
A user sent us a bug report that was really a screenshot of someone else's chat. It was not CORS. POST https://erasetext.com/api/mcp/erase Authorization: Bearer et_… FormData: image = <File> → reads JSON { output_url } Our API lives on a different host. The path is /v1/erase. The documented image_f
A user sent us a bug report that was really a screenshot of someone else's chat. It was not CORS. POST https://erasetext.com/api/mcp/erase Authorization: Bearer et_… FormData: image = <File> → reads JSON { output_url } Our API lives on a different host. The path is /v1/erase. The documented image_file. A success returns image bytes, not JSON. And /api/mcp/erase did not exist anywhere: the model had welded "MCP" — which we x/y/w/h crop parameters, then closed by recommending Everything structural. POST, multipart FormData, a file field, a key in a <img>. Preflight passed, the POST failed, and the browser surfaced an opaque network We could have replied with the correct snippet, and that would have fixed one Which reframes the problem: a wrong guess that a thousand people will make is URLs are cheap. So we made the guess true. The invented paths — /api/mcp/erase, /api/erase, /mcp/erase, and a bare /erase — map to the same upstream as /v1/erase. Authorization: Bearer et_… is copied into X-Api-Key, so either works. The form field image is accepted next to image_file. The loose paths answer with JSON, including output_url as a data URL — the exact shape the generated code was already trying to read. The marketing apex routes /api/* to the API worker, because that is the host models pick. The router, in the Cloudflare Worker that fronts the API: export function apiUpstreamPath(pathname) { const p = pathname.replace(/\/+$/, "") || "/"; if ( p === "/erase" || p === "/v1/erase" || p === "/api/erase" || p === "/api/mcp/erase" || p === "/mcp/erase" ) { return "/erase"; } if (p === "/account" || p === "/v1/account") return "/account"; if (p.startsWith("/v1/") && p.length > 4) return p.slice(3); return null; } /** Hallucinated "MCP HTTP" paths: return JSON `{ output_url }` for <img src>. */ export function wantsDemoJson(pathname) { const p = pathname.replace(/\/+$/, "") || "/"; return p === "/api/mcp/erase" || p === "/api/erase" || p === "/mcp/erase"; } Header aliasing is three lines, and worth more than it looks: const bearer = /^Bearer\s+(et_\S+)/i.exec( request.headers.get("Authorization") || "", ); if (bearer && !proxied.headers.get("X-Api-Key")) { proxied.headers.set("X-Api-Key", bearer[1]); } The JSON envelope is the part worth explaining. Our real success response is raw res.json(). So on the compatibility paths only, the bytes const dataUrl = `data:${mime};base64,${b64}`; return Response.json({ output_url: dataUrl, // what the generated code read output_base64: dataUrl, // …and its second guess image_base64: b64, content_type: mime, }); One deploy later, the demo that had been pasted at us ran unchanged apart from curl -i -X POST https://erasetext.com/api/mcp/erase \ -H 'Authorization: Bearer et_…' \ -F 'image=@photo.jpg' # 200 application/json { "output_url": "data:image/webp;base64,…" } Aliasing a URL costs nothing and changes no behaviour. Accepting an invented x/y/w/h crop fields are still a 400 That is the line we drew: honour guesses about where the thing is, refuse Then tell the next model the truth Aliases are a safety net, not a strategy. In the same week we made the contract llms.txt with a plain-language section for browser demos, and a hosted if you are generating a web page, do not call this A handshake string is one of the few chances you get to correct a model at the Read failing generated code as traffic data. It tells you which URL the world believes you have. Claim those guesses on your own domains — especially the marketing apex, before someone else's 404 becomes your bug report. Never let "CORS" in an assistant's diagnosis end the investigation. Check the status code on the real host first. Alias paths and headers freely. Do not invent semantics to match a hallucination. Publish the contract where machines read it: OpenAPI, llms.txt, an MCP handshake string, and a demo page worth copying. Our docs still name one canonical call. The aliases exist so that being wrong I work on EraseText, the text-erasure API in these docs · playground · llms.txt.
Key Takeaways
- •A user sent us a bug report that was really a screenshot of someone else's chat. It was not CORS. POST https://erasetext.com/api/mcp/erase Authorization: Bearer et_… FormData: image = <File> → reads JSON { output_url } Our API lives on a different host
- •This story was reported by Dev.to, covering developments in the dev space.
- •AI advancements continue to reshape industries — read the full article on Dev.to for complete coverage.
📖 Continue reading the full article:
Read Full Article on Dev.to →


