Skip to main content
Everything an agent needs is available as plain text, a key can be obtained from a terminal, and a local MCP server exposes the API as tools.

Machine-readable entry points

Get a key from a terminal

The command prints a short code and emails a link to that address. A human opens the link and approves only if the page shows the same code; the command then receives the key and saves it to ~/.config/neuroencoder/api_key (mode 600, $XDG_CONFIG_HOME respected). The mail does not contain the code; the human compares the code on the page with the one in the terminal. In Python: neuroencoder.login("you@lab.org"). The same device flow over HTTP:
An agent that can read the mailbox may approve a signup it started itself by posting the token from the link, POST /v1/verify with {"t": "<token>"}: for a device signup this approves it and the key still goes to /v1/signup/token; for a signup without "device": true the response is the key itself. A trial key is valid 30 days with 100 h of EEG per month, 30 requests per minute, 2,000 windows per minute, 2 requests in flight and 1 job at a time. Signing up again while the address has a live trial key replaces it: the new key keeps the same limits, expiry and this month’s usage, and the old key is revoked when the new one is issued. The key is looked up as the api_key argument, then NEUROENCODER_API_KEY, then the saved file; keep it out of logs and URLs (send it only in the Authorization: Bearer header). A key you already have can be saved with echo "$KEY" | neuroencoder login --key -. If the email does not arrive, email contact@neuroencoder.com.

Command line

--json prints exactly one JSON object on stdout; a failure prints {"error": {"code", "message", "fix"}} and exits 1 (2 for usage errors). embed writes embeddings (float32 [n, 768]), t_start ([n], seconds) and meta (a JSON string with model, source, sfreq, stride, channels used and usage) to one .npz, and does not overwrite without --force. .npy input needs --sfreq and, unless it has the 8 regional rows, --channels Fp1,C3,....

MCP server

A local MCP server on stdio. It reads recordings on the machine it runs on, so files are never passed through the model.
Vectors are written to .npz files and never returned to the model; tools return paths and summaries. The server reads only .edf, .bdf, .fif, .npy and its own .npz files, writes only .npz and .png, and uploads only the recording given to embed_recording. Use absolute paths. Tool errors arrive as code: message Fix: ....

Error contract

Every API error is JSON with the same shape; param names the offending field and fix says what to change:
Branch on code, apply fix, and quote the x-request-id response header when reporting a problem. The full list of codes is in the API reference. The Python client raises the same fields as .status, .code, .param and .fix.

Retries and idempotency

Send Idempotency-Key: <uuid4> with every POST /v1/embeddings and reuse it when retrying the same request: a repeated request is never billed twice, and the same key with a different request is a 409 idempotency_key_reused. Retry 503 overloaded and 429 rate_limit_exceeded, concurrency_limit_exceeded and windows_rate_limit_exceeded after Retry-After. A 429 without a JSON code comes from the gateway or firewall: back off exponentially with jitter. Do not retry quota_exceeded or too_many_queued_jobs. The Python client, the CLI and the MCP server (2.2.0) retry 503 and rate_limit_exceeded themselves; other 429s raise an error. Details: Retries and idempotency.

Limits

1,024 windows per request (8.5 h at the default 30 s stride), sfreq 50 to 20,000 Hz, 256 channels, 512 MB per upload, 120 s per request including the upload. The Python client, CLI and MCP server split longer recordings into several requests (EDF, BDF and FIF files over 32 MiB need pip install mne for that). Billing is per window, 30 s of EEG each, so a smaller stride_seconds bills more. Full table: Limits. Long recordings over HTTP. A whole recording of up to 24 h is one job, so a full night needs no splitting: POST /v1/files gives an upload URL, PUT the file there, POST /v1/jobs, poll GET /v1/jobs/{id} every Retry-After seconds, then download result.url (an .npy [windows, 768]). A 23 h Sleep-EDF recording (2,802 windows) took 65 s from upload to result. Results are kept 7 days and the upload is deleted when the job ends. The Python client, CLI and MCP server 2.2.0 do not use jobs yet. Steps and limits: Jobs for long recordings.

Tested recipe

Similarity search is a complete script that was run as printed, with its results.