> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mesolab.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Conventions

> Shared request and response rules that hold across every endpoint.

## 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`](https://docs.mesolab.ai/api-reference/assist/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:

```json theme={null}
{ "success": true, "data": { } }
```

Failure:

```json theme={null}
{ "success": false, "error": "human-readable message" }
```

The `error` field is a message, not a stable machine code. Branch on the HTTP
status, not the string.

> [!WARNING]
>
> 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.

> [!WARNING]
>
> `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:

| Limit | Value |
| - | - |
| Worker wall clock | 150 seconds |
| Worker memory | 150 MB |
| Document text extraction | 400,000 characters |
| Admin invite batch | 100 users |

## Telemetry

Generation responses carry a `usage` object. `model`, elapsed time and the
retrieval/grounding fields are real measurements.

> [!WARNING]
>
> **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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.