Access layer · verified root cause (3rd time's the truth)
/ask: it wanted query.text, not a stringorank reported /ask as "endpoint not found." Two wrong guesses later (scanner bug ❌, cold-start ❌), scanning ora.ai itself revealed the truth: the NLWeb request body is {query:{text}} — and our route read query as a string, crashing with a 500 on the real shape.
Sending our string format to ora.ai's own passing endpoint returned its error contract:
$ curl -X POST https://ora.ai/ask -d '{"query":"what is ora"}'
{"_meta":{"response_type":"failure","version":"0.55"},
"error":"Missing query.text"} # ← the canonical shape is query.text
POST /ask {"query":{"text":"…"}} — the NLWeb-canonical body orank sends → our route did query.trim() on an object → crashPOST /ask {"query":"…"} — a bare string → worked. This is the only shape I kept testing, which is why I never saw the failure.POST ora.ai/ask {"query":{"text":"…"}} — the reference passing shape- query = body?.query ?? body?.q ?? body?.question ?? query; + const bodyQuery = + typeof body?.query === "string" ? body.query : body?.query?.text; + query = bodyQuery ?? body?.q ?? body?.question ?? query;
{query:{text}} → 200 with _meta + results, and SSE with Accept: text/event-stream. ask tests 8/8, tsc + biome clean.nlweb-ask and nlweb-streaming (both 500'd on the object body).I blamed the scanner, then blamed cold-start — both without reproducing orank's exact request. The answer came from checking the reference (ora.ai) directly and reading its error. Verify the other side's actual behavior before diagnosing; never test only the convenient path.