Analytics & usage API

Reporting for your organization: metered usage and quotas, billing, call analytics, and the live wallboard. Usage and call analytics are organization-scoped (API key or JWT + X-Org-Id).

Usage & quotas

GET /api/orgs/{orgId}/usage

Returns the organization's metered usage for the current billing period — the calendar month in IST (the platform's billing timezone), returned as a UTC instant — plus the configured limits, in one call. Any org member may read it.

{
  "success": true,
  "data": {
    "period_start": "2026-06-01T00:00:00Z",
    "calls": 1280,
    "minutes": 4310,
    "active_calls": 3,
    "limits": {
      "monthly_call_limit": null,
      "monthly_minute_limit": 10000,
      "max_concurrent_calls": 5,
      "enforce": true
    }
  }
}
FieldMeaning
period_startStart of the current period (the IST calendar month, returned as a UTC instant).
callsBillable calls so far this period.
minutesConnected talk minutes so far this period.
active_callsLive concurrent calls right now (age-bounded so a crashed session can't wedge the gate).
limitsThe configured quotas (see below).

The snapshot fields are snake_case. A null limit means unlimited for that dimension. When there's no limits row at all, every limit reads null with enforce: true (so a later-set limit takes effect immediately).

Updating limits

PUT /api/orgs/{orgId}/usage/limits

Owners/admins only. This is a replace (PUT) — the body is the complete desired limit state, so send every field; omit a field (or send null) to make that dimension unlimited.

curl -X PUT "https://api.telenow.ai/api/orgs/{orgId}/usage/limits" \
  -H "x-api-key: vai_live_…" -H "Content-Type: application/json" \
  -d '{
        "monthlyCallLimit": null,
        "monthlyMinuteLimit": 10000,
        "maxConcurrentCalls": 10,
        "enforce": true
      }'
Body field (camelCase)TypeMeaning
monthlyCallLimitint / nullCalls allowed per period. null = unlimited.
monthlyMinuteLimitint / nullMinutes allowed per period. null = unlimited.
maxConcurrentCallsint / nullSimultaneous live calls. null = unlimited.
enforcebooltrue blocks calls at the limit; false meters only (observe-only). Defaults to true.

Notes:

  • The request accepts camelCase field names (snake_case aliases also work). A value of 0 hard-pauses that dimension; negative values are rejected with 400.
  • The response echoes the saved limits in snake_case (matching the limits block in the usage snapshot).

Enforcement: 429 vs 403

Limits are enforced at dial time, before any carrier is contacted. When a call is refused:

Limit hitStatusWhy
Concurrency — per number, per workspace (max_concurrent_calls), or browser429 Too Many RequestsTransient — a slot frees the moment a live call ends. The body names which ceiling in scope and how long to wait in retryAfter.
Monthly call / minute quota403 ForbiddenNot transient — raise the limit or wait for the next billing cycle.
Spend cap reached or account suspended403 ForbiddenA billing gate that runs ahead of both the concurrency and quota checks.

A 429 body:

{ "success": false, "error": "…", "retryAfter": 240,
  "reason": "number_at_capacity", "scope": "number",
  "cap": 2, "active": 2, "position": 3, "medianCallSecs": 120 }

scope is number, org or browser — it tells you which ceiling refused you, and therefore whether it is yours to raise (org, under Usage → Edit limits) or your platform admin's. retryAfter is also sent as the standard Retry-After header and differs per caller so a backlog spreads out; see Concurrency limits.

The concurrency caps also bound anonymous widget sessions, protecting your spend from public traffic.

See Usage & billing for the dashboard view.

Billing

The money endpoints behind the dashboard's Billing page, for building your own dashboards, alerts, or billing automations. Billing routes are owner/admin only (JWT).

