Local API

<- back to doc root  

What the Local API does

Audio Forge includes a small built-in server that runs on your machine. It lets external tools, like an Elgato Stream Deck plugin, a custom script, or any app on your PC, control playback over HTTP or WebSocket, with no extra software required.

If you already use the MQTT integration, the Local API supports the exact same commands. The difference is that it runs entirely on your machine with zero setup: no broker to install, no network to configure.

Enabling the Local API

Go to Settings → External Control → Local API and make sure the feature is enabled (it’s on by default).

You can configure:

  • Enabled toggle (default: on).
  • Port number (default: 8329).
  • Listen on LAN toggle (default: off). When on, devices on your local network can reach the API. When off, only software on the same machine can connect.
  • API Key (auto-generated). Required when connecting from another device on the network. You can copy it or regenerate it from Settings.

Once enabled, Audio Forge listens on http://localhost:8329. If “Listen on LAN” is turned on, it listens on all network interfaces instead.

Quick start

Open a terminal and try:

# See what's playing right now
curl http://localhost:8329/api/state

# List every library and category
curl http://localhost:8329/api/catalog

# List one-shot Echo sounds that are playing now
curl http://localhost:8329/api/echoes/active

# Play a category
curl -X POST http://localhost:8329/api/command ^
  -H "Content-Type: application/json" ^
  -d "{\"command\":\"play\",\"section\":\"Music\",\"categoryName\":\"Battle\"}"

That’s it, if Audio Forge is running with the Local API enabled, you’ll get JSON responses immediately.

HTTP endpoints

MethodPathDescription
GET/api/stateCurrent playback state
GET/api/catalogFull library and category catalog
GET/api/icon/{categoryUuid}Render a category icon as image data
GET/api/echoes/activeCurrently playing one-shot Echo sounds
GET/api/infoAPI version and available features (see Detecting features)
GET/api/ai_director/statusAI Director status (see AI Director)
POST/api/commandExecute a command (JSON body)

GET /api/state

Returns a JSON snapshot of what Audio Forge is doing right now:

{
  "libraryUuid": "...",
  "libraryName": "[Default]",
  "musicPlaying": true,
  "ambiancePlaying": false,
  "musicVolume": 0.75,
  "ambianceVolume": 0.5,
  "echoesVolume": 1.0,
  "musicCategories": [{"uuid": "...", "name": "Battle"}],
  "ambianceCategories": [{"uuid": "...", "name": "Rain"}],
  "lastEcho": {"uuid": "...", "name": "Thunder", "at": "2025-06-01T12:34:56Z"},
  "categoryVolume": {"<uuid>": 0.5}
}

All volume fields use the app’s display scale from 0.0 to 1.0. For Music and Ambiance, 0.5 is normal (unity) volume and 1.0 is the boosted maximum. For Echoes, 1.0 is normal volume.

categoryVolume contains current runtime values and may be sparse. Use each category’s volume field from /api/catalog when you need a complete value for every category, including categories not yet used.

GET /api/catalog

Returns every library with its Music and Ambiance categories:

{
  "activeLibraryUuid": "...",
  "libraries": [
    {
      "uuid": "...",
      "name": "[Default]",
      "music": [{
        "uuid": "...",
        "name": "Battle",
        "enabledInForge": true,
        "trackCount": 3,
        "volume": 0.5
      }],
      "ambiance": [{
        "uuid": "...",
        "name": "Rain",
        "enabledInForge": false,
        "trackCount": 0,
        "volume": 0.5
      }]
    }
  ]
}
  • enabledInForge says whether the category is visible and enabled in the Forge.
  • trackCount is the number of playable tracks currently installed for that category.
  • volume is the category’s effective volume on the 0.0 to 1.0 display scale. It is present for every category, including inactive categories.

For an active category, the catalog reports its current runtime volume. For an inactive category, it reports the configured target volume, or 0.5 when no target exists. Runtime adjustments do not replace the configured target. When a category starts again, an explicit command value is used when present; otherwise the configured target is used. The previous runtime adjustment is not reused. Restoring a State Link applies the runtime volumes recorded in that link.

