Local API
WhisperTyping includes a Local API that lets you integrate voice typing into your own projects. We believe there are a lot of possibilities here: connecting WhisperTyping to Stream Decks, home automation, AI agents, accessibility tools, and things we haven't even thought of yet. This is a work in progress, and we're always expanding it. If you build something cool with it, we'd love to hear about your project.
The API has two parts. The Command API lets you send commands to WhisperTyping via HTTP requests: control recording, change settings, manage your vocabulary and text replacements, and more. The Transcription Hook works the other way around: WhisperTyping sends each transcription to a service you specify, so you can intercept voice commands, process text, or trigger automations before anything is typed.
The Command API listens on port 39849 on localhost. The Transcription Hook sends requests to whatever URL you configure, which can be a local service or a remote server anywhere on the internet.
Base URL: http://localhost:39849
Table of Contents
Getting Started
The Local API is enabled by default. You can test it right away by opening a terminal and running:
# See all available endpoints
curl http://localhost:39849/
# Check app status
curl http://localhost:39849/api/status
# Start recording
curl -X POST http://localhost:39849/api/recording/start
# Stop recording (transcribes the audio)
curl -X POST http://localhost:39849/api/recording/stop
The base URL (http://localhost:39849/) returns the app version, current state, a link to this documentation, and a list of all available endpoints. This is a good starting point to verify the API is running and to discover what's available.
You can also call the API from JavaScript in the browser using fetch(). Browser requests are blocked by default for security. To allow browser access, go to Settings → Advanced and add your origin to "Allowed browser origins" (e.g. http://localhost:3000), or use * to allow all origins.
All endpoints return JSON:
{ "success": true }
If something goes wrong:
{ "success": false, "error": "Not currently recording" }
Settings
You can configure the Local API in Settings → Advanced:
* to allow all, or list specific origins separated by commas (e.g. http://localhost:3000). Scripts and desktop applications are always allowed.
Command API
Status
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/status | Get current app status |
Response:
{
"success": true,
"recording": false,
"state": "idle",
"model": "Whisper Ultra",
"language": "en",
"toolbar": "compact",
"version": "4.4.5"
}
The state field is one of: idle, recording, processing.
Recording
| Method | Endpoint | Description |
|---|---|---|
| GET/POST | /api/recording/start | Start recording (dictation) |
| GET/POST | /api/recording/stop | Stop recording and transcribe |
| GET/POST | /api/recording/cancel | Cancel current recording without transcribing |
| GET/POST | /api/recording/toggle | Toggle: starts if idle, stops if recording |
Recording started via the API works the same as recording started via a hotkey. Your AI mode, language, text replacements, and all other settings apply normally.
Request:
curl -X POST http://localhost:39849/api/recording/start
Response:
{ "success": true }
Error response (already recording):
{ "success": false, "error": "Cannot start recording - currently recording" }
Model
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/model | Get current model and available options |
| POST | /api/model | Change transcription model |
GET response:
{
"success": true,
"model": "Whisper Ultra",
"options": ["Whisper Large", "Whisper Ultra", "Ultra Medical (English)"]
}
The available options depend on your subscription tier. To change the model:
POST request:
curl -X POST http://localhost:39849/api/model \
-H "Content-Type: application/json" \
-d '{ "model": "Whisper Large" }'
POST response:
{ "success": true, "model": "Whisper Large" }
Language
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/language | Get current language and available options |
| POST | /api/language | Change language |
GET response:
{
"success": true,
"language": "en",
"model": "Whisper Ultra",
"options": [
{ "code": "", "name": "Auto-Detect" },
{ "code": "en", "name": "English" },
{ "code": "nl", "name": "Dutch" }
]
}
Available languages depend on the current model. Most models support 51 languages plus auto-detect. Medical models only support English variants. Use an empty string "" for auto-detect.
POST request:
curl -X POST http://localhost:39849/api/language \
-H "Content-Type: application/json" \
-d '{ "language": "nl" }'
POST response:
{ "success": true, "language": "nl", "model": "Whisper Ultra" }
Toolbar
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/toolbar | Get current toolbar mode and options |
| POST | /api/toolbar | Change toolbar mode |
Three modes are available:
full– Full-size toolbar, window visiblecompact– Compact toolbar, window visiblehidden– Minimized to system tray
POST request:
curl -X POST http://localhost:39849/api/toolbar \
-H "Content-Type: application/json" \
-d '{ "mode": "compact" }'
POST response:
{ "success": true, "toolbar": "compact" }
Vocabulary
The vocabulary is a list of words and phrases that helps the speech recognition model transcribe them correctly. This is especially useful for proper names, technical terms, abbreviations, and domain-specific jargon.
A vocabulary entry can be a single word or a multi-word phrase. For example, Dr. Smith, Microsoft Azure, or amoxicillin are all valid entries. The vocabulary is stored as a comma-separated string internally.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/vocabulary | Get the current vocabulary |
| POST | /api/vocabulary | Set the entire vocabulary (replaces all) |
| POST | /api/vocabulary/add | Add words to the vocabulary (skips duplicates) |
| POST | /api/vocabulary/remove | Remove words from the vocabulary |
Get Vocabulary
curl http://localhost:39849/api/vocabulary
Response:
{
"success": true,
"vocabulary": "Dr. Smith, amoxicillin, WhisperTyping",
"words": ["Dr. Smith", "amoxicillin", "WhisperTyping"],
"characterCount": 39,
"characterLimit": 500
}
vocabulary– The raw comma-separated string as stored in settings.words– The vocabulary split into individual entries (trimmed).characterCount– Current length of the vocabulary string.characterLimit– Maximum characters allowed (depends on the current model).
Set Vocabulary
Replaces the entire vocabulary with a new value.
curl -X POST http://localhost:39849/api/vocabulary \
-H "Content-Type: application/json" \
-d '{ "vocabulary": "Dr. Smith, amoxicillin, WhisperTyping" }'
Response: Returns the same format as GET (the new vocabulary state).
Add Words
Adds new entries to the existing vocabulary. Duplicates are skipped (case-insensitive comparison). Each entry in the words array can be a single word or a multi-word phrase.
curl -X POST http://localhost:39849/api/vocabulary/add \
-H "Content-Type: application/json" \
-d '{ "words": ["John Williams", "penicillin", "WhisperTyping"] }'
Response:
{
"success": true,
"added": ["John Williams", "penicillin"],
"skipped": ["WhisperTyping"],
"vocabulary": "Dr. Smith, amoxicillin, WhisperTyping, John Williams, penicillin",
"characterCount": 62,
"characterLimit": 500
}
added– Words that were added (not already present).skipped– Words that were already in the vocabulary (case-insensitive).
Remove Words
Removes entries from the vocabulary. Matching is case-insensitive.
curl -X POST http://localhost:39849/api/vocabulary/remove \
-H "Content-Type: application/json" \
-d '{ "words": ["amoxicillin", "nonexistent"] }'
Response:
{
"success": true,
"removed": ["amoxicillin"],
"notFound": ["nonexistent"],
"vocabulary": "Dr. Smith, WhisperTyping, John Williams, penicillin",
"characterCount": 50,
"characterLimit": 500
}
Screen OCR
When Screen OCR is enabled, WhisperTyping reads text from the active window before each recording and uses it to improve transcription accuracy. This helps with names, numbers, and context-specific terms visible on screen.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/ocr | Check if Screen OCR is enabled |
| POST | /api/ocr | Enable or disable Screen OCR |
Get OCR Status
curl http://localhost:39849/api/ocr
Response:
{ "success": true, "enabled": false }
Enable/Disable OCR
curl -X POST http://localhost:39849/api/ocr \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'
Response:
{ "success": true, "enabled": true }
Text Replacements
Text replacements automatically transform spoken text after transcription. For example, you can replace "my email" with your actual email address, or fix common transcription errors. Replacements are applied in order, so the position matters.
Each replacement has a unique id for stable referencing. The id is auto-generated and never changes, even when replacements are reordered.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/replacements | Get all text replacements |
| POST | /api/replacements | Set all text replacements (replaces entire list) |
| POST | /api/replacements/add | Add a new replacement |
| GET | /api/replacements/{id} | Get a single replacement by id |
| POST | /api/replacements/{id}/update | Update a replacement (partial update) |
| POST | /api/replacements/{id}/delete | Delete a replacement |
| POST | /api/replacements/{id}/move | Move a replacement to a new position |
Replacement Object
Each replacement has the following fields:
| Field | Type | Default | Description |
|---|---|---|---|
id | string | (auto) | Unique identifier (auto-generated, read-only). |
position | integer | (auto) | Position in the list (0-based, read-only). |
match | string[] | [] | One or more patterns to match. Multiple entries act as aliases (any of them will trigger the replacement). |
replace | string | "" | The replacement text. |
scope | string | "Anywhere" | Where to match: Anywhere, WholeWord, StartOnly, or EndOnly. |
preserveCase | boolean | false | If true, the replacement preserves the case of the matched text (ALL CAPS, lowercase, Title Case). |
includeVariants | boolean | false | If true, also matches variations with separators (e.g. "hash-tag" and "hash tag" both match "hashtag"). |
swallowPunctuations | boolean | false | If true, punctuation immediately following the matched text is removed. |
isRegex | boolean | false | If true, the match pattern is treated as a regular expression instead of literal text. |
enabled | boolean | true | If false, the replacement is skipped during processing. |
Get All Replacements
curl http://localhost:39849/api/replacements
Response:
{
"success": true,
"replacements": [
{
"id": "a1b2c3d4e5f6789012345678abcdef01",
"position": 0,
"match": ["my email"],
"replace": "[email protected]",
"scope": "WholeWord",
"preserveCase": false,
"includeVariants": false,
"swallowPunctuations": false,
"isRegex": false,
"enabled": true
},
{
"id": "fedcba9876543210fedcba9876543210",
"position": 1,
"match": ["hashtag", "hash tag"],
"replace": "#",
"scope": "Anywhere",
"preserveCase": false,
"includeVariants": true,
"swallowPunctuations": false,
"isRegex": false,
"enabled": true
}
],
"count": 2
}
Set All Replacements
Replaces the entire replacement list. Any replacement without an id gets one auto-assigned. Useful for bulk configuration.
curl -X POST http://localhost:39849/api/replacements \
-H "Content-Type: application/json" \
-d '{
"replacements": [
{
"match": ["my email"],
"replace": "[email protected]",
"scope": "WholeWord",
"enabled": true
},
{
"match": ["my phone"],
"replace": "+1 555-0123",
"scope": "WholeWord",
"enabled": true
}
]
}'
Response: Returns the same format as GET (the full list with auto-assigned ids).
Add a Replacement
Adds a single replacement to the end of the list. An id is auto-generated. Only match is required; all other fields use their defaults if omitted.
curl -X POST http://localhost:39849/api/replacements/add \
-H "Content-Type: application/json" \
-d '{
"match": ["my address"],
"replace": "123 Main Street, Springfield",
"scope": "WholeWord"
}'
Response:
{
"success": true,
"replacement": {
"id": "01234567890abcdef01234567890abcd",
"position": 2,
"match": ["my address"],
"replace": "123 Main Street, Springfield",
"scope": "WholeWord",
"preserveCase": false,
"includeVariants": false,
"swallowPunctuations": false,
"isRegex": false,
"enabled": true
}
}
Get a Single Replacement
curl http://localhost:39849/api/replacements/a1b2c3d4e5f6789012345678abcdef01
Response:
{
"success": true,
"replacement": {
"id": "a1b2c3d4e5f6789012345678abcdef01",
"position": 0,
"match": ["my email"],
"replace": "[email protected]",
"scope": "WholeWord",
"preserveCase": false,
"includeVariants": false,
"swallowPunctuations": false,
"isRegex": false,
"enabled": true
}
}
Update a Replacement
Updates specific fields of an existing replacement. Only include the fields you want to change. Fields not included keep their current value.
curl -X POST http://localhost:39849/api/replacements/a1b2c3d4e5f6789012345678abcdef01/update \
-H "Content-Type: application/json" \
-d '{ "enabled": false }'
Response:
{
"success": true,
"replacement": {
"id": "a1b2c3d4e5f6789012345678abcdef01",
"position": 0,
"match": ["my email"],
"replace": "[email protected]",
"scope": "WholeWord",
"preserveCase": false,
"includeVariants": false,
"swallowPunctuations": false,
"isRegex": false,
"enabled": false
}
}
You can update multiple fields at once:
curl -X POST http://localhost:39849/api/replacements/a1b2c3d4e5f6789012345678abcdef01/update \
-H "Content-Type: application/json" \
-d '{ "replace": "[email protected]", "match": ["my email", "email address"] }'
Delete a Replacement
curl -X POST http://localhost:39849/api/replacements/a1b2c3d4e5f6789012345678abcdef01/delete
Response:
{ "success": true }
Error (not found):
{ "success": false, "error": "Replacement not found: a1b2c3d4e5f6789012345678abcdef01" }
Move a Replacement
Moves a replacement to a new position in the list. Positions are 0-based. The position is clamped to the valid range.
curl -X POST http://localhost:39849/api/replacements/fedcba9876543210fedcba9876543210/move \
-H "Content-Type: application/json" \
-d '{ "position": 0 }'
Response:
{
"success": true,
"replacement": {
"id": "fedcba9876543210fedcba9876543210",
"position": 0,
"match": ["hashtag", "hash tag"],
"replace": "#",
"scope": "Anywhere",
"preserveCase": false,
"includeVariants": true,
"swallowPunctuations": false,
"isRegex": false,
"enabled": true
}
}
Transcription Hook Configuration
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/hook | Get current hook URL |
| POST | /api/hook | Set or clear the hook URL |
Get Hook Config
curl http://localhost:39849/api/hook
Response:
{ "success": true, "url": "http://localhost:8585/hook", "enabled": true }
Set Hook URL
curl -X POST http://localhost:39849/api/hook \
-H "Content-Type: application/json" \
-d '{ "url": "http://localhost:8585/hook" }'
Response:
{ "success": true, "url": "http://localhost:8585/hook", "enabled": true }
Set an empty string to disable the hook:
curl -X POST http://localhost:39849/api/hook \
-H "Content-Type: application/json" \
-d '{ "url": "" }'
Output Window
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/window/output | Get available icon names |
| POST | /api/window/output | Display content in the output window |
Display custom content in WhisperTyping's output window. This brings the window to the foreground. All fields are optional, but at least title or content must be provided.
POST request:
curl -X POST http://localhost:39849/api/window/output \
-H "Content-Type: application/json" \
-d '{ "title": "Home", "icon": "home", "content": "Lights turned off." }'
Response:
{ "success": true }
The icon field is optional. If omitted, a default icon is used. Available icons: microphone, edit, comment, terminal, reply, question, check, info, warning, error, home, lightbulb, gear, search, bolt, star, heart, flag, bell, clipboard, keyboard, play, globe, code, link.
Settings Pages
Open specific settings pages in the WhisperTyping window.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/window/settings/general | Open Settings → General |
| POST | /api/window/settings/microphone | Open Settings → Microphone |
| POST | /api/window/settings/hotkeys | Open Settings → Hotkeys |
| POST | /api/window/settings/vocabulary | Open Settings → Vocabulary |
| POST | /api/window/settings/replacements | Open Settings → Replacements |
| POST | /api/window/settings/aimodes | Open Settings → AI Modes |
| POST | /api/window/settings/appearance | Open Settings → Appearance |
| POST | /api/window/settings/history | Open Settings → History |
| POST | /api/window/settings/advanced | Open Settings → Advanced |
| POST | /api/window/settings/mobile | Open Settings → Mobile |
Request:
curl -X POST http://localhost:39849/api/window/settings/vocabulary
Response:
{ "success": true, "page": "vocabulary" }
Exit
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/exit | Shut down WhisperTyping |
curl -X POST http://localhost:39849/api/exit
Response:
{ "success": true }
Transcription Hook
The transcription hook lets an external service receive every transcription before it is typed. Your service decides what happens: let the text through normally, or intercept it and optionally display output in WhisperTyping's window.
This enables use cases like home automation voice commands, AI agents, custom command systems, logging, and more.
How It Works
The hook fires after text replacements and spoken punctuation are applied, but before AI mode processing:
Pipeline: Speech → Transcribe → Replacements & Punctuation → Hook → AI Mode → Virtual Keyboard
Configure the hook URL in Settings → Advanced under "Transcription hook URL", or via the API (POST /api/hook).
What Your Service Receives
WhisperTyping sends a POST request with JSON for each transcription:
POST http://your-service-url
Content-Type: application/json
{
"text": "Turn off all the lights in my office.",
"verbatim": "turn off all the lights in my office",
"timestamp": "2026-02-21T12:00:00.000Z",
"language": "en"
}
text– The transcription after text replacements and spoken punctuation. This is what would be typed.verbatim– The original transcription exactly as it came from the speech-to-text engine, before any processing.
How to Respond
Your service responds with a JSON object. All fields are optional. An empty response {} lets the text through normally.
| Field | Type | Default | Description |
|---|---|---|---|
intercept | boolean | false | If true, the text is blocked. Nothing is typed and no AI mode is applied. |
skip_ai | boolean | false | If true, AI mode processing is skipped. The text goes directly to the virtual keyboard. Has no effect when intercept is true. |
text | string | null | Override the transcription text. The overridden text continues through the rest of the pipeline (AI mode, virtual keyboard). If null or absent, the original text is used. |
title | string | null | Title text shown in the WhisperTyping output window header. |
icon | string | null | Icon shown in the output window header. See available icons. If omitted, a default icon is used. |
content | string | null | Body text shown in the output window panel. |
If title or content is provided, the WhisperTyping output window is brought to the foreground and displays the content. This works with any combination of flags.
Examples
Let it through (default):
{}
WhisperTyping continues normally: applies AI mode if active, then types the text.
Override the text:
{ "text": "The corrected transcription text." }
Replaces the transcription text with a new value. The overridden text continues through the pipeline normally (AI mode, virtual keyboard). Useful for fixing transcription errors, translating, or preprocessing text before it's typed.
Intercept and show output:
{
"intercept": true,
"title": "Home Automation",
"icon": "home",
"content": "Lights turned off in the office."
}
The text is blocked and a message is displayed in the output window.
Skip AI mode:
{ "skip_ai": true }
The text is typed directly, bypassing AI mode. Useful when your service has already processed the text.
Timeout
The default timeout is 30 seconds. If your service doesn't respond in time, WhisperTyping continues normally and types the text. While waiting for a response, the app shows "Handling" status.
Security
The Local API is designed to be safe by default:
- Localhost only. The server only accepts connections from your own computer. Other devices on your network cannot reach it.
- Browser requests are blocked by default. Websites cannot control WhisperTyping unless you explicitly allow their origin in settings. This prevents malicious websites from accessing the API.
- Local applications are always allowed. Scripts, automation tools, and desktop apps running on your machine can always use the API. This is by design: software already running on your computer could simulate keyboard input anyway.
Allowing Browser Access
If you're building a web-based dashboard or control panel that runs in your browser, you'll need to add its origin to the allowed list in Settings → Advanced. For example, if your dashboard runs at http://localhost:3000, add that as an allowed origin.
Use Cases
Stream Deck
Map Stream Deck buttons to start, stop, or toggle recording. Switch models or languages with a single button press.
Home Automation
Use the transcription hook to detect voice commands. Say "turn off the lights" and your service sends the command to Home Assistant while intercepting the text so it's not typed.
AI Agents
Build AI-powered agents that receive your transcriptions, understand context, take actions, and display results back in the WhisperTyping window. Agents can also configure vocabulary and text replacements for you.
Custom Scripts
Use Python, PowerShell, Node.js, or any language that supports HTTP to build your own voice-powered workflows and automations.
Get in Touch
We'd love to hear from you. Whether you're integrating WhisperTyping into your software, building a hobby project, or have an idea for a use case we haven't thought of, let us know!
Tell us about your project
What are you building with the Local API? What endpoints would be most useful to you? Your feedback helps us prioritize what to build next.