MethodPathPurpose
GET/api/orgs/{orgId}/billing/summaryPeriod payable, breakdown, per-day, account balance
GET/api/orgs/{orgId}/billing/breakdownCharges by agent × call type
GET/api/orgs/{orgId}/billing/invoicesInvoice history with line items
GET/api/orgs/{orgId}/billing/ledgerLedger entries, newest first — paged with ?limit= (max 200, default 50) and ?offset=; returns { "entries": [...], "total": N, "limit": N, "offset": N }
POST/api/orgs/{orgId}/billing/invoices/{invoiceId}/payCreate a Razorpay order for an unpaid invoice
POST/api/orgs/{orgId}/billing/topupBuy credits (prepaid) — body { "amountInr": 1000 }, range ₹50–₹10,00,000

The usage snapshot and limits are under Usage & quotas above. See Usage & billing for prepaid vs. postpaid, the charge components, and invoices.

Call analytics

GET /api/orgs/{orgId}/analytics

Aggregated, org-wide call analytics over a date window: call volume per day, an outcome breakdown, total/average duration, average agent response latency, and a per-agent rollup. Any org member may read. The same endpoint powers the agent Analysis tab — the tab just passes agentId.

Query paramPurpose
from, toDate range (YYYY-MM-DD, both optional). Defaults to the last 30 days; to defaults to today (UTC). Both dates are inclusive — calls on the to date are counted. (Internally the to date is advanced to the start of the next day and the query is start_time >= from AND start_time < to+1day, i.e. a half-open timestamp window — which is why the resolved window below shows to as an exclusive timestamp.)
agentIdLimit to one agent (optional, UUID). Omit for all agents.

The reportable span is capped at 366 days (a hand-crafted request for a larger range returns 400). from must be on or before to. A malformed date (not YYYY-MM-DD) or a from after to also returns 400.

curl "https://api.telenow.ai/api/orgs/{orgId}/analytics?from=2026-06-01&to=2026-06-30" \
  -H "x-api-key: vai_live_…"

A response for from=2026-05-01&to=2026-05-31, in the standard { success, data } envelope:

{
  "success": true,
  "data": {
    "from": "2026-05-01T00:00:00Z",
    "to": "2026-06-01T00:00:00Z",
    "totalCalls": 1280,
    "completed": 1041,
    "missed": 198,
    "inProgress": 41,
    "machineAnswered": 73,
    "totalSeconds": 254880,
    "avgSeconds": 244.8,
    "avgResponseMs": 920,
    "daily": [
      { "day": "2026-05-01", "calls": 44, "completed": 38, "missed": 5, "seconds": 9120 }
    ],
    "agents": [
      { "agentId": "…", "agentName": "Support", "calls": 720, "completed": 612, "seconds": 150480 }
    ]
  }
}

The data object is camelCase and includes:

FieldMeaning
from, toThe resolved window as ISO timestamps (half-open: from inclusive, to exclusive — to is the start of the day after your end date).
totalCalls, completed, missed, inProgressCall counts. Outcomes are derived from session status + duration (completed = ended with duration > 0; missed = ended with zero duration; in-progress = still active).
machineAnsweredCalls the carrier's answering-machine detection flagged (0 when AMD was never enabled). A subset of totalCalls, not a fourth category.
totalSecondsTotal talk time of every call in the window.
avgSecondsMean duration of completed calls only.
avgResponseMsMean agent response latency (ms) across measured calls (0 when none in range was measured).
daily[]Per-day series, one point per calendar day: { day, calls, completed, missed, seconds }.
agents[]Per-agent rollup: { agentId, agentName, calls, completed, seconds }. agentName is null for a deleted agent.

For the dashboard view, the KPIs, charts, and how each outcome is derived, see Analytics dashboard. Per-call AI insights are documented under Post-call analysis.

Post-call analysis

One call's post-call analysis, and what analysis has cost your organization:

MethodPathReturns
GET/api/orgs/{orgId}/analysis/result/{sessionId}The call's analysis object, or data: null when the call has none
GET/api/orgs/{orgId}/analysis/cost?from&to{ from, to, costUsd }: the platform-AI cost billed to the organization in the window
  • Signed-in users only. Both need a user JWT from a member of the organization; an API key gets 401. A server-to-server integration gets each analysis from the call.analyzed webhook instead.
  • from and to are ISO timestamps. to defaults to now, and from to 30 days before to.
  • data is null until the analysis has run, for a call whose agent has analysis off, and for a call in another organization.
  • Every field of the analysis object, its empty values, and how the webhook's shape differs: API responses → The post-call analysis object.

