---
title: "Quickstart"
description: "Give your AI agent a phone number in four lines of Python."
icon: "rocket"
---

# Python Quickstart

Patter connects an AI agent to a real phone number in four lines of Python. This page walks you end-to-end: install, credentials, the four lines, and your first call.

## Prerequisites

- Python 3.11+
- A Twilio **or** Telnyx account with a phone number you own
- An AI provider key — OpenAI for the default Realtime engine, or any supported STT/TTS pair for pipeline mode

## Step 1 — Install

```bash
pip install getpatter
```

## Step 2 — Set environment variables

Each carrier and engine reads its credentials from the environment by default. The four-line quickstart below works as-is once these are set.

<CodeGroup>

```bash .env
# Telephony — pick one carrier
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your_auth_token

# Realtime engine (default)
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
```

```bash bash/zsh
export TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export TWILIO_AUTH_TOKEN=your_auth_token
export OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
```

```powershell PowerShell
$env:TWILIO_ACCOUNT_SID = "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$env:TWILIO_AUTH_TOKEN = "your_auth_token"
$env:OPENAI_API_KEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxx"
```

</CodeGroup>

| Variable | Purpose |
|---|---|
| `TWILIO_ACCOUNT_SID` | Account SID from the Twilio console. Starts with `AC`. |
| `TWILIO_AUTH_TOKEN` | Auth token paired with the SID. Keep it secret. |
| `OPENAI_API_KEY` | Used by `OpenAIRealtime`, `WhisperSTT`, and `OpenAITTS`. |

Using Telnyx instead? Swap the Twilio vars for `TELNYX_API_KEY` and `TELNYX_CONNECTION_ID` (plus optional `TELNYX_PUBLIC_KEY` for webhook signature verification) and use `Telnyx()` in place of `Twilio()`. See the [Carrier](/python-sdk/carrier) page for all supported env vars.

## Step 3 — The four-line quickstart

Create `main.py`:

```python main.py
from getpatter import Patter, Twilio, OpenAIRealtime

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(engine=OpenAIRealtime(), system_prompt="You are a receptionist.")
await phone.serve(agent, tunnel=True)
```

Want a custom greeting? Add `first_message="..."`:

```python
agent = phone.agent(
    engine=OpenAIRealtime(),
    system_prompt="You are a friendly receptionist for Acme Corp.",
    first_message="Hello! How can I help?",
)
```

What each line does:

1. **Import** — the three primitives you need: the client, a carrier, and an engine. All three read credentials from environment variables by default, so no keys appear in source.
2. **`Patter(...)`** — build the client and bind it to your phone number. `Twilio()` (or `Telnyx()`) carries the carrier credentials; swap it to change providers.
3. **`phone.agent(...)`** — describe the agent: the engine (OpenAI Realtime by default), the system prompt, and the first message it speaks on answer.
4. **`await phone.serve(agent, tunnel=True)`** — start the embedded server. `tunnel=True` spawns a Cloudflare tunnel and points your Twilio number at it automatically, so the agent is reachable from the PSTN without any additional DNS or firewall work.

<Note>
  `tunnel=True` is the dev shorthand. In production replace it with a stable webhook URL via the constructor:

  ```python
  phone = Patter(carrier=Twilio(), phone_number="+15550001234", webhook_url="api.prod.example.com")
  ...
  await phone.serve(agent)
  ```

  Or use a pre-provisioned tunnel object: `Patter(..., tunnel=Static(hostname="agent.example.com"))`. See [Tunneling](/dev-tools/tunneling).
</Note>

## Step 4 — Run it

```bash
python main.py
```

You'll see the Patter banner, a Cloudflare tunnel URL, and a log line confirming the Twilio voice webhook was set to that URL.

## Step 5 — Call the number

Pick up your phone, dial your Twilio number, and the agent will answer with `"Hello! How can I help?"`. Start talking.

