Skip to main content
A project attributes cost to one agent, team, or customer. Every request attach() or guard() records carries a project id; querying costs, logs, or exports by project is how you split a shared deployment’s bill.

Defining projects

Projects live under projects: in voicegw.yaml. The key is the project id used everywhere (CLI, API, dashboard, and the project= argument to attach()/guard()).
Projects also accept routing, branding, replay, and metrics blocks; those aren’t cost-tracking concerns and are covered in the voicegw.yaml reference. Unknown keys under a project fail config validation (typos are caught at startup, not silently ignored).

Budgets

daily_budget is a dollar cap; budget_action (warn / throttle / block) records what you want to happen once spend crosses it.
Today none of the three budget_action values change gateway behavior on their own. attach() only records spend against the cap. The dashboard, voicegw project <id>, and the MCP project tools read that spend back as a status of ok, warning (≥80% of daily_budget), or exceeded (≥100%): identical regardless of which action is configured. No call is slowed, rerouted, or rejected by budget_action alone.
To actually stop or reroute a call once a cap is hit, wrap the provider with guard() and pass its own budget="$X/day" argument. That check is a separate mechanism scoped to the guard() call itself; it does not read a project’s daily_budget/budget_action. Budget tracking (the spend that powers the status badge) requires the storage backend to be on. For attach()/guard(), that means VOICEGW_DB_PATH (local SQLite) or VOICEGW_COLLECTOR_URL + VOICEGW_API_KEY (fleet collector) set as environment variables: attach() does not read cost_tracking.enabled from voicegw.yaml. For the Gateway behind voicegw serve, cost_tracking.enabled: true in voicegw.yaml (or VOICEGW_DB_PATH / VOICEGW_DB_URL) turns storage on instead. Without storage, spend always reads as $0.

Which project a call lands under

Pass project="<id>" to attach() or guard() to tag every record from that session. Omit it and records land under the default project (auto-created on first run, $0 budget). See attach() for the exact resolution order between the argument and the VOICEGW_PROJECT environment variable.

Reading project data

CLI

voicegw projects prints a table: ID, Name, Tags, Budget/day (- if unlimited), Default Stack (- if none). With no projects configured it warns and exits 0. voicegw project <id> prints a panel with the name, description, tags, default stack, and daily budget, plus a Today: $X.XXXX (N requests) line when the storage backend is enabled. Exits 1 if the id isn’t found. Both accept --config/-c to point at a non-default voicegw.yaml.

HTTP API and dashboard

The web dashboard (voicegw dashboard) shows a per-project spend bar against daily_budget and the budget_action value as a label. Projects can also be created at runtime from the dashboard, the MCP server (voicegw mcp), or POST /v1/projects; runtime-created projects persist in SQLite and merge with YAML on startup (YAML wins on id conflicts).

Tags

Tags are arbitrary strings for filtering. The dashboard picks an accent color from the first tag (substring match, case-insensitive): contains prod → green, contains stag → yellow, contains dev or test → blue, anything else → pink.
See Models for the provider/model id format and the default_stack bundles. See Tenant attribution for per-end-user attribution within a project. See voicegw.yaml reference for the full config file shape.