Billing: usage, call records, balance, ledger
Read-only views of what the project spends. Everything is the project in the path only. Top-ups and payment
methods stay in the portal. Paths are relative to https://api.contactivity.io/v1/projects/{project_id}; see the
overview. Scope: billing:read.
| Method | Path | Purpose |
|---|---|---|
| GET | /usage |
Usage and cost by product over a period |
| GET | /cdrs |
Call detail records (paged) |
| GET | /balance |
Current balance |
| GET | /ledger |
Balance changes (paged) |
from and to (on /usage and /cdrs) take RFC 3339 or YYYY-MM-DD (00:00 UTC). from defaults to the start
of the current UTC month, to to one month after from; the range is [from, to) and at most 366 days
(400 invalid_parameter, param from or to).
Usage
curl "https://api.contactivity.io/v1/projects/$PROJECT_ID/usage?from=2026-10-01&to=2026-11-01" \
-H "Authorization: Bearer $CONTACTIVITY_API_KEY"{
"from": "2026-10-01T00:00:00.000Z", "to": "2026-11-01T00:00:00.000Z",
"products": [
{"product": "programmable_voice_outbound", "count": 412, "duration_secs": 30215, "cost": {"amount": "4.1230", "currency": "USD"}}
],
"total_cost": {"amount": "4.1230", "currency": "USD"}
}Call lines are <product>_inbound and <product>_outbound for each product, for example
sip_trunking_outbound (from the call records); other lines are the ledger types of other charges
(number fees, features). Errors: 400 invalid_parameter, 503 usage_unavailable.
Products
Each call leg is billed on its own, as one product:
| Product | Legs |
|---|---|
sip_trunking |
Between your SIP trunk or PBX and the phone network |
programmable_voice |
Phone network legs handled by a voice app (CoXML, Call Control, flow) or the Calls API |
programmable_voice_sip |
Voice app legs that never reach the phone network: SIP addresses, in-app clients, your own trunks. One rate for every destination |
byoc_sip_trunking |
Trunk legs through your own carrier. Platform fee only, the same both ways |
byoc_programmable_voice |
Voice app legs through your own carrier. Platform fee only, the same both ways |
A call that uses several of them is billed for each leg: a number routed to a voice app that dials your trunk is
one programmable_voice leg and one programmable_voice_sip leg, and a number forwarded to a phone number is two
sip_trunking legs: inbound at the number's inbound rate and outbound at the forwarding destination's rate, both for
the whole call (a BYOC number forwarded through your carrier: two byoc_sip_trunking legs, the fee each way). Legs are billed per 60 seconds, rounded up,
unless your agreement says otherwise; each rate's increment is under GET /pricing.
Calls between Contactivity numbers stay on our network and are billed at the programmable_voice_sip rate, never
at phone network rates. A number forwarded to another Contactivity number (yours or another customer's) pays its
inbound leg as usual and a programmable_voice_sip leg to that number; the number it reaches pays a
programmable_voice_sip inbound leg on its own project. In a chain of forwards (A to B to C) each number pays for its own legs:
A its programmable_voice_sip leg out, B one in and one out, C one in; a chain that ends at a phone number pays
that outbound leg at its sip_trunking rate. When the project a call would be billed to on the receiving side has no
credit, the caller hears that the number is not available (480); your own calls out of a project without credit
get 402. A call that goes on to a route's fallback route is checked against your balance again before it rings,
at the fallback's price; without enough credit it ends with 480.
Transactions show amounts to 4 decimals, rounded up: a charge below $0.0001 shows as $0.0001. Your balance and
the amounts we store keep 6 decimals. A call from your trunk or a voice app to a Contactivity number is a programmable_voice_sip
leg for you and one for the number's project. More than 3 numbers forwarding in a row is a loop: the call is
refused with 482 Loop Detected and nothing is charged.
Standard prices
GET https://api.contactivity.io/v1/public/pricing returns the platform's standard prices, with no API key: voice
per product (sip_trunking and programmable_voice per +1 region and direction, the others one rate
per minute), and US and Canada local and toll-free numbers. It never includes your own terms;
your project's prices are GET /pricing.
curl "https://api.contactivity.io/v1/public/pricing"{
"currency": "USD", "billing_increment": "60/60",
"voice": {
"sip_trunking": {"regions": [{"id": "us48", "name": "US48",
"outbound": {"rate_per_min": {"amount": "0.009000", "currency": "USD"}, "increment": "60/60"},
"inbound": {"rate_per_min": {"amount": "0.005000", "currency": "USD"}, "increment": "60/60"}}]},
"programmable_voice_sip": {"rate_per_min": {"amount": "0.004000", "currency": "USD"}, "increment": "60/60"},
"byoc_programmable_voice": {"rate_per_min": null, "increment": "60/60"}
},
"numbers": [{"country": "US", "type": "local", "available": true,
"setup_fee": {"amount": "1.000000", "currency": "USD"}, "monthly_fee": {"amount": "1.150000", "currency": "USD"}}]
}A price that is not set is null; a number with "available": false is not sold. Responses are cached for 5 minutes (Cache-Control: public, max-age=300)
and rate limited per IP address (429 with Retry-After).
Each region also lists its prefixes, the +1 area codes it covers (+1 holds every area code not in another
region). features has the call feature prices: transcription and noise_suppression per minute,
machine_detection and premium_machine_detection per attempt (price_per_attempt), recording per recorded minute and
recording_storage per stored minute per month (rate_per_min_month) after free_days (see
Recording and storage), live_transcription per minute (see
Live transcription), and speech_recognition per 15 seconds (price_per_15_secs) only
when it is priced. A detection attempt is
charged each time detection runs on a call, whatever the result.
International prices
GET https://api.contactivity.io/v1/public/pricing/international?q= searches the standard international outbound
prices of sip_trunking and programmable_voice, also with no API key. q is a destination name (2-60
characters, matched anywhere in the name), a prefix such as +44, or a whole number, which returns every prefix of
it. Up to 100 destinations, by name; has_more is true when more match. A product without a standard price for a
destination is null. Destinations under a blocked prefix are left out, and your own terms are never included.
curl "https://api.contactivity.io/v1/public/pricing/international?q=united%20kingdom"{
"currency": "USD", "billing_increment": "60/60", "has_more": false,
"destinations": [{"prefix": "+44", "destination": "United Kingdom",
"sip_trunking": {"rate_per_min": {"amount": "0.020000", "currency": "USD"}, "increment": "60/60"},
"programmable_voice": {"rate_per_min": {"amount": "0.025000", "currency": "USD"}, "increment": "60/60"}}]
}Calling these destinations needs account tier 3 and an approved international calling request
(POST /international-requests). Errors: 400 invalid_parameter (q missing, too short or too long),
429 rate_limit. Cached for 5 minutes, like the standard prices.
Recording and storage
Call recordings are a software charge, separate from call minutes (ledger types recording and
recording_storage):
- Recording: charged once when a recording completes, whatever started it (
<Record>,<Dial record>,record_start, conference and trunk recording, voicemail): its duration rounded up to the billing increment (60/60) at the recording price per minute. A dual-channel recording counts its minutes once. The charge is the recording'spriceand shows in the call's pricing. - Storage: free for
free_days(90) from the recording's creation. After that, each calendar month (UTC) a stored recording is charged for the whole month: its billed minutes times the storage price per minute-month, once per recording and month, at the first hourly run after its free period ends. Archived recordings are charged too: recordings are never deleted. Billing contacts get one notice per project and month.
Amounts are rounded to 4 decimals; a charge that rounds to $0 is not booked. Organizations can have their own
recording and storage prices; GET /pricing shows them under recording.
Live transcription
Live transcription (<Start><Transcription>, transcription_start) is charged per minute of streamed audio,
rounded up to the billing increment, once per session and track (ledger type live_transcription). Dual-track
transcription counts each track. Organizations can have their own price, shown under recording.live_transcription
in GET /pricing. Speech recognition in a speech <Gather> (ledger type
speech_recognition) is charged per 15 seconds, at least one block per gather, when priced.
Call detail records
GET /cdrs lists rated call legs, newest first. Filters: from, to, direction (inbound, outbound), product,
status (all, answered, missed), number (digits matched anywhere in the caller or destination),
min_sec, min_cost, limit, starting_after.
| Field | Type | Description |
|---|---|---|
id |
string | |
sip_call_id |
string | The leg's SIP Call-ID |
direction |
string | inbound or outbound |
from, to |
string | |
started_at, answered_at |
timestamp | answered_at null when unanswered |
answered |
boolean | |
duration_secs |
integer | |
price |
money or null | |
product |
string | See Products |
hangup_cause |
string | |
quality |
object or null | Media quality per leg |
Errors: 400 invalid_parameter, 503 cdrs_unavailable.
Balance
GET /balance returns {"balance": {"amount": "125.40", "currency": "USD"}} (2 decimals). Errors:
503 balance_unavailable.
Ledger
GET /ledger lists balance changes, newest first; filter type (for example topup, refund, adjustment,
number_monthly, recording, recording_storage, live_transcription, speech_recognition).
| Field | Type | Description |
|---|---|---|
id |
string | |
type |
string | |
amount |
money | Always positive |
direction |
string | credit or debit |
description |
string or null | |
status |
string | succeeded, or pending until applied to the balance |
created_at |
timestamp |
Prices per destination are under GET /pricing (Voice apps).

