Configuration
Every SDK option, environment variable and dashboard setting in one place, with defaults and the tradeoff each one carries.
Python SDK
VaaniObserver(
endpoint=None,
api_key=None,
spool_directory=None,
capture=None,
instrumentations=None,
endpoints=None,
upload=None,
strict=False,
)| Option | Type | Default | Notes |
|---|---|---|---|
endpoint | str | None | Dashboard base URL; only used by upload |
api_key | str | None | Bearer token; not validated by the local dashboard |
spool_directory | str | Path | cwd/.vaani-spool | Package output directory |
capture | dict | see below | What to record |
instrumentations | dict | {http: True, websocket: True} | Which clients to patch |
endpoints | list[dict] | [] | Provider URL rules |
upload | dict | see below | Upload behaviour |
strict | bool | False | Raise instead of degrading |
Node.js SDK
new VaaniObserver({
endpoint: undefined,
apiKey: undefined,
spoolDirectory: undefined,
capture: undefined,
instrumentations: undefined,
endpoints: undefined,
upload: undefined,
strict: false,
});Identical shape in camelCase. instrumentations uses { fetch, websocket }
rather than { http, websocket }.
capture
| Option (Python / Node) | Default | Effect |
|---|---|---|
audio / audio | True | Record caller and agent PCM |
http_bodies / httpBodies | False | Capture request and response bodies |
websocket_text_frames / websocketTextFrames | False | Configuration flag; the documented socket observer records counts/lifecycle, not frame contents |
stt_content / sttContent | False | Capture transcript text |
payload_max_bytes / payloadMaxBytes | 16384 | Size bound on every payload |
Audio is on by default when frames are supplied. http_bodies and
stt_content can put prompts and caller speech in plain text into
events.jsonl and the dashboard's database. Review recording permissions,
retention and access for your use case; these settings provide no legal or
compliance guarantee. See
Capture and privacy.
endpoints
{"id": "llm", "type": "llm", "url": "https://api.openai.com/v1", "match": "path"}| Field | Required | Values |
|---|---|---|
id | yes | Unique; duplicates raise at construction |
type | yes | stt | llm | tts |
url | yes | http, https, ws or wss |
match | no | path (default) | origin | exact |
An unknown match raises TypeError. A URL matching more than one rule at the
same scheme precedence raises Ambiguous Vaani endpoint rules for …. See
Endpoints and instrumentation.
upload
The environment column is Python only. The Node SDK accepts the same three
transport options as camelCase constructor keys — { retries, timeoutMs, minThroughputBps } — with the same defaults (3, 30000, 131072). It does
not compress.
| Option | Environment | Default | Effect |
|---|---|---|---|
retries | VAANI_UPLOAD_RETRIES | 3 | Retries per request for eligible transient failures; checkout-specific status rules are below |
timeout_s | VAANI_UPLOAD_TIMEOUT_S | 30.0 | Socket timeout for the handshake |
min_throughput_bps | VAANI_UPLOAD_MIN_THROUGHPUT_BPS | 131072 | Assumed worst-case speed; extends the timeout by body size |
compress | VAANI_UPLOAD_COMPRESS | True | gzip objects of 64 KiB or more, when the ingest advertises gzip |
The environment column matters because from_env() — the constructor every
integration guide shows — builds the observer itself. Until these variables
existed, a value set from this table was silently discarded. An explicit
upload={...} argument still wins over the environment.
timeout_s is a base request budget, with a transfer-size allowance controlled
by min_throughput_bps. Larger allowances help slow uplinks but can occupy a
shutdown hook longer. Set min_throughput_bps to 0 to disable the allowance.
Test your longest recordings against the actual SDK revision and network.
upload_package(finalized, timeout=...) provides an overall upload budget across
objects and retries. It is not a delivery or durability guarantee: finalization,
local file work and the host's shutdown deadline also matter. Digests describe
the uncompressed object, never the transfer encoding. A failed upload normally
leaves its package on the spool; retry from persistent storage with
python -m vaani_observer.drain.
Development-checkout uploader hardening
The current checkout adds streamed object uploads, an inventory at session
creation, per-object upload headers, bounded retries that respect Retry-After,
and a 300-second default overall upload budget. These changes are available
in the pinned hosted source previews in the Python and Node.js quickstarts.
Both revisions were installed in clean environments. Registry publication and
public service admission remain separate; check your installed revision before
relying on this behavior.
| Development-checkout setting | Default | Override |
|---|---|---|
Python upload.total_timeout_s | 300 seconds | upload_package(finalized, timeout=90) or the sync equivalent overrides the overall budget |
Node.js upload.totalTimeoutMs | 300000 milliseconds | uploadPackage(finalized, 90000) overrides the overall budget |
In Python, configure the constructor with
upload={"total_timeout_s": 300}; in Node.js use
upload: { totalTimeoutMs: 300000 }. Omitting the per-call override (or passing
Python timeout=None) uses the configured total budget. Node's second argument
is a number of milliseconds, not an options object. Total budgets must be finite
and positive. This is distinct from Python timeout_s or Node.js
timeoutMs, which are base per-request budgets. Hashing, compression where
applicable, requests and retries share the overall budget.
Python total_timeout_s is a constructor setting only; no new environment
variable is provided. VAANI_UPLOAD_TIMEOUT_S continues to configure the
per-request base, not the overall budget. Eligible retry statuses are 408, 425, 429, 500,
502, 503 and 504. Named quota, validation and limit errors remain permanent
even on 429; ordinary 401/403 responses are not retried. An explicit
expired-object-capability response can refresh the idempotent create once,
without resetting the total budget.
Refresh requires an explicit expiry code or message. The hosted development
backend uses 401 with detail.code: "upload_expired". An elapsed expires_at
alone does not permit refreshing a generic 401 or 403: invalid, tampered or
wrong-object credentials remain permanent failures. Hosted 429 responses with
detail.code: "monthly_quota_exhausted" or "storage_quota_exhausted" are also
permanent, not transient retry signals.
For a create response with upload URLs, the SDK repeats PUTs and completion,
while the hosted development backend verifies identical content without
replacing accepted objects.
If the initial create or refresh
explicitly returns status: "accepted"
with upload_urls: {}, the checkout SDKs skip object uploads and repeat
completion with the unchanged inventory. Missing URLs without the explicit
accepted status are not a success shortcut. The current hosted development
backend uses this accepted-replay response, also returning empty
upload_headers and the same session/upload identity. Different manifests or
inventories fail with 409 before the shortcut. The compatible SDK handling
is included in those pinned source previews; older or unqualified Git revisions
may not support this backend contract. See HTTP API.
Streaming limits object buffering, but hashing and optional Python gzip scratch files still consume I/O, CPU and disk. A retry reopens the stream rather than making failed delivery durable. HTTPS or loopback HTTP is required, redirects are rejected, and an HTTPS ingest cannot downgrade to an HTTP object upload. Only allowed object headers are applied without forwarding the ingest bearer token. See HTTP API for the additive protocol fields; local backwards compatibility does not imply hosted acceptance.
Python custom transport overrides have additional limits: legacy four-argument
callables cannot be forcibly interrupted. Unmarked overrides receive byte
bodies only up to 1 MiB; larger uploads require supports_streaming=True.
Adapters must enforce timeouts and reject redirects themselves. The native
Python transport ignores proxy environment variables, while Node sends raw
uncompressed objects. System DNS resolution and stalled filesystem work also
prevent a universal hard wall-clock guarantee. See the SDK's upload documentation
before replacing its transport.
strict
| Value | Behaviour | Use it |
|---|---|---|
False (default) | Instrumentation errors are swallowed; degradation surfaces in capture_status | Production |
True | Errors raise | Staging and CI |
The default trades visibility for safety: a bug in observability cannot take
down a call, but capture can degrade silently. Your signals that it happened are
capture_status in the manifest and the unverifiable class in the dashboard —
not an exception.
LiveKit integration environment
Read by VaaniLiveKitRecorder.from_env().
| Variable | Default | Purpose |
|---|---|---|
VAANI_ENABLED | false | Master switch — off by default |
VAANI_ENDPOINT | http://localhost:8000 | Dashboard base URL |
VAANI_API_KEY | local-dev | Bearer token |
VAANI_SPOOL_DIR | .vaani-spool | Package output directory |
VAANI_CAPTURE_AUDIO | true | Record PCM |
VAANI_CAPTURE_HTTP_BODIES | true | Capture bodies |
VAANI_CAPTURE_STT_CONTENT | true | Capture transcripts |
VAANI_PAYLOAD_MAX_BYTES | 16384 | Payload bound |
VAANI_AGENT_ID | livekit-agent | Default agent id |
VAANI_UPLOAD | true | Upload after finish() |
VAANI_UPLOAD_TIMEOUT_S | 30.0 | Socket timeout for the handshake |
VAANI_UPLOAD_RETRIES | 3 | Retries per request |
VAANI_UPLOAD_MIN_THROUGHPUT_BPS | 131072 | Assumed worst-case upload speed |
VAANI_UPLOAD_COMPRESS | true | gzip large objects in transit |
VAANI_ENDPOINTS | [] | JSON array of endpoints rules; enables connection capture |
These defaults are more permissive than the core SDK's —
VAANI_CAPTURE_HTTP_BODIES and VAANI_CAPTURE_STT_CONTENT default to true.
Set them to false for production traffic unless you have reviewed the privacy
implications.
from_env() never raises: on failure it returns an inert recorder whose
methods are no-ops and whose enabled is False. Check recorder.enabled at
startup if you want a misconfiguration to be visible; recorder.last_error
carries the reason. When VAANI_ENABLED is set but recording ends up off, the
reason is also logged at ERROR — silence there was a bug, not a design.
When VAANI_ENDPOINTS is empty, HTTP and WebSocket instrumentation is not
installed at all: patching clients with no rules to match is pure cost.
Dashboard
| Variable | Default | Purpose |
|---|---|---|
VAANI_DATA_DIR | ./data | SQLite file and uploaded objects |
VAANI_REQUIRE_API_KEY | 0 | Optional local ingest gate; does not authenticate reads or isolate tenants |
VAANI_EVALUATIONS_ENABLED | off | Only 1 opts into manual external evaluation |
ELEVENLABS_API_KEY | — | Challenger transcription; required to evaluate |
OPENAI_API_KEY | — | Semantic risk judge |
STT_EVAL_JUDGE_MODEL | gpt-4o-mini | OpenAI judge model; compatible OpenAI model fallback may be used |
VAANI_ENV_FILE | ./.env | os.pathsep-separated dotenv paths |
Fixed limits, not configurable:
| Constant | Value |
|---|---|
MAX_UPLOAD_BYTES | 128 MiB |
ALLOWED_OBJECTS | events.jsonl, call.audio, caller.audio, agent.audio |
| Challenger worker pool | 2 threads |
COHORT_SAMPLE_LIMIT | 25 sessions |
CHALLENGER_MODELS | elevenlabs_scribe_v2 |
These limits describe the localhost app.main:app path, not the separate hosted
beta under construction. The local entrypoint refuses hosted-environment startup
except for explicitly configured curated demo operation. Do not expose it behind
a key as a tenant-safe service.
External evaluation requires the current consent_version from
GET /v1/evaluation-policy on every confirmed run. It sends full recorded caller
audio to ElevenLabs and transcript comparison content to OpenAI when judging is
configured. The local policy supplies no cost estimate or allowance (null);
provider billing and a global spend cap are not managed by this path.
Recommended postures
| Setting | Sensitive calls | Synthetic debugging calls |
|---|---|---|
audio | Off unless recording is approved; no audio review when off | On |
http_bodies | Off | On |
stt_content | Off unless evaluating STT | On |
websocket_text_frames | Off | Do not rely on it for frame-content capture |
payload_max_bytes | 16384 | 65536 |
strict | False | True |
| SDK source | Pinned tag or commit | Branch is fine |
| Spool cleanup | Required | Optional |
Upload methods do not remove spool directories. The Python drainer has a separate delivered-package cleanup policy; configure it deliberately. The local dashboard has no automated retention policy. More capture improves review coverage but increases data exposure, disk use and maintenance across spools, database rows, objects and backups.