GET /api/echoes/active

Returns a sampled snapshot of every one-shot Echo sound that is currently playing. Simultaneous triggers are returned as separate entries, even when they use the same category or sound file.

{
  "count": 1,
  "sampledAt": "2026-07-24T12:00:02.500Z",
  "echoes": [
    {
      "playbackId": "7b304347-410f-4eaf-bad7-4e8488778302",
      "categoryUuid": "2e4d51f1-11ce-46af-a411-0c6bc2c420cb",
      "categoryName": "Thunder",
      "fileName": "thunder-01.wav",
      "startedAt": "2026-07-24T12:00:00.000Z",
      "durationMs": 10000,
      "positionMs": 2500,
      "remainingMs": 7500,
      "progress": 0.25,
      "progressPercent": 25.0,
      "playing": true,
      "processingState": "ready"
    }
  ]
}
  • playbackId uniquely identifies this specific trigger. Two overlapping plays of the same category receive different IDs.
  • sampledAt is when Audio Forge generated the snapshot. Position values are not streamed continuously, so clients should request a new snapshot when they need current progress.
  • durationMs, remainingMs, progress, and progressPercent are null when the sound’s duration is unavailable.
  • progress uses a 0.0 to 1.0 scale. progressPercent uses 0.0 to 100.0.
  • fileName contains only the file name, never a local filesystem path.
  • Finished or stopped Echo sounds are removed from the list.

Echoes share the global echoesVolume value returned by /api/state. There is no per-playback or per-category Echo volume.

POST /api/command

Send a JSON command in the request body. On success:

{"ok": true, "message": "play ok"}

On error:

{"ok": false, "error": "category not found"}

GET /api/icon/{categoryUuid}

Returns the image for a category icon. Use the category UUID from /api/catalog. The category must belong to an active library. The optional size query parameter sets the requested size for font and SVG icons and defaults to 144 pixels.

  • Font icons and SVG icons are rendered as PNG images and tinted with the category color.
  • If a custom file is missing or an SVG cannot be rasterized, the API falls back to the font icon.
  • Custom PNG, JPG, and JPEG icons are returned unchanged; the size parameter is ignored.
  • Successful responses use image/png or image/jpeg and may be cached for one hour.
  • If the category is not found, the response is 404 with {"error":"category not found"}.
  • If the icon cannot be rendered, the response is 500 with {"error":"failed to render icon"}.

WebSocket

For tools that want real-time updates (like a Stream Deck plugin that needs to show current state), connect a WebSocket to:

ws://localhost:8329/ws

Sending commands

Send the same JSON command objects you would POST to /api/command:

{"command": "play", "section": "Music", "categoryName": "Battle"}

The server replies with a result message for each command.

Receiving updates

The server automatically pushes updates whenever something changes. Each message has a type field:

typeWhen it’s sentWhat it contains
stateOn connect + whenever playback changesSame as GET /api/state
catalogOn connect + when libraries changeSame as GET /api/catalog
resultAfter each command you sendok, message or error, requestId
echoWhen an echo sound is triggereduuid, name, at
helloOn connect, first messageapiVersion and features, see Detecting features
ai_directorOn connect + when the AI Director changesSame as GET /api/ai_director/status
ai_director_logOn connect + as the AI Director hears and actsRecent activity entries

State updates are debounced (250 ms) so you won’t be flooded during rapid changes like volume sweeps.

Messages are flat JSON objects. The payload is not nested under a data or payload field. Examples:

{"type": "state", "musicPlaying": true, "ambiancePlaying": false,
  "musicVolume": 0.5, "ambianceVolume": 0.5,
  "musicCategories": [], "ambianceCategories": [], "categoryVolume": {}}
{"type": "catalog", "activeLibraryUuid": "<uuid>", "libraries": []}
{"type": "result", "ok": true, "message": "play ok", "requestId": "request-1"}
{"type": "echo", "uuid": "<uuid>", "name": "Thunder",
  "at": "2026-07-18T12:34:56Z"}

Commands

All commands use the same JSON schema as the MQTT integration. Every command accepts an optional requestId string you can use to match responses.

