Skip to content

Ableton Live REST API

Producer Pal includes a REST API for building custom scripts, automation, and integrations with Ableton Live using plain HTTP requests — no MCP SDK needed. It also works as an alternative interface for coding agents: download this page as Markdown (button at the top) and give it to your agent for a complete reference.

The REST API runs on the same server as the MCP endpoint (default port 3350) and is available whenever the Producer Pal Max for Live device is running.

This is for developers

Most users don't need the REST API. The normal way to use Producer Pal is through an AI chat client like Claude Desktop — see the Installation guide to get started.

Endpoints

List Tools

GET http://localhost:3350/api/tools

Returns all enabled tools with their JSON Schema input definitions:

json
{
  "tools": [
    {
      "name": "ppal-read-live-set",
      "title": "Read Live Set",
      "description": "Read an overview of the Live Set...",
      "annotations": { "readOnlyHint": true, "destructiveHint": false },
      "inputSchema": { "type": "object", "properties": { ... } }
    }
  ]
}

Call a Tool

POST http://localhost:3350/api/tools/{toolName}
Content-Type: application/json

{ "trackIndex": 0, "include": ["session-clips"] }

Returns:

json
{ "result": "...", "isError": false }
  • 200 with isError: false — tool ran successfully
  • 200 with isError: true — tool ran but reported an error (e.g. invalid path, execution error)
  • 404 — unknown or disabled tool
  • 400 — invalid input (includes validation details)

Warnings from the Live API appear inline in the result text, prefixed with WARNING:. The ppal-update-* tools use this when updating multiple objects — if any individual operation fails or is inapplicable (e.g. setting quantize on an audio clip), it emits a warning and continues with the rest.

Response format: ?format=json

Default will change in v1.5.0

The default response format will change from compact to json in v1.5.0. We recommend explicitly using ?format=json for now, and removing the override once v1.5.0 ships. The compact JS-literal format is optimized for LLM context efficiency and is generally not the right fit for HTTP integrations — if you do specifically want it, keep ?format=compact explicit to stay forward-compatible.

When ?format is omitted, the server uses the global compact-output setting configured on the Setup tab of the Producer Pal Max for Live device. By default that setting is on, so result is a string in a compact JavaScript-literal syntax (unquoted keys, no whitespace) optimized for LLM token efficiency — the same format MCP clients receive.

For scripts that want structured data (no JSON.parse or jq | fromjson gymnastics), append ?format=json to the request — the server parses on your behalf and the wrapper shape changes:

bash
# Compact JS-literal (default) — result is a string, warnings are inline
curl -X POST http://localhost:3350/api/tools/ppal-read-live-set \
  -H 'Content-Type: application/json' -d '{}'
# → {"result":"{tempo:120,timeSignature:\"4/4\",...}","isError":false}

# JSON — result is the parsed value; warnings are a separate string array
curl -X POST 'http://localhost:3350/api/tools/ppal-read-live-set?format=json' \
  -H 'Content-Type: application/json' -d '{}'
# → {"result":{"tempo":120,"timeSignature":"4/4",...},"isError":false}

When ?format=json is set:

  • result is the parsed value (object, array, number, string, etc.) — not a JSON-encoded string. Access fields directly: body.result.tempo.
  • warnings is a string[] (with the WARNING: prefix stripped), present only when the tool emitted any. In compact mode, warnings remain inline in result for backwards compatibility.
  • On error (isError: true), result is still a plain error string regardless of format — error messages are not JSON.

Pass ?format=compact or ?format=json to force a specific format regardless of the device-level setting. Other values return 400. Per-request format overrides apply to REST only and never affect MCP clients.

Per-request timeout: ?timeoutMs=N

Override the configured tool-call timeout for a single request. Useful for long-running operations (e.g. bulk clip generation) that need more headroom than the global timeout, or for short polling calls that should fail fast.

bash
curl -X POST 'http://localhost:3350/api/tools/ppal-create-clip?timeoutMs=10000' \
  -H 'Content-Type: application/json' \
  -d '{"slot": "0/0", "length": "16:0", "notes": "..."}'

timeoutMs must be a positive integer up to 60000 (60 seconds). Other values return 400. Combinable with ?format=:

POST /api/tools/{name}?format=json&timeoutMs=10000

Quick Start with curl

bash
# Read the Live Set overview
curl -X POST http://localhost:3350/api/tools/ppal-read-live-set \
  -H 'Content-Type: application/json' -d '{}'

# Read track 0 with all clips
curl -X POST http://localhost:3350/api/tools/ppal-read-track \
  -H 'Content-Type: application/json' \
  -d '{"trackIndex": 0, "include": ["session-clips", "arrangement-clips"]}'

# List available tools
curl http://localhost:3350/api/tools

Sample Scripts

Zero-dependency client examples — they use only built-in HTTP libraries. Copy and modify them for your own integrations.

Node.js

The Node.js client doubles as the Producer Pal Agent Skill script — see The bundled script for the full source and CLI reference. It works with Claude Code, Codex CLI, Gemini CLI, and any other agent runtime that reads the SKILL.md convention.

Python

Works with Python 3.6+ (no dependencies).

py
#!/usr/bin/env python3

"""Producer Pal REST API client (Python, no dependencies).

Usage:
  python ppal.py --list-tools [options]
  python ppal.py <tool-name> [json-args] [options]

Options:
  --url <baseUrl>      override Producer Pal URL (default http://localhost:3350)
  --timeout-ms <ms>    per-request timeout (1-60000)

Examples:
  python ppal.py --list-tools
  python ppal.py ppal-read-live-set
  python ppal.py ppal-read-track '{"trackIndex": 0}'
  python ppal.py --list-tools | jq -r '.tools[].name'
  python ppal.py ppal-read-live-set | jq .result.tempo
"""

