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/echoes/activeCurrently playing one-shot Echo sounds
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"}

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

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"}

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.