<Note>
Since 0.6.2, `Patter(...)` persists per-call records (`metadata.json`, `transcript.jsonl`, `events.jsonl`) to the platform default data directory by default so the local dashboard's call history survives process restarts. Pass `persist=False` to keep the old ephemeral-RAM-only behaviour, or `persist="/custom/path"` to choose a different location. See [Call logging](/python-sdk/call-logging) for the full layout.
</Note>

---

## Using a different engine

OpenAI Realtime is the default. To switch engines, pass a different instance:

```python
from getpatter import Patter, Twilio, ElevenLabsConvAI

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(engine=ElevenLabsConvAI(), system_prompt="You are a helpful assistant.")
await phone.serve(agent, tunnel=True)
```

`ElevenLabsConvAI()` reads `ELEVENLABS_API_KEY` and `ELEVENLABS_AGENT_ID` from the environment.

For the STT + LLM + TTS pipeline mode, pick each stage independently:

```python
from getpatter import Patter, Twilio, DeepgramSTT, AnthropicLLM, ElevenLabsTTS

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
    stt=DeepgramSTT(),                    # reads DEEPGRAM_API_KEY
    llm=AnthropicLLM(),                   # reads ANTHROPIC_API_KEY
    tts=ElevenLabsTTS(voice_id="rachel"), # reads ELEVENLABS_API_KEY
    system_prompt="You are a helpful assistant.",
    first_message="Hello!",
)
await phone.serve(agent, tunnel=True)
```

Swap `AnthropicLLM()` for `OpenAILLM()`, `GroqLLM()`, `CerebrasLLM()`, or `GoogleLLM()` — tool calling works across all five. Need fully custom LLM logic? Drop `llm=` and pass `on_message=handler` to `serve()` instead.

---

## Non-English agents

OpenAI Realtime auto-detects the spoken language from the inbound audio and matches the language of your `system_prompt`. Writing the prompt in the target language is the primary control:

```python
# Japanese — write the prompt in Japanese.
agent = phone.agent(
    engine=OpenAIRealtime(),
    system_prompt="あなたは丁寧な日本語のアシスタントです。",
    first_message="お電話ありがとうございます。ご用件をお伺いします。",
)
```

The `language="ja"` parameter on `OpenAIRealtime` only seeds the auto-generated fallback prompt (`"Respond in {language}"`) when you don't supply your own `system_prompt`. It does not configure the Realtime API session itself.

For pipeline mode the STT needs the language code so it can pick the right acoustic model:

```python
agent = phone.agent(
    stt=DeepgramSTT(language="ja"),        # required: Deepgram needs the JP model
    llm=AnthropicLLM(),                    # LLM responds in whatever language you prompt it in
    tts=ElevenLabsTTS(voice_id="..."),     # ElevenLabs auto-detects from text; pick a voice that supports JP
    system_prompt="あなたは丁寧な日本語のアシスタントです。",
)
```

**E.164 formatting for international calls:** outbound `to=` values must be E.164. Many regions drop the trunk-zero — Japanese numbers like `090-xxxx-xxxx` are dialled as `+81XXXXXXXXX` (drop the leading `0`).

---

## What's next

<CardGroup cols={2}>
  <Card title="Configuration" icon="gear" href="/python-sdk/configuration">
    Every constructor option in one place.
  </Card>
  <Card title="Agents" icon="robot" href="/python-sdk/agents">
    Customize voice, model, tools, and guardrails.
  </Card>
  <Card title="STT providers" icon="microphone" href="/python-sdk/stt">
    Deepgram, Whisper, Cartesia, Soniox, Speechmatics, AssemblyAI.
  </Card>
  <Card title="TTS providers" icon="volume" href="/python-sdk/tts">
    ElevenLabs, OpenAI, Cartesia, Rime, LMNT.
  </Card>
  <Card title="Tools" icon="wrench" href="/python-sdk/tools">
    Give your agent function-calling superpowers.
  </Card>
</CardGroup>