import argparse
import json
import sys
import urllib.error
import urllib.parse
import urllib.request


def list_tools(base_url):
    """GET /api/tools — returns the full envelope `{"tools": [...]}` as a dict.

    The tool list endpoint always returns JSON; it has no `?format` toggle.
    """
    req = urllib.request.Request(f"{base_url}/api/tools")
    with urllib.request.urlopen(req) as res:
        return json.loads(res.read())


def call_tool(base_url, name, args, *, timeout_ms=None):
    """Call a Producer Pal tool by name with the given args.

    Always uses `?format=json` so `result` is a parsed value (dict/list/etc.)
    and warnings are surfaced as a separate `warnings` list.
    """
    params = {"format": "json"}
    if timeout_ms is not None:
        params["timeoutMs"] = str(timeout_ms)

    url = f"{base_url}/api/tools/{name}?{urllib.parse.urlencode(params)}"
    data = json.dumps(args).encode()
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req) as res:
        return json.loads(res.read())


def main():
    parser = argparse.ArgumentParser(
        description="Producer Pal REST API client",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            "Examples:\n"
            "  python ppal.py --list-tools\n"
            "  python ppal.py ppal-read-live-set\n"
            '  python ppal.py ppal-read-track \'{"trackIndex": 0}\''
        ),
    )
    parser.add_argument("tool", nargs="?", help="Tool name to call")
    parser.add_argument(
        "args",
        nargs="?",
        default="{}",
        help="Tool arguments as a JSON object (default: {})",
    )
    parser.add_argument("--url", default="http://localhost:3350")
    parser.add_argument("--timeout-ms", type=int, default=None, dest="timeout_ms")
    parser.add_argument("--list-tools", action="store_true", dest="list_tools")
    parsed = parser.parse_args()

    if parsed.list_tools:
        print(json.dumps(list_tools(parsed.url), indent=2))
        return

    if not parsed.tool:
        parser.error(
            "Missing tool name. Use --list-tools to discover tools, "
            "or pass a tool name as the first argument."
        )

    try:
        tool_args = json.loads(parsed.args)
    except json.JSONDecodeError as e:
        parser.error(f"Invalid JSON for tool args: {e}")

    response = call_tool(
        parsed.url,
        parsed.tool,
        tool_args,
        timeout_ms=parsed.timeout_ms,
    )
    if response.get("isError"):
        print(f"API error: {response['result']}", file=sys.stderr)
        sys.exit(1)
    print(json.dumps(response, indent=2))


if __name__ == "__main__":
    try:
        main()
    except urllib.error.URLError as e:
        if "Connection refused" in str(e.reason):
            print(
                "Could not connect to Producer Pal."
                " Is Ableton Live running with the Producer Pal device?",
                file=sys.stderr,
            )
        else:
            raise
        sys.exit(1)

Tool Reference

Use the list tools endpoint to discover all available tools and their input schemas at runtime. You can also browse the full tool documentation on the Features page.

Raw Live API

The ppal-raw-live-api tool provides direct access to the Ableton Live Object Model for development, scripting, and debugging. It is always available via the REST API regardless of tool configuration.

WARNING

This is a development tool for scripting and debugging. It can read and modify any Live Set property — use it with care.

Request structure

The path parameter sets the initial Live Object Model object to operate on (e.g., "live_set", "live_set tracks 0", "live_set tracks 0 clip_slots 1 clip"). The operations array is then executed sequentially on that object. Use goto to navigate to a different object mid-sequence.

Available operation types:

TypeProperties usedDescription
get_property / getpropertyRead a property value
set_property / setproperty, valueWrite a property value
call_method / callmethod, args (optional)Call a method
gotovalue (path)Navigate to a different object
infoGet object info
getPropertypropertyAlias for get_property
getChildIdsproperty (child type)Get child object IDs
existsCheck if the object exists
getColorRead object color
setColorvalue (hex string)Write object color

Examples

bash
# Get the tempo
curl -X POST http://localhost:3350/api/tools/ppal-raw-live-api \
  -H 'Content-Type: application/json' \
  -d '{
    "path": "live_set",
    "operations": [{"type": "get_property", "property": "tempo"}]
  }'

# Set the tempo to 140 BPM
curl -X POST http://localhost:3350/api/tools/ppal-raw-live-api \
  -H 'Content-Type: application/json' \
  -d '{
    "path": "live_set",
    "operations": [{"type": "set_property", "property": "tempo", "value": 140}]
  }'

# Fire scene 0
curl -X POST http://localhost:3350/api/tools/ppal-raw-live-api \
  -H 'Content-Type: application/json' \
  -d '{
    "path": "live_set",
    "operations": [{"type": "call", "method": "fire_scene_at_index", "args": [0]}]
  }'

# Chain multiple operations on one object
curl -X POST http://localhost:3350/api/tools/ppal-raw-live-api \
  -H 'Content-Type: application/json' \
  -d '{
    "path": "live_set tracks 0",
    "operations": [
      {"type": "get", "property": "name"},
      {"type": "get", "property": "color_index"},
      {"type": "get", "property": "has_midi_input"}
    ]
  }'

INFO

This tool is always available via the REST API. It is only available via MCP when the ENABLE_RAW_LIVE_API environment variable is set at build time (npm run build:debug).

Tips

  • The inputSchema in the tool list response is standard JSON Schema, so you can use it for client-side validation or code generation.
  • The REST API shares the same tool configuration as MCP — tools enabled or disabled on the device apply to both interfaces.
  • The REST API has no authentication (same as the MCP endpoint). It is designed for use on localhost or trusted networks only.

Released under the GPL-3.0 License.