Skip to main content

Transport

Capability endpoints are POST and accept application/json. A generation request carries too much context for a query string, so there are no GET variants of the generation calls themselves. Two exceptions worth knowing:
  • /whisper-transcribe takes multipart/form-data, because it carries an audio blob.
  • Several capability handlers answer GET /health, and unit planning also answers GET /debug and GET /session.

Response envelope

Success:
Failure:
The error field is a message, not a stable machine code. Branch on the HTTP status, not the string.
There is no error-code enum. Messages are free-text English and may be reworded without notice.The two curriculum endpoints are the exception: /recommend-lo and /recommend-as return a code field alongside error (MISSING_SUBJECT_CODE, UNSUPPORTED_SUBJECT and so on). Those codes are safe to branch on. Nothing else on the platform has them.
Failures often carry details alongside error — an upstream status and body excerpt, or a message and stack.

Casing

Request bodies use camelCase. Some downstream model-facing payloads use snake_case, and the transformation layer converts between them. That conversion is internal — the API contract is always camelCase.

CORS

Endpoints emit permissive CORS headers and answer preflight OPTIONS requests, so browser-side calls from an authorised origin work directly.

Irish curriculum vocabulary

yearGroup refers to the post-primary year ("1" through "6"). Learning outcomes are referenced by their specification codes as published by the NCCA — for example "1.1" — not by internal database identifiers.

Rate limits

There is no platform-wide application rate limiter.
RATE_LIMIT_REQUESTS_PER_MINUTE and RATE_LIMIT_WINDOW_MS exist in shared config and are parsed at startup, but no endpoint reads them. Seeing them in a config dump does not mean a limit is being enforced.
The one real limiter is on /account-lookup: a sliding window of 10 requests per minute per caller IP. It is in-memory, so the window is per isolate — Supabase may run several, and a cold start resets it. Treat it as a speed bump rather than a wall; the actual ceiling is the platform’s own rate limiting in front of the functions. Practical limits that do bind:

Telemetry

Generation responses carry a usage object. model, elapsed time and the retrieval/grounding fields are real measurements.
The token counts are not. The agents report token_source: "estimated" and derive input_tokens / output_tokens / total_tokens from whitespace word counts, not from a tokeniser. Do not bill, budget or enforce quota from them.
traceId is the useful field — quote it when reporting a bad generation.