> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cartesia.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from the Line SDK

> Move a Line SDK agent to Managed Agents, feature by feature.

If you built a custom code-first agent using the Line SDK, then your agent's prompt, model, voice, and tools all have direct equivalents on Managed Agents. What changes is that you simply configure these via UI or API instead of deploying custom code. This guide helps you map each piece.

<Warning>
  Cartesia stops hosting Line SDK agents on **December 1, 2026**. This covers code-first agents you deploy as Python. Your Line agents keep running until then, so you can build the managed version, test it, and switch traffic when you're ready.
</Warning>

<Note>
  If you had built an agent on the Playground before (without deploying custom code), your agent has already been migrated and should have access to all the new features described here automatically.
</Note>

## What carries over

* **Phone numbers.** Keep the numbers you have and [assign](/line/integrations/telephony/phone-numbers#assigning-an-inbound-agent) them to a managed agent.
* **Pricing.** Per-minute voice agents rates are unchanged.
* **Call history.** Recordings and transcripts stay, and [`GET /agents/calls/{call_id}`](/api-reference/agents/calls/get-call) returns them as before.
* **APIs.** [Calls](/api-reference/agents/calls/create-outbound-call), [batch calling](/api-reference/agents/call-batches/create-call-batch), [phone numbers](/api-reference/agents/phone-numbers/list), and [metrics](/line/evaluations/metrics) work against managed agents.

## What's new

Line agents ran on your own provider key. Managed Agents don't need one: pick a model from the [LLM catalog](/agents/models) and Cartesia runs it, billing token usage as passthrough. There's no provider account to maintain and no separate bill from the model provider.

<Tip icon="gift">
  **For a limited time (until October 1, 2026), LLM usage is free.**
</Tip>

## Coming soon

* **Knowledge bases.** Attaching documents an agent can retrieve during a call.
* **Multiple languages per agent.** Agents currently take a single `language.primary`.
* **Call event webhooks.** Lifecycle events for a call, delivered to your endpoint.

If one of these blocks your migration, or you need something that isn't listed here, let us know at [support@cartesia.ai](mailto:support@cartesia.ai).

## Self-hosted agent code

Self-hosted agent code keeps working after December 1. Only agents Cartesia hosts need to migrate — if you run the agent server yourself and point Cartesia at its URL, nothing changes.

The Line SDK is [open source on GitHub](https://github.com/cartesia-ai/line) and works with self-hosted agents, so you can keep the code you have. Reach out at [support@cartesia.ai](mailto:support@cartesia.ai) for guidance on hosting it on your own servers.

Set [`self_hosted_deployment_url`](/api-reference/agents/agents/update#body-self-hosted-deployment-url-one-of-0) on the agent:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.cartesia.ai/agents/$AGENT_ID \
    -H "X-API-Key: $CARTESIA_API_KEY" \
    -H "Cartesia-Version: 2026-08-14" \
    -H "Content-Type: application/json" \
    -d '{ "self_hosted_deployment_url": "https://my-agent.example.com" }'
  ```

  ```bash CLI theme={null}
  cartesia connect --agent-id $AGENT_ID --url https://my-agent.example.com
  ```
</CodeGroup>

To disconnect the agent from your code, send `self_hosted_deployment_url` as `null` or run [`cartesia disconnect`](/line/cli#self-hosted-agent-code).

## How to migrate

### Build the managed agent

Let's start with a small agent and add features back piece by piece. Here's a basic Line agent:

```python main.py theme={null}
import os
from line.llm_agent import LlmAgent, LlmConfig, end_call
from line.voice_agent_app import VoiceAgentApp

async def get_agent(env, call_request):
    return LlmAgent(
        model="anthropic/claude-haiku-4-5-20251001",
        api_key=os.getenv("ANTHROPIC_API_KEY"),
        tools=[end_call],
        config=LlmConfig(
            system_prompt="You are Acme's support agent.",
            introduction="Hi, thanks for calling Acme. How can I help?",
        ),
    )

app = VoiceAgentApp(get_agent=get_agent)
```

The same agent as a configuration:

```bash theme={null}
curl -X POST https://api.cartesia.ai/v1/agents \
  -H "X-API-Key: $CARTESIA_API_KEY" \
  -H "Cartesia-Version: 2026-08-14" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Support",
    "config": {
      "instructions": "You are Acme'\''s support agent.",
      "initial_message": "Hi, thanks for calling Acme. How can I help?",
      "model": { "id": "claude-haiku-4.5" },
      "audio": {
        "output": { "voice_id": "e07c00bc-4134-4eae-9ea4-1a55fb45746b" }
      },
      "system_tools": { "end_call": {} }
    }
  }'
```

The configuration examples in the rest of this guide are [`PATCH /v1/agents/{agent_id}`](/api-reference/agents/update) bodies. Send only the fields you're changing; the rest of the configuration stays as it is.

### Configuration map

**Prompt and model**

| Line SDK                           | Managed Agents                                            |
| ---------------------------------- | --------------------------------------------------------- |
| `LlmConfig.system_prompt`          | `config.instructions`                                     |
| `LlmConfig.introduction`           | `config.initial_message`                                  |
| `LlmAgent(model=..., api_key=...)` | `config.model.id`, from the [LLM catalog](/agents/models) |
| `LlmConfig.temperature`            | `config.model.temperature`                                |
| `LlmConfig.max_tokens`             | `config.model.max_output_tokens`                          |

**Voice and audio**

| Line SDK                                       | Managed Agents                                    |
| ---------------------------------------------- | ------------------------------------------------- |
| `PreCallResult` `tts.voice_id`                 | `config.audio.output.voice_id`                    |
| `PreCallResult` `tts.pronunciation_dict_id`    | `config.audio.output.pronunciation_dictionary_id` |
| `PreCallResult` `tts.language`, `stt.language` | `config.language.primary`                         |
| Noise suppression level                        | `config.audio.input.noise_suppression`            |
| Background sound                               | `config.audio.output.background_sound`            |

**Tools**

| Line SDK                                   | Managed Agents                           |
| ------------------------------------------ | ---------------------------------------- |
| `http_server_tool`                         | [Webhook tools](/agents/webhook-tools)   |
| `end_call`                                 | `config.system_tools.end_call`           |
| `transfer_call`                            | `config.system_tools.transfer_to_number` |
| `send_dtmf`                                | `config.system_tools.send_dtmf`          |
| `AgentSendCustom` and other in-app actions | [Client tools](/agents/client-tools)     |
| `is_background=True`                       | `execution_mode: "async"`                |
| `timeout`                                  | `response_timeout_secs`                  |

**Running the agent**

| Line SDK                                         | Managed Agents                                                                                |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| `cartesia deploy`                                | A configuration change publishes a [version](/agents/versions)                                |
| `cartesia deployments ls`                        | [`GET /v1/agents/{agent_id}/versions`](/api-reference/agents/versions/list)                   |
| `cartesia env set`                               | [Secrets stored on the tool that uses them](/agents/webhook-tools#headers-and-authentication) |
| `wss://api.cartesia.ai/agents/stream/{agent_id}` | [`wss://api.cartesia.ai/v1/agents/websocket/{agent_id}`](/line/integrations/websocket-api)    |

### Prompt, model, and greeting

LLMs come from Cartesia's [catalog](/agents/models) instead of a provider key of your own. Claude Haiku 4.5 can now be used with `claude-haiku-4.5`. You no longer need to specify an API key — Cartesia bills model usage per call. [`GET /v1/agents/models`](/api-reference/agents/models/list) lists the available IDs with their latency and pricing.

```json theme={null}
{
  "config": {
    "instructions": "You are Acme's support agent.",
    "initial_message": null,
    "model": { "id": "claude-haiku-4.5", "temperature": 0.3 }
  }
}
```

### Voice and audio

Line allowed you to override the configured TTS voice in the `pre_call_handler`. On a managed agent it's part of the configuration:

```json theme={null}
{
  "config": {
    "language": { "primary": "en" },
    "audio": {
      "input": { "noise_suppression": "auto", "keyterms": ["Acme", "ProGrip"] },
      "output": {
        "voice_id": "e07c00bc-4134-4eae-9ea4-1a55fb45746b",
        "speed": 1.0,
        "pronunciation_dictionary_id": "your-dict-id"
      }
    }
  }
}
```

`language.primary` covers both speech recognition and synthesis, replacing the separate `tts.language` and `stt.language` settings. `keyterms`, `speed`, `volume`, and `emotion` are new; see [Agent configuration](/agents/configuration) for the full set.

### Tools

#### Webhook tools

`http_server_tool` becomes a [webhook tool](/agents/webhook-tools): Cartesia calls an HTTPS endpoint and feeds the response back to the agent. Point it at your backend services, a third-party API, or any server endpoint you can reach over HTTPS.

```python theme={null}
from line.llm_agent import http_server_tool

get_order_status = http_server_tool(
    name="get_order_status",
    description="Looks up the current status of an order. Use it whenever the caller asks where an order is.",
    url="https://api.acme.com/orders/{order_id}",
    method="GET",
    path_params_schema={
        "order_id": {"type": "string", "description": "The order number the caller provides."},
    },
    auth={"X-Api-Key": "${ACME_API_KEY}"},
    timeout=5.0,
)
```

The same tool on Managed Agents:

```bash theme={null}
curl -X POST https://api.cartesia.ai/v1/agents/tools \
  -H "X-API-Key: $CARTESIA_API_KEY" \
  -H "Cartesia-Version: 2026-08-14" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "webhook",
    "name": "get_order_status",
    "description": "Looks up the current status of an order. Use it whenever the caller asks where an order is.",
    "pre_tool_speech": "auto",
    "execution_mode": "immediate",
    "response_timeout_secs": 5,
    "api_schema": {
      "url": "https://api.acme.com/orders/{order_id}",
      "method": "GET",
      "path_params_schema": {
        "order_id": { "type": "string", "description": "The order number the caller provides." }
      },
      "request_headers": {
        "X-Api-Key": { "type": "secret", "secret_value": "sk_live_..." }
      }
    }
  }'
```

Field for field:

| `http_server_tool`             | Webhook tool                                                     |
| ------------------------------ | ---------------------------------------------------------------- |
| `url`, `method`                | `api_schema.url`, `api_schema.method`                            |
| `path_params_schema`           | `api_schema.path_params_schema`                                  |
| `query_params_schema`          | `api_schema.query_params_schema`                                 |
| `request_body_schema`          | `api_schema.request_body_schema`                                 |
| `headers`                      | `api_schema.request_headers`                                     |
| `auth={"X-Api-Key": "${VAR}"}` | `api_schema.request_headers` with a stored secret                |
| `timeout`                      | `response_timeout_secs`, a whole number of seconds from 1 to 120 |
| `is_background=True`           | `execution_mode: "async"`                                        |
| `constant_value`               | `constant_value`                                                 |

Credentials move off `cartesia env set` and onto the tool. Cartesia stores each one as a secret, and secret values are write-only, so a read returns a placeholder rather than the value. For a standard bearer token or basic auth, set `api_schema.authentication` instead of a header. See [Headers and authentication](/agents/webhook-tools#headers-and-authentication) for updating and removing them.

#### Built-in tools

Line's built-in tools become system tools: fields on a config instead of code you import.

```python theme={null}
from line.llm_agent import end_call, send_dtmf, transfer_call

tools = [
    end_call(
        description="Ends the call. Use it once the order is confirmed and the customer says goodbye."
    ),
    send_dtmf,
    transfer_call,
]
```

And on Managed Agents:

```json theme={null}
{
  "config": {
    "system_tools": {
      "end_call": {
        "description": "Ends the call. Use it once the order is confirmed and the customer says goodbye.",
        "pre_tool_speech": "force"
      },
      "send_dtmf": {},
      "transfer_to_number": {
        "transfers": [
          {
            "destination": { "type": "phone", "phone_number": "+18005551234" },
            "condition": "The caller asks to speak with a person."
          }
        ]
      }
    }
  }
}
```

See [system tools](/agents/system-tools) for the settings each slot takes.

#### Client tools

In Line, an agent reached into your app by yielding a custom event from a passthrough tool:

```python theme={null}
from typing import Annotated
from line.events import AgentSendCustom
from line.llm_agent import passthrough_tool

@passthrough_tool
async def open_cart(ctx, cart_id: Annotated[str, "Cart to open"]):
    """Opens the shopping cart. Use it when the user wants to review or check out their cart."""
    yield AgentSendCustom(metadata={"action": "open_cart", "cart_id": cart_id})
```

That becomes a [client tool](/agents/client-tools), created with the same [`POST /v1/agents/tools`](/api-reference/agents/tools/create) endpoint:

```json theme={null}
{
  "type": "client",
  "name": "open_cart",
  "description": "Opens the shopping cart. Use it when the user wants to review or check out their cart.",
  "pre_tool_speech": "auto",
  "execution_mode": "async",
  "expects_response": false,
  "parameters": {
    "type": "object",
    "properties": {
      "cart_id": { "type": "string", "description": "Cart to open." }
    },
    "required": ["cart_id"]
  }
}
```

Cartesia sends a `client_tool_call` over the WebSocket when the agent uses it, and your app opens the cart:

```javascript theme={null}
ws.onmessage = (message) => {
  const event = JSON.parse(message.data);
  if (event.type !== "client_tool_call") return;

  if (event.tool_name === "open_cart") {
    openCart(event.parameters.cart_id);
  }
};
```

`expects_response` is `false` here, so nothing is sent back. Set it to `true` and answer with `client_tool_result` when the agent needs the result.

#### Attach tools to an agent

Creating a webhook or client tool doesn't attach it to anything. Add its ID to the agent's `config.tools`, which replaces the whole list on every update:

```json theme={null}
{
  "config": {
    "tools": [
      { "id": "agent_tool_5RkP2wZmQ8xTnLc4vB7dHy" },
      { "id": "agent_tool_9dK3xQm7VbNs2PfR4tLwZa" }
    ]
  }
}
```

System tools are separate, on `config.system_tools`.

### Versions replace deployments

There's nothing to build or deploy. Every configuration change is validated and saved as an immutable [version](/agents/versions), live for new calls right away. A call already in progress finishes on the version it started with.

To roll back, read an old version's `config` and send it through `PATCH`. That records the rollback as a new version instead of rewriting history.

### Connect your clients

The agent WebSocket moved to [`/v1/agents/websocket/{agent_id}`](/api-reference/agents/agent-websocket). Authentication is unchanged — `X-API-Key` from a server, or a short-lived token from the [`/access-token` endpoint's `agent` grant](/api-reference/auth/access-token#body-grants-agent) from a browser.

| Old API                                                | Managed Agents          |
| ------------------------------------------------------ | ----------------------- |
| `start`                                                | `session_create`        |
| `ack`                                                  | `session_ready`         |
| `media_input`                                          | `audio_input`           |
| `media_output`                                         | `audio_output`          |
| `clear`                                                | `audio_output_clear`    |
| `dtmf`                                                 | `dtmf_input`            |
| `turn_started`, `turn_output_text_delta`, `turn_ended` | Same names, flat fields |

```javascript theme={null}
const ws = new WebSocket(
  `wss://api.cartesia.ai/v1/agents/websocket/${agentId}` +
    `?cartesia_version=2026-08-14&access_token=${accessToken}`
);

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "session_create",
    audio: { input_format: "pcm_44100" },
  }));
};

ws.onmessage = (msg) => {
  const data = JSON.parse(msg.data);
  if (data.type === "audio_output") playAudio(atob(data.audio));
  if (data.type === "audio_output_clear") stopPlayback();
};
```

Two things to check in your client:

* **`stream_id` is gone.** One connection is one call.
* **Turn fields were renamed.** `was_interrupted` to `interrupted`, `start_timestamp` and `end_timestamp` to `start_time` and `end_time`, and `id` to `turn`.

The [WebSocket API](/line/integrations/websocket-api) documents every event in full.

## Tell us what you built

If your Line agents run custom logic with no equivalent on Managed Agents, email [support@cartesia.ai](mailto:support@cartesia.ai) and tell us what you built. We'll help you port it over.