play

Play or resume a section, or select a specific category.

{"command": "play", "section": "Music"}
{"command": "play", "section": "Music", "categoryName": "Battle"}
{"command": "play", "section": "Ambiance", "categoryUuid": "<uuid>"}
  • Without a category: resumes the whole section.
  • With a category: selects and plays it.
  • Ambiance uses toggle semantics. Playing an already-active Ambiance category turns it off.

pause

{"command": "pause", "section": "Music"}

stop

{"command": "stop", "section": "Ambiance"}

setActiveLibrary

Switch the active library by UUID or name.

{"command": "setActiveLibrary", "libraryName": "My Library"}
{"command": "setActiveLibrary", "libraryUuid": "<uuid>"}

setVolume

Set the volume for a whole section, or for a specific category within a section. Use value from 0.0 to 1.0, or from 0 to 100 with valueScale: percent.

{"command": "setVolume", "section": "Music", "value": 1.0}
{"command": "setVolume", "section": "Music", "value": 75, "valueScale": "percent"}
{"command": "setVolume", "section": "Echoes", "value": 0.5}
{"command": "setVolume", "section": "Ambiance", "value": 0.5,
  "categoryName": "Rain", "transitionMs": 1000}

The optional transitionMs controls how quickly the volume fades to the new level (default: 500 ms).

Music and Ambiance use the same display scale as the app: 0.5 is normal (unity) volume and 1.0 is the boosted maximum. Echoes do not have a boosted range, so 1.0 is normal volume. The API converts these display values to the internal playback gain automatically.

nextTrack

Skip to the next track in an active Music or Ambiance category. The category must already be playing.

{"command": "nextTrack", "section": "Music", "categoryUuid": "<uuid>"}
{"command": "nextTrack", "section": "Ambiance", "categoryName": "Rain"}

playEcho

Trigger a one-shot Echo sound.

{"command": "playEcho", "categoryName": "Thunder"}
{"command": "playEcho", "section": "Ambiance", "categoryUuid": "<uuid>"}

On success, the command waits until the Echo playback is registered and returns its tracking and playback fields:

{
  "ok": true,
  "message": "echo played",
  "requestId": "play-thunder",
  "playbackId": "<playback-id>",
  "categoryUuid": "<category-uuid>",
  "categoryName": "Thunder",
  "fileName": "thunder-01.wav",
  "startedAt": "2026-08-09T12:00:00.000Z",
  "durationMs": 4000,
  "positionMs": 0,
  "remainingMs": 4000,
  "progress": 0.0,
  "progressPercent": 0.0,
  "playing": true,
  "processingState": "ready"
}

Use the returned playbackId with stopEcho to stop only this playback instance. Current values can also be refreshed with GET /api/echoes/active.

stopEcho

Stop active Echo playback using exactly one selector:

{"command": "stopEcho", "playbackId": "<playback-id>"}
{"command": "stopEcho", "categoryUuid": "<category-uuid>"}
{"command": "stopEcho", "categoryName": "Thunder"}
  • playbackId stops only that playback instance. Get playback IDs from GET /api/echoes/active.
  • categoryUuid stops every active Echo from that category.
  • categoryName stops every active Echo whose category has that exact name. If categories in multiple libraries share the name, all matches are stopped.
  • The result includes stoppedCount. A selector with no active matches succeeds with stoppedCount: 0.
  • To stop every active Echo regardless of category, use {"command": "stop", "section": "Echoes"}.

setCategoryEnabled

Explicitly enable or disable a category in a section.

{"command": "setCategoryEnabled", "section": "Ambiance",
  "categoryName": "Rain", "enabled": true}

You can enable a category and set its volume atomically by including value. If value is omitted, the category’s configured target volume is used. A previous runtime adjustment is not reused.

{"command": "setCategoryEnabled", "section": "Ambiance",
  "categoryName": "Rain", "enabled": true, "value": 0.5}

restoreState

Restore a previously saved state from a share link created by the app.

{"command": "restoreState", "link": "slashpaf://..."}

setConfig

