---
title: "Configuration"
description: "Constructor options for local mode."
icon: "gear"
---

# Configuration

The `Patter` constructor accepts `PatterOptions`. Pass a carrier instance and a phone number — everything else flows through environment variables unless you override it.

## Minimal config

```typescript
import { Patter, Twilio } from "getpatter";

const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });  // TWILIO_* from env
```

The carrier instance reads credentials from environment variables when you don't pass them explicitly. See [Carrier](/typescript-sdk/carrier) for the full list.

## Constructor parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `carrier` | `Twilio \| Telnyx` | Yes | — | Telephony carrier instance. Reads credentials from env vars when arguments are omitted. See [Carrier](/typescript-sdk/carrier). |
| `phoneNumber` | `string` | Yes | — | Your phone number in E.164 format. |
| `webhookUrl` | `string` | Conditional | — | Public hostname for webhooks (no protocol prefix, no path). Required unless using `tunnel: true` in `serve()`. |
| `tunnel` | `CloudflareTunnel \| StaticTunnel \| boolean` | No | — | Tunnel directive. `true` is shorthand for `new CloudflareTunnel()`. See [Tunneling](/dev-tools/tunneling). |
| `pricing` | `Record<string, Partial<ProviderPricing>>` | No | — | Override default provider pricing estimates. See [Metrics](/typescript-sdk/metrics). |
| `persist` | `boolean \| string` | No | `true` (on, platform default path) | Persist the dashboard's call history to disk so it survives process restarts. Defaults to ON since 0.6.2 — pass `persist: false` to keep the prior ephemeral-RAM-only behaviour. See [Persistent dashboard history](#persistent-dashboard-history) below. |

## Webhook URL Format

The `webhookUrl` must be a bare hostname with no protocol prefix or path:

```typescript
// Correct
webhookUrl: "abc123.ngrok.io"

// Wrong — will throw
webhookUrl: "https://abc123.ngrok.io"
webhookUrl: "abc123.ngrok.io/webhooks"
```

The SDK constructs full URLs internally (e.g., `https://{webhookUrl}/webhooks/twilio/voice`).

<Note>
**Telnyx:** Telnyx is a fully supported alternative to Twilio, with feature parity for DTMF, call transfer, recording, and cost tracking.
</Note>

## Environment variables

Every credential resolves from an env var when the matching argument is omitted. See `.env.example` in the repo for a complete list.

| Env var | Used by |
|---------|---------|
| `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` | `new Twilio()` |
| `TELNYX_API_KEY`, `TELNYX_CONNECTION_ID`, `TELNYX_PUBLIC_KEY` (optional) | `new Telnyx()` |
| `OPENAI_API_KEY` | `new OpenAIRealtime()`, `new OpenAITTS()`, `new WhisperSTT()` |
| `ELEVENLABS_API_KEY`, `ELEVENLABS_AGENT_ID` | `new ElevenLabsTTS()`, `new ElevenLabsConvAI()` |
| `DEEPGRAM_API_KEY` | `new DeepgramSTT()` |
| `CARTESIA_API_KEY` | `new CartesiaSTT()`, `new CartesiaTTS()` |
| `RIME_API_KEY` | `new RimeTTS()` |
| `LMNT_API_KEY` | `new LMNTTTS()` |
| `SONIOX_API_KEY` | `new SonioxSTT()` |
| `ASSEMBLYAI_API_KEY` | `new AssemblyAISTT()` |

### Runtime env vars

These tune SDK runtime behaviour (no credential lookup).

