Skip to main content
voicegw reconcile compares VoiceGateway’s recorded costs against a provider’s usage export. Different providers ship different exports, so VoiceGateway defines one canonical reconcile-input format per provider, and documents how to produce that format from each provider’s native export. This page is the schema reference. The walkthrough that ties it to the day-to-day reconciliation workflow lives at Cost Reconciliation.
Reference fixturesWorking examples of each provider’s canonical schema live in the repo at src/voicegateway/tests/fixtures/usage_exports/:
  • openai-sample.csv (3 LLM models, 4M tokens)
  • deepgram-sample.csv (3 STT models, 5 hours of audio)
  • cartesia-sample.csv (2 TTS models, 3.3M characters)
Diff your converter output against these files to confirm schema parity before running reconcile against your own usage.

OpenAI

Canonical input shape

voicegw reconcile --provider openai --provider-usage-file <FILE> expects either CSV or JSON. The format is auto-detected from the file extension; the schemas are equivalent. CSV (header row required, column order does not matter):
JSON (top-level array of objects):

Field semantics

Cached tokens, audio tokens, and embedding-model lines (if present in your export) are not in this schema. Drop those rows before running reconcile, or include them with their own model id (e.g., gpt-4o-mini-audio-preview) and let VG report them as unmatched.

Producing the canonical format from the OpenAI dashboard

The OpenAI usage dashboard at platform.openai.com/usage ships a “Download CSV” button. Its column set varies over time; the columns this guide assumes are stable:
  • model (or snapshot_id): the model id.
  • n_context_tokens_total: maps to input_tokens in VoiceGateway’s schema.
  • n_generated_tokens_total: maps to output_tokens.
  • n_requests: maps to n_requests.
  • cost_total_usd: maps to cost_usd. If the dashboard CSV does not include this column directly, sum the cost_input_usd and cost_output_usd columns.
A short Python conversion (one-time, drop alongside your VG checkout): Save the snippet below as convert-openai.py and invoke it with the source export and the desired destination filename: python convert-openai.py <openai-export.csv> <vg-format.csv>.
If your OpenAI export schema differs from the column names above, the parser will surface the column names that are present so you can adjust the conversion. Open an issue at github.com/mahimailabs/voicegateway if your export shape diverges enough that this conversion is painful; we treat reconcile-format friction as a reconciliation bug.

Why a normalized format and not a direct dashboard parser

OpenAI’s dashboard CSV columns have changed during 2025-2026 as new modalities (audio, embeddings, batch) shipped. A direct parser inside VoiceGateway would tie us to whatever shape was current the week we shipped. The normalized format is small enough that the conversion above is a few lines of Python, and stable enough that VoiceGateway’s reconcile semantics do not regress when OpenAI changes their export. When real users surface that the conversion is annoying, we will ship a built-in voicegw reconcile-import openai <NATIVE-FILE> helper. Until then: the small Python snippet is the contract.

Deepgram

Canonical input shape

voicegw reconcile --provider deepgram --provider-usage-file <FILE> expects either CSV or JSON. The format is auto-detected from the file extension; the schemas are equivalent. CSV (header row required, column order does not matter):
JSON (top-level array of objects):

Field semantics

Real-time vs pre-recorded vs streaming distinctions are not in this schema. Sum the durations across all delivery modes for a given model into a single row; if your account uses different rate cards per mode, split into separate model rows (e.g., nova-3-realtime, nova-3-prerecorded) and record those same suffixed names in voicegw.yaml so VG’s logs match.

Producing the canonical format from the Deepgram console

Deepgram’s console exposes a usage page with per-model rollups. Two paths to the canonical CSV: Path A: console export. Click “Export CSV” on the Usage page for your billing window. The exported columns this guide assumes:
  • model (or model_name): the model id.
  • seconds_total (or duration_seconds_total): maps to audio_seconds. If your export reports minutes, multiply by 60.
  • requests_total: maps to n_requests.
  • total_cost_usd (or amount_usd): maps to cost_usd.
Path B: management API. The GET /v1/projects//usage/requests endpoint returns per-request rows; aggregate them per-model client-side. A short Python conversion for Path A: Save the snippet below as convert-deepgram.py and invoke it as python convert-deepgram.py <deepgram-export.csv> <vg-format.csv>.
If your Deepgram export reports minutes instead of seconds, replace float(row.get("seconds_total", 0)) with float(row.get("minutes_total", 0)) * 60.

Why audio_seconds and not minutes

Deepgram’s billing dashboards display minutes by default, but VG records audio duration in seconds (the unit livekit-plugins-deepgram emits on its usage_collected event, and the unit src/voicegateway/pricing/stt.py calculates against). Storing seconds in the canonical reconcile file keeps both sides of the comparison in the same unit. If your export hands you minutes, the conversion above multiplies in.

Cartesia

Canonical input shape

voicegw reconcile --provider cartesia --provider-usage-file <FILE> expects either CSV or JSON. The format is auto-detected from the file extension; the schemas are equivalent. CSV (header row required, column order does not matter):
JSON (top-level array of objects):

Field semantics

Voice-id selection (Cartesia lets you switch voices per-request) is not in this schema. Voice id does not affect billing in Cartesia’s current pricing; aggregate across all voices for a given model into a single row. If a future Cartesia rate card differentiates by voice, split into suffixed model rows (e.g., sonic-3-staging, sonic-3-production) and mirror those names in voicegw.yaml.

Producing the canonical format from the Cartesia portal

Cartesia’s billing portal lists usage by model with both a character count and a credits column. The portal CSV columns this guide assumes:
  • model (or model_id): the model id.
  • chars_synthesized (or characters_total): maps to characters.
  • credits_used (or credits_consumed): maps to credits.
  • requests (or n_requests): maps to n_requests.
  • cost_usd (or total_cost): maps to cost_usd.
A short Python conversion. Save as convert-cartesia.py and invoke as python convert-cartesia.py <cartesia-export.csv> <vg-format.csv>.
If your Cartesia portal export does not ship cost_usd directly, multiply credits_used by your account’s USD-per-credit rate (visible on the billing portal’s rate sheet) and write that into cost_usd.

Why both characters and credits

Cartesia is currently credit-based: the billing portal’s primary unit is credits, and the credits-to-USD conversion depends on the account’s plan tier. VG records characters (the LiveKit plugin’s usage_collected event ships character counts, not credits) and calculates an estimated cost via a documented per-character rate in src/voicegateway/pricing/tts.py. Surfacing both columns lets reconcile report two diffs:
  • VG’s character-count vs Cartesia’s character-count (a units check).
  • VG’s calculated USD vs Cartesia’s billed USD (the cost diff).
If the units agree but the dollars disagree, VG’s per-character rate in pricing/tts.py is stale relative to your plan; refresh that catalog entry and re-run. If your account is invoiced as flat-USD (not credits), set credits = 0 and only the cost diff is meaningful.

Other providers

Reconcile schemas for Anthropic, ElevenLabs, AssemblyAI, and additional providers can be added in a future release. Open an issue at github.com/mahimailabs/voicegateway if you need a provider that is not listed here.