Export and Delivery
Export produces CSV files for a payment system or an external biller. Delivery decides where those files go.

What is exported
Export emits typed rows, one per charge:
| Type | Source |
|---|---|
USAGE | Rated calls |
RECURRING | Seats, DIDs, licences |
ONE_TIME | Catalogue and one-off charges |
Included calls are exported as 0.00 rows rather than omitted, so the receiving system sees the traffic.
Rows carry each charge's own sell amount. Export never writes invoice totals and never writes tax — it is a charge feed, not an invoice feed.
Export is sell-only by construction. Cost prices and margin cannot reach an export file.
Properties
- Watermarked on
rated_at, never on call date, so a recovery import of last month's calls is exported once and only once. - Split per currency automatically. A fleet billing in three currencies produces three files.
- sha256 manifest alongside each file.
- Immutable. A generated file is never regenerated. A retry re-sends the stored bytes, so the receiving system can never get two different files under one sequence number.
- Sequenced. Each export carries a sequence number, recorded per charge as
exported_seq.
Delivery destinations
Delivery configures where export files are sent. Three transports:
SFTP — uploaded as .part and then renamed, so a watcher on the far side
never reads a half-written file. Host, port, user, password or key, and remote
path.
HTTP — POST or PUT with the CSV as the request body, and headers the
receiver can verify against: X-Content-SHA256, X-Filename, X-Currency
and X-Row-Count. Optional bearer token.
Email — the CSV as an attachment to one or more addresses.
Destinations marked deliver automatically receive every new export. Anything can also be delivered or retried by hand.
Attempt tracking
Every attempt is recorded per destination and per file on the export record. A delivery where some files succeeded and others failed is reported as partial, and a retry is limited to the files that actually failed.
Exported but unbilled
The charge_exported_unbilled detector finds charges that were exported but
never landed on an invoice. Those charges are otherwise permanently invisible:
the export watermark has passed them, so they will not be exported again, and
no invoice carries them.
If this alert fires, the charges need to be identified and handled manually. It is the one detector whose findings represent money that is silently gone.
Worked example
The file
One invoice's charges as an export file. Sell-only, one row per charge, no tax and no invoice totals:
seq,type,tenant,account_ref,period,description,quantity,unit,amount,currency
1041,USAGE,hq.northwind-example.com,NW-1041,2026-08,US National,1240.00,min,14.88,USD
1041,USAGE,hq.northwind-example.com,NW-1041,2026-08,United Kingdom,86.00,min,3.01,USD
1041,USAGE,hq.northwind-example.com,NW-1041,2026-08,UK Mobile,12.00,min,1.68,USD
1041,USAGE,hq.northwind-example.com,NW-1041,2026-08,US Toll Free,318.00,min,0.00,USD
1041,RECURRING,hq.northwind-example.com,NW-1041,2026-08,Extensions,42,ea,504.00,USD
1041,RECURRING,hq.northwind-example.com,NW-1041,2026-08,Hunt groups,3,ea,15.00,USD
1041,RECURRING,hq.northwind-example.com,NW-1041,2026-08,DID numbers,18,ea,27.00,USD
1041,RECURRING,hq.northwind-example.com,NW-1041,2026-08,Managed support plan,1,ea,150.00,USD
1041,ONE_TIME,hq.northwind-example.com,NW-1041,2026-08,Desk handset,1,ea,89.00,USD
The toll-free row is present at 0.00 rather than omitted, so the receiving
system sees the traffic.
A multi-currency export
A fleet billing in three currencies produces three files from one export, and a manifest:
export-1041-USD.csv 412 rows sha256 3f9a...c218
export-1041-EUR.csv 88 rows sha256 71bd...04ef
export-1041-GBP.csv 34 rows sha256 aa19...9d5c
export-1041.manifest (all three checksums)
Delivery, and a partial
Export 1041, 3 files, 2 destinations
sftp://biller.example.com/upload
export-1041-USD.csv delivered 2026-09-01 03:14:02
export-1041-EUR.csv delivered 2026-09-01 03:14:05
export-1041-GBP.csv delivered 2026-09-01 03:14:07
https://api.partner-example.com/charges
export-1041-USD.csv delivered 2026-09-01 03:14:11
export-1041-EUR.csv FAILED 504 Gateway Timeout
export-1041-GBP.csv FAILED 504 Gateway Timeout
Status: PARTIAL
A retry touches only the two failed files, and re-sends the stored bytes rather than regenerating them. The biller can never receive two different files under one sequence number.
The HTTP headers a receiver can verify
POST /charges HTTP/1.1
Host: api.partner-example.com
Authorization: Bearer <token>
Content-Type: text/csv
X-Filename: export-1041-USD.csv
X-Content-SHA256: 3f9a...c218
X-Currency: USD
X-Row-Count: 412
The body is the CSV itself. A receiver that checks X-Content-SHA256 against
what it read cannot silently ingest a truncated file.
The SFTP rename
Files are uploaded as .part and then renamed:
put export-1041-USD.csv.part
rename export-1041-USD.csv.part -> export-1041-USD.csv
A watcher on the far side polling for *.csv therefore never reads a
half-written file.
Watermarking, and why recovery does not double-export
Export selects on rated_at, never on call date.
2026-09-01 export 1041 runs, watermark advances to 2026-09-01 03:00
2026-09-04 archive CSV for 2026-08-19 imported, 240 legs rated
2026-10-01 export 1042 runs
-> includes the 240 legs from 2026-08-19
(rated 2026-09-04, after the previous watermark)
Those legs appear on the August invoice, because invoice periods are cut on when the call started. But they export in the October file, because export tracks when they were rated. Both are correct, and they are answering different questions.