| Env var | Default | Effect |
|---------|---------|--------|
| `PATTER_LOG_DIR` | unset | Persistent dashboard root (see [Persistent dashboard history](#persistent-dashboard-history) below). |
| `PATTER_LOG_RETENTION_DAYS` | `30` | Days of disk history to retain. `0` disables cleanup. |
| `PATTER_LOG_REDACT_PHONE` | `1` | Mask phone numbers in `metadata.json` (last 4 digits). Set to `0` to store full E.164. |
| `PATTER_DASHBOARD_NOTIFY` | enabled | Set to `0`, `false`, `no`, or `off` (case-insensitive) to skip the fire-and-forget dashboard ingest POST. Use this when you embed Patter alongside your own Express server on port 8000 to avoid 404 spam in your access log. |
| `PATTER_BIND_HOST` | `127.0.0.1` | Host the embedded server binds to. Set to `0.0.0.0` when running inside a container whose port must be reachable from the host (e.g. `docker run -p 8000:8000` — Docker's port-mapping cannot forward to a 127.0.0.1 listener inside the container). |

<Note>
`PATTER_BIND_HOST` defaults to loopback on purpose — exposing the embedded server on `0.0.0.0` outside a containerised deployment puts the dashboard and webhook routes on every interface. Pair `PATTER_BIND_HOST=0.0.0.0` with the `dashboardToken` option on `serve()` (or skip the dashboard) before going public.
</Note>

<Note>
`PATTER_DASHBOARD_NOTIFY` only gates the standalone-dashboard ingest webhook. It does not disable the embedded dashboard you can serve from `phone.serve({ dashboard: true })` — that route runs in-process and is unaffected.
</Note>

## Examples

### OpenAI Realtime on Twilio

```typescript
import { Patter, Twilio, OpenAIRealtime } from "getpatter";

const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });

const agent = phone.agent({
  engine: new OpenAIRealtime({ voice: "alloy" }),
  systemPrompt: "You are a friendly receptionist.",
  firstMessage: "Thanks for calling!",
});
```

### OpenAI Realtime on Telnyx

```typescript
import { Patter, Telnyx, OpenAIRealtime } from "getpatter";

const phone = new Patter({ carrier: new Telnyx(), phoneNumber: "+15550001234" });

const agent = phone.agent({
  engine: new OpenAIRealtime(),
  systemPrompt: "You are a friendly receptionist.",
});
```

### Pipeline mode

```typescript
import { Patter, Twilio, DeepgramSTT, ElevenLabsTTS } from "getpatter";

const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });

const agent = phone.agent({
  stt: new DeepgramSTT({ endpointingMs: 80 }),
  tts: new ElevenLabsTTS({ voiceId: "rachel" }),
  systemPrompt: "You are a helpful assistant.",
});
```

## Persistent dashboard history

By default the dashboard is an in-memory ring buffer — restart the process and the call list is empty. Pass `persist` to keep per-call records (`metadata.json`, `transcript.jsonl`, `events.jsonl`) on disk and rebuild the dashboard on the next startup. No external database required.

| `persist` value | Behaviour |
|-----------------|-----------|
| omitted / `undefined` (default) | Persistence is **on**. Falls back to `PATTER_LOG_DIR` when set; otherwise writes under the platform default location. Changed from opt-in to default-on in 0.6.2 so the dashboard's hydrate path can rebuild call history across process restarts without extra wiring. |
| `false` | Force-off. Disk writes are skipped even when `PATTER_LOG_DIR` is set. |
| `true` | Write under the platform default location (see below). Equivalent to `PATTER_LOG_DIR=auto`. |
| `"<path>"` (string) | Write under the supplied path (`~` is expanded). Equivalent to `PATTER_LOG_DIR=<path>`. |

When `persist` is set explicitly the env var is ignored. When `persist` is `undefined`, `PATTER_LOG_DIR` continues to work as a deployment-time override.

### Platform default location

| Platform | Default root |
|----------|--------------|
| macOS    | `~/Library/Application Support/patter` |
| Linux    | `$XDG_DATA_HOME/patter` (falls back to `~/.local/share/patter`) |
| Windows  | `%LOCALAPPDATA%\patter` |

### Simplest opt-in

```typescript
import { Patter, Twilio, OpenAIRealtime } from "getpatter";

const phone = new Patter({
  carrier: new Twilio(),
  phoneNumber: "+15555550100",
  persist: true,                    // platform default location
});

const agent = phone.agent({
  engine: new OpenAIRealtime(),
  systemPrompt: "You are a friendly receptionist.",
});
```

### Custom path

```typescript
const phone = new Patter({
  carrier: new Twilio(),
  phoneNumber: "+15555550100",
  persist: "/var/log/patter",       // explicit path; ~ is expanded
});
```

### Env-var override (deployment-time)

Leave `persist` unset in code and let ops decide per environment:

```bash
# Production: keep history under a managed volume
export PATTER_LOG_DIR=/var/log/patter

# Dev: platform default
export PATTER_LOG_DIR=auto

# CI: leave unset → no disk writes
```

```typescript
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15555550100" });
// `persist` defaults to undefined → reads PATTER_LOG_DIR
```

### Programmatic hydration on startup

`phone.serve()` calls `MetricsStore.hydrate(logRoot)` automatically when persistence is enabled, so the dashboard repopulates from disk before the first call lands. You can call it directly if you build the store yourself:

```typescript
import { MetricsStore } from "getpatter/dashboard/store";

const store = new MetricsStore();
const restored = store.hydrate("/var/log/patter");
console.log(`Restored ${restored} calls from disk`);
```

`hydrate()` is idempotent — `callId`s already in the store are skipped, and unparseable records are logged at debug level rather than aborting.

### Retention

```bash
# Default: 30 days. Old day-directories are swept on ~2% of calls (no daemon).
export PATTER_LOG_RETENTION_DAYS=30

# Keep forever — opt out of automatic cleanup
export PATTER_LOG_RETENTION_DAYS=0
```

<Warning>
Retention defaults to **30 days** and phone numbers in `metadata.json` are **masked by default** (last 4 digits) via `PATTER_LOG_REDACT_PHONE`. If you need to keep call history indefinitely or store full E.164 numbers, set those env vars explicitly — and gate access to the log root, since `transcript.jsonl` is never redacted and may contain customer PII spoken during the call.
</Warning>

See [Call logging](/typescript-sdk/call-logging) for the full layout, schema, and reading patterns.

## Loading from .env

```typescript
// Node 20.6+: --env-file flag
// node --env-file=.env --loader tsx index.ts

import { Patter, Twilio, OpenAIRealtime } from "getpatter";

const phone = new Patter({
  carrier: new Twilio(),                              // reads TWILIO_* from env
  phoneNumber: process.env.PHONE_NUMBER!,
  webhookUrl: process.env.WEBHOOK_URL!,
});

const agent = phone.agent({
  engine: new OpenAIRealtime(),                       // reads OPENAI_API_KEY
  systemPrompt: "You are a helpful assistant.",
});
```

<Info>
Telnyx delivers 16 kHz PCM audio natively, so no transcoding is needed. Twilio uses mulaw 8 kHz, which the SDK transcodes automatically.
</Info>

<Warning>
Never hardcode API keys, tokens, or secrets in your source code. Always use environment variables or a secret manager.
</Warning>
