Reconcile File Formats
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.
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):
Field semantics
| Field | Required | Notes |
|---|---|---|
model | yes | OpenAI model id without the openai/ prefix. VoiceGateway prepends the prefix when matching against its own logs. |
input_tokens | yes | Aggregate prompt/context tokens across the reconcile window. Set to 0 if you only have output counts. |
output_tokens | yes | Aggregate generated tokens. Set to 0 if not applicable. |
n_requests | optional | Carried through to the diff output’s per-row metadata for cross-checking VG’s request count against the provider’s. Omit if your export does not include it. |
cost_usd | yes | Aggregate cost OpenAI charged for that model in the window. The reconcile diff is computed against this number. |
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(orsnapshot_id): the model id.n_context_tokens_total: maps toinput_tokensin VoiceGateway’s schema.n_generated_tokens_total: maps tooutput_tokens.n_requests: maps ton_requests.cost_total_usd: maps tocost_usd. If the dashboard CSV does not include this column directly, sum thecost_input_usdandcost_output_usdcolumns.
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>.
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-invoicegw 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):
Field semantics
| Field | Required | Notes |
|---|---|---|
model | yes | Deepgram model id without the deepgram/ prefix. VoiceGateway prepends the prefix when matching against its own logs. |
audio_seconds | yes | Aggregate transcribed audio duration, in seconds, across the reconcile window. Deepgram bills per-minute, so audio-minutes from the dashboard multiplied by 60 is the value to use. Float allowed. |
n_requests | optional | Carried through to the diff output’s per-row metadata for cross-checking VG’s request count against the provider’s. Omit if your export does not include it. |
cost_usd | yes | Aggregate cost Deepgram charged for that model in the window. The reconcile diff is computed against this number. |
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(ormodel_name): the model id.seconds_total(orduration_seconds_total): maps toaudio_seconds. If your export reports minutes, multiply by 60.requests_total: maps ton_requests.total_cost_usd(oramount_usd): maps tocost_usd.
convert-deepgram.py and invoke it as
python convert-deepgram.py <deepgram-export.csv> <vg-format.csv>.
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 unitlivekit-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):
Field semantics
| Field | Required | Notes |
|---|---|---|
model | yes | Cartesia model id without the cartesia/ prefix. VoiceGateway prepends the prefix when matching against its own logs. |
characters | yes | Aggregate synthesized character count across the reconcile window. This is what VG records (the unit livekit-plugins-cartesia emits on its usage_collected event), so the reconcile diff against VG’s logs uses this column. Set to 0 if your export only ships credits. |
credits | optional | Aggregate Cartesia credits consumed in the window. Cartesia’s billing portal exposes credits as the primary unit; surfacing them here lets reconcile cross-check the credits-to-USD math even when characters are absent. |
n_requests | optional | Carried through to the diff output’s per-row metadata for cross-checking VG’s request count against the provider’s. Omit if your export does not include it. |
cost_usd | yes | Aggregate cost Cartesia charged for that model in the window. The reconcile diff is computed against this number. Convert credits-to-USD via your account’s rate sheet (see below). |
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(ormodel_id): the model id.chars_synthesized(orcharacters_total): maps tocharacters.credits_used(orcredits_consumed): maps tocredits.requests(orn_requests): maps ton_requests.cost_usd(ortotal_cost): maps tocost_usd.
convert-cartesia.py and invoke
as python convert-cartesia.py <cartesia-export.csv> <vg-format.csv>.
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’susage_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).
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.