Dictation
Félagi Desktop has a microphone. In a meeting somebody says a point aloud instead of typing it, and it lands as text in their own notes. The app records, and the recording becomes text through this server.
Then no key is on anybody's laptop: the desktop sends the transcription request it would have sent OpenAI, with the person's own API token, and the server adds its key, checks what is being sent, and passes the answer back.
Off by default, like the other places where Félagi's server calls a model (minutes, tidying up prose, the assistant — see Configuration). An installation that decided nothing sends nothing anywhere, and the desktop shows no microphone.
Turning it on
FELAGI_TRANSCRIPTION_PROVIDER=openai
OPENAI_API_KEY=sk-…
The key and the address are the AI SDK's own (OPENAI_API_KEY, and OPENAI_URL, which
defaults to https://api.openai.com/v1). openai is the only provider: the desktop sends
OpenAI's own request. Anything else, or no key, and the route answers 503 without
calling anybody.
| Variable | Default | |
|---|---|---|
FELAGI_TRANSCRIPTION_PROVIDER |
empty — off | openai turns it on |
FELAGI_TRANSCRIPTION_MODELS |
whisper-1,gpt-4o-transcribe,gpt-4o-mini-transcribe |
The models the desktop may ask for, comma-separated. Anything else is a 422. The desktop asks for whisper-1 |
FELAGI_TRANSCRIPTION_MAX_KB |
25600 |
The largest recording, in KB; larger is a 413. OpenAI takes up to 25 MB, and the desktop records at most five minutes, under 10 MB. PHP's own upload_max_filesize and post_max_size must allow it too |
FELAGI_TRANSCRIPTION_PER_MINUTE |
20 |
Requests per person per minute, across all their devices and tokens. Over it is a 429 with retry-after |
GET /api/v1/me says whether it is on, as features.transcription.
The route
POST /api/elyra/transcription/v1/audio/transcriptions
Authorization: Bearer fat_…
Content-Type: multipart/form-data
file=@recording.wav model=whisper-1 language=no
Elyra's contract (Backend::transcription() in the desktop, RFC 0032), not the API's, which
is why it is outside /api/v1, beside the assistant's. Any of the person's API tokens, and
the API's own rate limit applies as well. language is a hint, an ISO-639-1 code; anything
else is left out rather than refused.
The answer is OpenAI's, unchanged: { "text": "…" }, or its error with its status and
retry-after. On success the server dispatches App\Events\TranscriptionUsed — the person,
the workspace, the model and the recording's size in bytes — and logs the same.
What is checked, in order
Every refusal is in OpenAI's error shape, which the desktop shows:
{ "error": { "type": "invalid_request_error", "message": "A recording is needed, as `file`." } }
| Answer | |
|---|---|
| Not switched on | 503 server_error — Transcription isn't set up on the server. Nothing is sent anywhere |
| No token, or a bad one | 401, as everywhere in the API |
Over FELAGI_TRANSCRIPTION_PER_MINUTE |
429 rate_limit_exceeded, with retry-after in seconds |
A model not in FELAGI_TRANSCRIPTION_MODELS |
422 invalid_request_error |
No file |
422 invalid_request_error |
A recording above FELAGI_TRANSCRIPTION_MAX_KB |
413 invalid_request_error |
| OpenAI cannot be reached, or does not answer in 120 seconds | 502 server_error |
What leaves the server
The recording: the person's voice, and whatever else the microphone heard while they held it. That is why this is off until an operator decides, and why the desktop says, beside the microphone, that a recording goes to the server and on to OpenAI.
Nothing is kept here. The recording is read from where PHP put it for the length of the request and passed on, and PHP removes it after. Neither it nor the text is stored or logged — only who, which model and how many bytes. The text goes back to the desktop, into the person's own notes, and is somebody else's to read only when they share it.