Forward a configuration setting to the MQTT integration (if connected). Useful for advanced automation.

{"command": "setConfig", "key": "someKey", "value": "someValue"}

Detecting features

Newer versions of Audio Forge add features to the API. So a tool can tell what the running app supports, the server announces its version. It’s the first WebSocket message, and GET /api/info returns the same thing:

{"type": "hello", "apiVersion": 3,
  "features": {"aiDirector": {"available": true, "enabled": true},
               "roomControl": {"commands": ["play", "pause", "stop", "..."]}}}
FieldMeaning
apiVersionStarts at 1. Version 2 (Audio Forge 1.5.18) added the AI Director. Version 3 added room control.
features.aiDirector.availableThis platform supports the AI Director.
features.aiDirector.enabledThe AI Director is switched on in this app’s settings.
features.roomControl.commandsThe commands a room guest can be allowed to run.

Older versions of Audio Forge send no hello message and have no /api/info: treat that as apiVersion 1 and don’t call anything newer. Only show or call AI Director features when available and enabled are both true.

Room control

When you host an online room you can let guests control Audio Forge from the guest page. Two commands set who can (they only work while a room is open):

{"command": "setRoomDefaultPermission", "permission": "control"}
{"command": "setRoomGuestPermission", "clientId": "<guest clientId>", "permission": "listen"}

permission is "listen" (only you control Audio Forge) or "control". The room status in GET /api/state (room) then includes defaultGuestPermission and a permission for each entry of guests (the effective one: with "control" as the default you can still set a single guest to "listen"). room.chatMessages also lists what controlling guests did, as entries with kind: "activity", command and resolved params; chat entries have kind: "chat". Guests joining or leaving appear as activity entries with command guestJoined, guestLeft, guestRemoved or guestReconnected. While guests can control, Audio Forge also uploads the category icons to the rooms server so the guest page can show them. Guests with control can only run the commands listed in features.roomControl.commands; capturing, removing guests, configuration, the AI Director and permissions stay with you.

AI Director

The AI Director listens to your table and changes the music and ambiance for you. Through the Local API you can start and stop it, choose where it gets its audio, feed it text, and follow what it understood and did. It needs the AI Director to be enabled in the app and its models to be installed.

Commands

CommandFieldsWhat it does
aiDirectorStartinputSource (optional), fresh (optional), extraReasoning (optional), device (optional), gpuLayers (optional), threads (optional), probe (optional), whisperGpu (optional)Start listening. Loading the models takes a while, so the reply comes first: watch state go from activating to listening. fresh: true forgets the previous scene. extraReasoning overrides the app’s Extra reasoning setting for this session only: true asks the model a second, short question to find the sound effect, which is more accurate and a little slower. device (Android only, for testing) is where the model runs: gpu, cpu or auto; the model reloads when it changes. gpuLayers and threads (Android only, for testing, GGUF models only) set the GPU layers and the CPU thread count; 0, the default, keeps the model on the CPU and lets the engine choose the threads. probe: true (for tuning) logs main-thread stalls and slow frames, with build and raster time apart, into the debug log for this session. whisperGpu (for testing) runs speech recognition on the GPU (true) or the CPU (false) for this session only; it is on by default; on Android it is only used on Adreno GPUs. Refused with an error if the feature is off, not unlocked, or the models aren’t installed.
aiDirectorStopStop listening and free the models.
aiDirectorSetInputinputSourceSwitch where the audio comes from while running.
aiDirectorSendTexttextGive the AI Director text as if it had been heard, with no microphone and no speech recognition. See below.

inputSource is "device" (the input device chosen in Audio Forge, the default) or "remote" (audio sent by your tool over the WebSocket).

{"command": "aiDirectorStart", "inputSource": "remote", "fresh": true}

Sending text

aiDirectorSendText is fire and forget: the reply only confirms the text was queued. Like with speech, the AI Director gathers what it hears for a few seconds and then decides what, if anything, to change. It is refused unless the AI Director is running.

{"command": "aiDirectorSendText", "text": "Heavy rain starts to fall on the old market square."}

