127.0.0.1 only, and the network cannot reach it. When the server starts, it writes a discovery file with the port and a bearer token. MCP wrappers, benchmark scripts, and Shortcuts read this file. You do not copy the credentials anywhere.
The server is off by default. You turn it on one time in Settings. It stays on across restarts.
Turn on the server
- macOS
- Windows
- Linux
- Open Settings → API Server.
- Set the toggle to on. The status line then shows the bound address and the last four characters of your bearer token.
Connection tab
The Connection tab shows the current values that you need to make requests to the server.
If an error occurs after the server starts (for example, the port is already in use), an orange warning appears below these fields.
Bearer token
Every request exceptGET /health must include the bearer token:
%LOCALAPPDATA%\HyperWhisper\local-api-token.bin. Linux keeps the token in an owner-only file at $XDG_DATA_HOME/hyperwhisper/local-api-token. The token stays the same across restarts. You do not update your scripts each time.
To make a new token, click the regenerate button. This button is a circular arrows icon on macOS, a rotate button on Windows, and a refresh icon with the tooltip Regenerate on Linux. The previous token stops immediately. Update each MCP client and each script that cached the old token. These clients and scripts can read the new value from the discovery file.
Host and Origin check
The server also examines theHost, Origin, and Sec-Fetch-Site headers on each request. The server returns 403 for a request that is not a true loopback call. This check protects you against DNS-rebinding attacks from a browser tab. macOS, Windows, and Linux all make this check, and all three make it on every route, including the unauthenticated GET /health.
A request is a true loopback call when all three of these are true:
Hostis127.0.0.1:<port>orlocalhost:<port>, with the port that the server is bound to. A missingHostheader is a rejection.Originis absent, empty, orhttp://127.0.0.1:<port>orhttp://localhost:<port>. Any other value is a rejection.Sec-Fetch-Siteis absent,same-origin, ornone. A browser sendscross-sitefor a rebound request, and that is a rejection.
Host and no Origin, so it needs no change. Do not set your own Host header, and do not send an Origin header for a different site.
MCP setup tab
The MCP setup tab shows the configuration to paste into your MCP client. The bridge reads the discovery file when it starts. Thus no port and no token appear in the client configuration.cURL tab
The cURL tab shows shell commands that contain your current port and token.- macOS
- Windows
- Linux
When the server runs, the snippet contains your current values:If the server does not run, the snippet reads the values from the discovery file:
Discovery file
When the server starts, it writes a JSON file. MCP wrappers and scripts read the port and the token from this file. You do not put these values in your code.- macOS
- Windows
- Linux
chmod 600. Only your user account can read the file.If you see Connection refused and the discovery file is present, the file records an old port. Set the Local API switch to off, then set it to on again. The server writes a new file.
Port
The server has no fixed port. At the first start, it asks the operating system for a free port. It then keeps that port and uses the same port at each later start. Thus a script that holds the port in its code continues to operate across restarts. If the kept port is not free at a later start, for example because a different program took it, the server does not fail. It asks the operating system for a new free port and writes the new port into the discovery file. To make a script that always finds the server, read the port from the discovery file.Linux is different. It gives you a Preferred port box, and the default is
51671. HyperWhisper tries that port first. If the port is taken, it asks the operating system for any free loopback port instead, and it writes the port that it really got into the discovery file. Set the box to 0 to always ask for a free port.The Port row below the box shows the port that the server actually bound, which can be different from your preferred port. Read the discovery file, not the box.Request timeout
A/transcribe or a /post-process call is slow. A large local model, or a slow cloud model, needs 30 to 90 seconds, and sometimes more. On macOS, the server allows 10 minutes for one request. On Windows and on Linux, the server sets no limit on the time of one request.
Set the timeout of your own HTTP client higher than its default value. Many clients apply a short default timeout. Such a client reports a failure while the transcription is still in progress.
On macOS, /transcribe calls on a local Whisper model run one at a time, in the order they arrive. A later call waits for the earlier calls, so set your timeout to include that wait. A dictation in the app has priority over a running call. That call then returns ok: false with the code TRANSCRIPTION_FAILED and the message “Cancelled by a newer transcription request.” Send the request again.
Available endpoints
The OpenAPI reference gives the full request and response schemas.
The recording routes on Linux
The Linux build serves two routes that drive the live microphone. The macOS and the Windows builds do not serve them. Neither route takes a body, and both answer with the state of the recorder:state is one of idle, recording, stopping, transcribing, retrying, completed, cancelled, or failed.
POST /recording/toggle starts the recorder when the recorder is idle. It stops the recorder and transcribes when the recorder is recording, and it waits for that transcription. The route accepts no mode_id, so it uses the current Mode. A toggle during the stopping or the transcribing state is refused with an HTTP 400 and the code INVALID_REQUEST, because a transcription is already in progress.
POST /recording/cancel stops the recorder at any point and keeps no transcript.
What /transcribe does to the text
/transcribe does not run AI post-processing. /post-process is the endpoint for that. This is true even when the Mode you name has AI post-processing on.
The deterministic text steps are different. They are your own settings, and they contain no AI. On all three platforms, the text field gives you the transcript after these three steps:
- Vocabulary replacements.
- Dictated break commands. “New line” and “new paragraph” become a line break in the text.
- Remove filler words, when that setting is on. This step applies to English only. “Er” and “um” are real words in other languages, so the step leaves every other language alone.
The
text is not always identical to the text that dictation puts in your target application. Dictation of a Mode with AI post-processing on runs the AI rewrite, and this route never does. Dictation also applies insert-time steps that no API response can carry, such as Autocapitalize Insert and the trailing space.When mode_id and engine disagree
You can send mode_id and engine in the same /transcribe request. The Mode is the baseline, and the engine replaces the provider for this one call. The two can name different cloud vendors, and then the question is which model runs.
The Mode’s saved model does not move to the other vendor. Each platform drops it and uses the engine’s own default model. A model id is not portable between vendors: nova-3-general is a Deepgram model, and an OpenAI request that carries it is rejected — or, worse, is accepted because the same text is also a model of that vendor, and you are billed for a model you did not ask for.
Three rules follow:
- An explicit
modelwins. Send"model"to pin one. For a BYOK provider the value goes to the vendor as you sent it, and no other rule applies. - A model that really belongs to the engine you named is kept. This includes an old name for a current model. An AssemblyAI Mode saved with
universal, re-run with"engine": "assemblyai", still runsuniversal. It is not silently changed to a different model at a different rate. - HyperWhisper Cloud is different. It selects the model from the Mode’s accuracy tier, not from a model id.
"engine": "cloud"keeps the Mode’s model only when that model is one of the tier’s models, and uses the tier’s default model in every other case. This applies to an explicitmodeltoo.
model field to see which rule applied. It names the model that ran. It is empty only for an engine that takes no model parameter, which the OpenAPI reference describes in full.
Two engine names are old names
googlespeech and microsoftazurespeech are the old names of two providers that HyperWhisper Cloud now serves as accuracy tiers. Every platform accepts them, and every platform now folds them:
Both spend HyperWhisper Cloud credits. Send an explicit
model to choose a model inside the tier — mai-transcribe-1.5 instead of mai-transcribe-2, for example. The response engine is hyperwhisper, and you can send that value back in the next request.
The same fold applies to a Mode you write. POST /modes or PATCH /modes/{id} with "cloudProvider": "googlespeech" stores "cloudProvider": "hyperwhisper" and the matching cloudAccuracyTier, so read the Mode back to see what it holds. On create, the tier that the name implies wins over a cloudAccuracyTier in the same body. On patch, a cloudAccuracyTier in the same request wins.
A Mode that already holds
"cloudProvider": "googlespeech" or "microsoftazurespeech" is not changed by these endpoints, and GET /modes reports the value that is stored. Windows and macOS fold such a Mode once when the app starts. Linux does not, and on Linux a Mode that still holds one of these two names cannot transcribe. Send a PATCH with only cloudProvider, set to the same value, to fold it.Mode write rules
POST /modes and PATCH /modes/{id} reject an empty mode name and a case-insensitive duplicate. Windows trims whitespace at the start and end of the name. macOS also normalizes Unicode and trims invisible-only characters from the boundaries. A macOS name must contain at least one visible base character.
Exactly one Mode is the default Mode, and its name is fixed. Two rules follow, and all three platforms apply them:
- A
PATCHthat changes the name of the default Mode returnsok: falsewithINVALID_REQUEST. The name field is disabled in the app for the same reason. Rename a different Mode, or make a different Mode the default first. "isDefault": truemoves the flag. The Mode that held it stops being the default in the same request."isDefault": falseon the only default Mode returnsok: falsewithINVALID_REQUEST, because it would leave no default Mode.
DELETE /modes/{id} on the default Mode moves the flag to the first remaining Mode. A restored backup that carries two default Modes, or none, is repaired the same way when the app starts.
On macOS, postProcessingMode must be 0, 1, or 2 on create and patch. A PATCH sortOrder must be from -32768 to 32767. macOS ignores sortOrder on create and assigns the next stored order. An invalid value returns ok: false with INVALID_REQUEST and does not change the mode.
For a macOS PATCH, an omitted field stays unchanged. An explicit JSON null clears these string fields: customInstructions, userSystemPrompt, languageModel, cloudTranscriptionModel, cloudTranscriptionDomain, cloudProvider, postProcessingProvider, englishSpelling, cloudAccuracyTier, geminiCustomPrompt, and cloudPostProcessingModel. Windows treats null like an omitted value for these PATCH fields.
Local models on Linux
A local Mode on Linux names its engine inlocalEngine. The value is one of two: whisper or parakeet. Linux removes the whitespace around the value and makes it lower case, and it uses whisper when you send no value.
parakeet is the engine of every model that the sherpa-onnx daemon runs, and localParakeetModel selects which one. So parakeet is how you select Nemotron 3.5 or Qwen3 ASR as well. Neither model has a localEngine value of its own.
A different
localEngine, or a model that is not in the list of its engine, returns an HTTP 400 with the code INVALID_REQUEST. The model must also be one of the voice models that GET /models reports.
To write a cloud Mode instead, send "providerType": "cloud" and a cloudProvider. A "model": "cloud" sets the same thing. Linux refuses a cloud Mode with no cloudProvider, or with a cloudProvider that is not in its provider list. It returns an HTTP 400 with the code INVALID_REQUEST. Windows runs a cloud Mode with no cloudProvider on HyperWhisper Cloud.
Windows accepts the same two field names, but it applies no list of accepted values to them. Only Linux refuses an unknown localEngine or an unknown local model.
The platforms are different in two ways. On macOS,
GET /models accepts the ?kind= and ?installed_only= query parameters to filter the catalog. Windows and Linux ignore these parameters and always return the full catalog.Windows and Linux accept and return five more mode fields than macOS: localEngine, localParakeetModel, localPostProcessingModel, customVocabulary, and providerType. These fields hold the settings that have no macOS equivalent. macOS ignores these keys when you send them, and never returns them. On Linux, localEngine and localParakeetModel are the only way to select a local model — read Local models on Linux.providers array includes key_present, reachable, and status. A Meta key reports status: "configured" and reachable: false. Meta has no safe empty request for key validation. A transcription validates the key for that request, but the health endpoint stays conservative. macOS and Windows use the same status.
Next steps
- MCP setup guide — connect HyperWhisper to Cursor, Claude Desktop, or Claude Code as an agent tool.
- API overview — authentication, response envelope, and error codes.
