cd /news/artificial-intelligence/an-ai-invented-an-endpoint-for-our-a… · home topics artificial-intelligence article
[ARTICLE · art-93249] src=dev.to ↗ pub= topic=artificial-intelligence verified=true sentiment=↑ positive

An AI invented an endpoint for our API. We shipped it.

EraseText, an image processing API, deployed compatibility routes to handle URLs hallucinated by AI assistants. The company mapped invented endpoints like /api/mcp/erase to its real API, added header aliasing, and wrapped responses in JSON to match what generated code expects. This turns a common AI error into a working feature.

read4 min views1 publishedAug 12, 2026

A user sent us a bug report that was really a screenshot of someone else's chat.

They had asked an AI assistant to build a web page that erases text from an

image using our API, and it produced a complete, confident little demo: file

input, preview pane, fetch call, error handling. It did not work. The error it

reported was CORS.

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

field is image_file

. A success returns image bytes, not JSON. And

/api/mcp/erase

did not exist anywhere: the model had welded "MCP" — which we

do run, as JSON-RPC for agent runtimes — onto an HTTP path, which is not what

MCP is. It also invented x/y/w/h

crop parameters, then closed by recommending

a Node proxy to work around the CORS it had diagnosed.

Everything structural. POST, multipart FormData, a file field, a key in a

header, and a JSON body carrying a URL you can drop straight into an <img>

.

That is the average of ten thousand image APIs, and the average is a reasonable

prior. The model was not being careless. It filled the parts of our API it could

not see with the parts almost every other API has.

Preflight passed, the POST failed, and the browser surfaced an opaque network

error with no readable status. In a console that looks exactly like CORS, and

every assistant will tell you it is CORS. What actually happened is duller: the

marketing domain is a static site, so a POST to an invented path under it never

reached an API at all. Once you see that, the fix stops being about headers.

We could have replied with the correct snippet, and that would have fixed one

person. The next assistant, tomorrow, generates the same URL — because it is

generating from the same priors, not from our docs.

Which reframes the problem: a wrong guess that a thousand people will make is

not a support ticket, it is an unclaimed route on your own domain.

URLs are cheap. So we made the guess true.

/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.image

is accepted next to image_file

.output_url

as a data URL — the exact shape the generated code was already trying to read./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

image bytes, which is right for a pipeline and wrong for a generated demo whose

very next line is res.json()

. So on the compatibility paths only, the bytes

get wrapped:

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

the API key:

curl -i -X POST https://erasetext.com/api/mcp/erase \
  -H 'Authorization: Bearer et_…' \
  -F 'image=@photo.jpg'

Aliasing a URL costs nothing and changes no behaviour. Accepting an invented

parameter is a different animal. Those x/y/w/h

crop fields are still a 400

with a machine-readable code, because pretending to support them would mean

silently ignoring what a caller asked for.

That is the line we drew: honour guesses about where the thing is, refuse guesses about what the thing does.

Aliases are a safety net, not a strategy. In the same week we made the contract

machine-readable where assistants actually look: an OpenAPI document, an

llms.txt

with a plain-language section for browser demos, and a hosted

playground that can be copied wholesale. Our MCP handshake now returns an

instruction that amounts to: if you are generating a web page, do not call this JSON-RPC endpoint — POST FormData to the HTTP path instead.

A handshake string is one of the few chances you get to correct a model at the

moment it is deciding.

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

about it is survivable.

I work on EraseText, the text-erasure API in these snippets. Contract: docs · playground · llms.txt.

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @erasetext 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/an-ai-invented-an-en…] indexed:0 read:4min 2026-08-12 ·