Usage
Read your organization's plan, minute balance, spend, and usage history.
Usage endpoints are read-only. They report what the call pipeline has already recorded — plans, balances, and minutes are changed through the dashboard and Stripe, never through the API.
Get Usage Summary
GET /api/v1/usageRequired scope: usage:read
Returns the current plan, minutes left on the balance, minutes spent in the current billing period, and how many days are left before that period ends.
Example Request
curl -X GET "https://app.erzycall.com/api/v1/usage" \
-H "X-API-Key: ek_live_abc123"Example Response
{
"data": {
"plan": {
"id": "outreach_growth",
"name": "Outreach Caller Growth",
"productType": "outreach",
"status": "active",
"includedMins": 300,
"isTrial": false,
"isComplimentary": false,
"cancelAtPeriodEnd": false,
"cancelAt": null,
"features": {
"allowInbound": false,
"allowOutbound": true,
"allowWhatsApp": false
}
},
"balanceMins": 212,
"isLowBalance": false,
"period": {
"start": "2026-08-01T00:00:00.000Z",
"end": "2026-09-01T00:00:00.000Z",
"daysRemaining": 12,
"source": "subscription"
},
"currentPeriodUsage": {
"totalMins": 88,
"deductedMins": 88,
"totalCalls": 61,
"inbound": { "calls": 24, "mins": 40 },
"outbound": { "calls": 37, "mins": 48 },
"truncated": false
},
"trial": {
"isActive": false,
"hasEnded": true,
"expiredWithoutPlan": false,
"startedAt": "2026-06-01T09:00:00.000Z",
"endsAt": "2026-06-04T09:00:00.000Z",
"daysRemaining": null,
"grantedMins": 800
},
"subscriptions": [
{
"id": "bs_001",
"planId": "outreach_growth",
"planName": "Outreach Caller Growth",
"productType": "outreach",
"status": "active",
"currentPeriodStart": "2026-08-01T00:00:00.000Z",
"currentPeriodEnd": "2026-09-01T00:00:00.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null
}
]
}
}Reading the numbers
| Field | Meaning |
|---|---|
balanceMins | Minutes left to spend right now |
currentPeriodUsage.totalMins | Talk time this period, whether or not it came off the balance |
currentPeriodUsage.deductedMins | The part of that talk time that actually reduced the balance |
period.daysRemaining | Whole days until the period ends, rounded up, floored at 0 |
plan.includedMins | Minutes the plan grants each renewal — not a live balance |
plan.cancelAt | When the plan actually ends. null while it keeps renewing — period.end is the renewal date |
totalMins and deductedMins differ when a plan covers a call outright: inbound calls on AI Receptionist and every call on Complimentary consume talk time without reducing the balance.
trial.hasEnded is the literal fact that the trial's end date has passed — it stays true for an organization that went on to subscribe. trial.expiredWithoutPlan is the narrower signal that the trial ended and nothing replaced it, which is when the organization has no entitlement left.
period.source says what the window means:
subscription— the active subscription's billing period.trial— the free-trial window, for an organization still inside it.rolling_30d— the organization has no period this API can name, so usage is summed over the last 30 days.
period.end is never a date that has already passed. An organization whose subscription lapsed or whose trial expired falls to rolling_30d rather than reporting the window that has already closed.
A null end means two different things depending on the source. Under subscription it means the period's end was never recorded — some legacy and manually linked subscriptions carry a start with no end — so the window is real but open-ended. Under rolling_30d it means there is no billing period at all. period.daysRemaining is null in both cases, and period.start always states the exact window the usage totals cover.
currentPeriodUsage.truncated is true only for an organization with more than 10,000 billing rows in one period. The totals then cover the most recent rows and are a floor — page /usage/history for the exact ledger.
List Usage History
GET /api/v1/usage/historyRequired scope: usage:read
Every balance movement, newest first: call usage, plan renewals, minute top-ups, coupon redemptions, and admin adjustments.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Results per page, 1–100. Defaults to 25. |
cursor | string | Cursor from the previous page of this same query. |
type | string | Return only one kind of movement — see below. |
from | string | ISO 8601 timestamp; only rows created at or after it. |
to | string | ISO 8601 timestamp; only rows created at or before it. |
Valid type values: inbound_call, outbound_call, monthly_renewal, addon_purchase, admin_adjustment, coupon_redemption, trial_activation, trial_conversion, trial_expiration.
A cursor belongs to the exact filter set that produced it. Change type, from, or to and start over without a cursor — reusing one across a filter change returns 400 INVALID_CURSOR.
Example Request
curl -X GET "https://app.erzycall.com/api/v1/usage/history?type=outbound_call&limit=2" \
-H "X-API-Key: ek_live_abc123"Example Response
{
"data": [
{
"id": "bt_002",
"type": "outbound_call",
"amountMins": -2,
"balanceAfterMins": 212,
"durationMins": 2,
"includedInPlan": false,
"description": "Outbound call usage: 2 min(s)",
"productType": "outreach",
"providerCallId": "b7c2f4e1-2a55-4d0e-9c31-6f8a2d1e4b90",
"createdAt": "2026-08-19T14:05:00.000Z"
},
{
"id": "bt_001",
"type": "outbound_call",
"amountMins": -1,
"balanceAfterMins": 214,
"durationMins": 1,
"includedInPlan": false,
"description": "Outbound call usage: 1 min(s)",
"productType": "outreach",
"providerCallId": "3f9a1c08-7d64-4b12-8e05-1a7c9b3d2f45",
"createdAt": "2026-08-19T11:42:00.000Z"
}
],
"pagination": {
"cursor": "u1a2b3c4.eyJ...",
"hasMore": true,
"pageSize": 2
}
}Row fields
| Field | Meaning |
|---|---|
amountMins | Signed as stored — negative consumed minutes, positive granted them |
balanceAfterMins | The balance immediately after this movement |
durationMins | Talk time for a call row; null for grants and adjustments |
includedInPlan | true when the plan covered the call, so amountMins is 0 while durationMins is not |
providerCallId | The telephony provider's ID for the call this row bills. null on non-call rows |
Correlating a spend row with its call
providerCallId is the same value the calls resource publishes as apiCallId — match on that to find the call behind a charge:
curl -X GET "https://app.erzycall.com/api/v1/calls?from=2026-08-01T00:00:00Z" \
-H "X-API-Key: ek_live_abc123"
# then match calls[].apiCallId against providerCallId from the history rowproviderCallId is not a Convex document ID, so GET /calls/{callId} will not accept it — that endpoint takes the id field from the calls resource. The ledger stores the provider's reference, and translating it here would mean reading a full page of call documents (which carry transcripts and raw provider payloads) on every request, so the API publishes the reference it has rather than paying that cost.
Rows are returned newest first. When type is set, a page can come back shorter than limit while hasMore is still true — keep following the cursor until hasMore is false.