Agent analysis

The routes behind the agent Analysis tab's post-call-analysis insights and its Caller memory list. These are dashboard routes — { success, data } envelope. Authenticate with an API key, or a user JWT plus the X-Org-Id header (the same one /agents/:id/stats uses):

MethodPathReturns
GET/api/agents/{id}/analysis/rollup?from&toKPIs + sentiment trend, disposition breakdown, top objections, top topics/keywords, talk-ratio word totals
GET/api/agents/{id}/analysis/calls?from&to&limitRecent analyzed calls, newest first (limit 1–200, default 50)
GET/api/agents/{id}/analysis/memoryOrg-shared caller memory rows
DELETE/api/agents/{id}/analysis/memory?caller={callerKey}Erase one caller's memory → { removed }

from and to are ISO timestamps (not date strings). to defaults to now, and from to 30 days before to. Only finished analyses count.

The rollup response is { from, to, rollup }, where rollup carries:

FieldMeaning
totalAnalyzed calls in the window
avgScoreMean judge score, or null when no call has one
hallucinationCallsCalls with at least one hallucination flag
positive, neutral, negativeCalls per sentiment
agentWords, customerWordsTalk-ratio word totals
sentimentTrend[]{ day, positive, neutral, negative } per day
dispositionBreakdown[]{ disposition, count }
topObjections[]{ objection, count }
topTopics[], topKeywords[]{ tag, count }

A calls row is { sessionId, createdAt, sentiment, disposition, score, summary, hallucinationCount }. Both read empty until post-call analysis has run on at least one call in the window. For the per-field meaning of the underlying analysis, see Post-call analysis.

Wallboard

GET /api/orgs/{orgId}/wallboard?since={ISO8601}

The live-ops snapshot behind the dashboard's Wallboard: team presence, calls in progress, and today's totals. Readable by any org member.

since is the client's local midnight (the Wallboard page sends it automatically); omit it and the backend falls back to UTC midnight, which may not match your timezone.

{
  "success": true,
  "data": {
    "now": "2026-06-13T10:22:05Z",
    "members": [
      {
        "userId": "…", "name": "Sarah Chen", "email": "[email protected]",
        "role": "admin", "receiveCalls": true,
        "live": true, "lastSeenMs": 1749810120000, "onCallId": null
      }
    ],
    "activeCalls": [
      {
        "id": "…", "agentName": "Support", "callMode": "ai",
        "from": "+14155550100", "to": "+14155550111",
        "startedAt": "2026-06-13T10:19:40Z", "initiatedByEmail": null
      }
    ],
    "today": {
      "total": 88, "completed": 71, "missed": 12,
      "inProgress": 5, "totalSeconds": 21340, "avgSeconds": 300.6
    }
  }
}
FieldMeaning
members[].livetrue when the member is present within the freshness window.
members[].lastSeenMsLast-seen epoch ms (drives the "Seen N ago" label), or null.
members[].onCallIdSession id of the manual call this member is on, or null.
activeCalls[].callModemanual for a softphone call; anything else is an AI agent call.
todayThe same of-today totals shown in the Wallboard's KPI band.

Dial aggregates

/api/orgs/{orgId}/dial

Aggregates that span calls, backing the Dial page. User JWT + X-Org-Id.

MethodPathPurpose
GET/statsOutcome breakdown — total, answered, machine, not-answered, rejected
GET/followupsFollow-ups across all of this caller's calls, each with light call context
GET/number-summaryPer-number rollup
GET/number-callsCalls for one number
GET/missed-callsMissed inbound calls

These default to the calling user's own activity, not the organization's. The page they back is "my dialling", so a report built on them will silently under-count unless you account for that. Both /stats and /followups take an optional YYYY-MM-DD date window applied to the call start time. For org-wide figures use the analytics endpoints above.