This is useful for tools that already have a transcript, for text-based sessions, and for testing how the AI Director reacts to a scene.

Status

GET /api/ai_director/status, and the ai_director WebSocket message, which is pushed whenever something changes:

{"type": "ai_director", "available": true, "enabled": true, "entitled": true,
  "modelsReady": true, "state": "listening", "inputSource": "device",
  "sceneDescription": "Heavy rain over a market square", "lastTranscript": "...", "error": null}
FieldMeaning
entitledThe AI Director is unlocked on this account.
modelsReadyThe speech and language models are installed.
stateidle, activating, listening, processing or paused.
inputSourcedevice or remote.
extraReasoningWhether Extra reasoning is on for this run.
backendThe inference backend the language model loaded with (for example Vulkan or CPU), or null while it is not loaded.
whisperGpuWhether speech recognition runs on the GPU (Vulkan) when the build has one; it falls back to the CPU by itself when there is no usable GPU.
whisperBackendThe compute devices the speech recognition library can use, for example CPU; Vulkan0 Radeon 8060S Graphics (gpu), or null before the first start. A build without the GPU backend lists only the CPU.
sceneDescription, lastTranscriptWhat the AI Director currently believes, and the last thing it heard.
errorThe reason it couldn’t start, if any.

Activity log

The ai_director_log WebSocket message carries what the AI Director heard and did. It is sent in full when you connect (reset: true, the last 100 entries), then only new entries:

{"type": "ai_director_log", "reset": false,
  "entries": [{"id": 42, "at": "2026-10-27T15:20:48Z", "kind": "transcript", "text": "..."}]}

kind is transcript (what was heard), action (what it changed), error or info.

For a deeper look, GET /api/ai_director/debug?since=<id> returns every entry after id, including the prompt sent to the language model and its raw answer. It is meant for tuning, not for display.

Sending your own microphone audio

With inputSource set to "remote", send binary WebSocket frames of raw audio: 16 kHz, mono, signed 16-bit little-endian PCM. Audio Forge applies its usual silence detection, so you can stream continuously. Frames are ignored while the source is "device". Only one WebSocket client can supply audio at a time; when it disconnects, the AI Director goes back to the input device.

Error handling

If a command can’t be executed (for example, a category name doesn’t exist or a required field is missing), the response will contain "ok": false and an error message describing what went wrong.

{"ok": false, "error": "category not found"}
{"ok": false, "error": "section required"}
{"ok": false, "error": "invalid json"}

Security

By default the Local API binds to 127.0.0.1 (localhost only) and is not reachable from other devices on your network.

If you enable Listen on LAN, the API becomes reachable from other devices. In that case an API key is required for all non-localhost requests. The key is auto-generated and shown in Settings → External Control → Local API; pass it with every request using one of:

  • Header: Authorization: Bearer <your-api-key>
  • Query parameter: ?apiKey=<your-api-key>

Examples:

# Using the Authorization header
curl http://192.168.0.10:8329/api/state ^
  -H "Authorization: Bearer your-api-key-here"

# Using the query parameter
curl "http://192.168.0.10:8329/api/state?apiKey=your-api-key-here"

# Sending a command from another device
curl -X POST http://192.168.0.10:8329/api/command ^
  -H "Authorization: Bearer your-api-key-here" ^
  -H "Content-Type: application/json" ^
  -d "{\"command\":\"play\",\"section\":\"Music\",\"categoryName\":\"Battle\"}"

# WebSocket with API key
wscat -c "ws://192.168.0.10:8329/ws?apiKey=your-api-key-here"

Localhost requests never require a key, even when LAN mode is on.

You can regenerate the API key at any time from Settings → External Control → Local API. Any previously connected clients will need the new key.

Mobile background behavior

Mobile operating systems can suspend inbound HTTP and WebSocket handling when Audio Forge is backgrounded or the device is locked, even when native audio playback continues. The Local API does not deliberately stop itself, but it cannot guarantee background availability on iOS or Android.

For stable remote control during a session, keep Audio Forge in the foreground and keep the screen awake. This avoids relying on battery-intensive background execution that mobile platforms may stop at any time.