# 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.

::: info 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](/installation) 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`

::: warning 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](/guide/skills)
script — see [The bundled script](/guide/skills#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.

- Source:
  [`examples/skills/producer-pal/ppal.mjs`](https://github.com/adamjmurray/producer-pal/tree/main/examples/skills/producer-pal/ppal.mjs)
- Requires Node.js 18+ (for built-in `fetch`)

### 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](#list-tools) to discover all available tools and
their input schemas at runtime. You can also browse the full tool documentation
on the [Features](/features) page.

## Raw Live API

The `ppal-raw-live-api` tool provides direct access to the
[Ableton Live Object Model](https://docs.cycling74.com/apiref/lom/) 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:

| Type                   | Properties used             | Description                    |
| ---------------------- | --------------------------- | ------------------------------ |
| `get_property` / `get` | `property`                  | Read a property value          |
| `set_property` / `set` | `property`, `value`         | Write a property value         |
| `call_method` / `call` | `method`, `args` (optional) | Call a method                  |
| `goto`                 | `value` (path)              | Navigate to a different object |
| `info`                 | —                           | Get object info                |
| `getProperty`          | `property`                  | Alias for `get_property`       |
| `getChildIds`          | `property` (child type)     | Get child object IDs           |
| `exists`               | —                           | Check if the object exists     |
| `getColor`             | —                           | Read object color              |
| `setColor`             | `value` (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](https://json-schema.org/), 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.
