English
English
Appearance
English
English
Appearance
A group of 9 reports for the Telesales module: dashboard overview, disposition breakdown, agent performance, funnel, caller-ID health, hour x day-of-week heatmap, lead-source attribution, time series and abandon rate. All support CSV/Excel export.
Every endpoint shares the following parameters (UTC ISO 8601):
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
campaign_id | integer | optional | Filter by campaign | |
date_from | datetime | YES | Start timestamp (ISO 8601 UTC) | |
date_to | datetime | YES | End timestamp (ISO 8601 UTC) | date_from..date_to <= 90 days |
agent_ids[] | array<integer> | optional | Multi-select agent filter (?agent_ids[]=25&agent_ids[]=26) | |
team_id | integer | optional | Filter by team | |
timezone | string | optional | Group by day/hour using this timezone | TZDB name (UTC, Asia/Ho_Chi_Minh, ...) - default UTC |
format | string | optional | Response format | json (default) / csv / xlsx (see 9.2) |
Common 422 error - range exceeds 90 days:
{
"message": "The given data was invalid.",
"errors": { "date_to": ["The date_from..date_to range may not exceed 90 days."] }
}| Endpoint | Content |
|---|---|
GET /api/telesales/reports/dashboard | Overall KPIs: dials, answer rate, AHT, conversion |
GET /api/telesales/reports/disposition-breakdown | Percentage breakdown per disposition code |
GET /api/telesales/reports/agent-performance | Per-agent KPIs: calls, conversions, AHT, talk time |
GET /api/telesales/reports/funnel | Funnel: Lead -> Dialed -> Connected -> Talked > 30 s -> Converted |
GET /api/telesales/reports/caller-id-health | Caller-ID rotation health (ASR, short-call ratio, carrier) |
GET /api/telesales/reports/hour-dow-heatmap | 24-hour x 7-day matrix (best calling windows) |
GET /api/telesales/reports/lead-source-attribution | Conversion by lead source |
GET /api/telesales/reports/time-series | Time series by ?group_by=day|week|hour |
GET /api/telesales/reports/abandon-rate | Realtime abandon rate (PDPL/TCPA gate) |
GET /api/telesales/reports/dashboard?campaign_id=43&date_from=2026-06-01&date_to=2026-06-10 | Field | Type | Description | Valid values |
|---|---|---|---|
total_dials | integer | Total dial attempts in the time window | >= 0 |
total_answers | integer | Total calls answered by the customer | >= 0 |
answer_rate_pct | string | Decimal string (2 digits) - total_answers / total_dials x 100 | "0.00"-"100.00" |
avg_talk_time_sec | string | Average talk time (seconds) | |
total_talk_time_sec | string | Total talk time (seconds) | |
unique_agents | integer | Distinct agents that dialed in the window | >= 0 |
pdpl_blocked_count | integer | Calls blocked by PDPL (outside allowed window) | >= 0 |
{
"data": {
"total_dials": 487,
"total_answers": 312,
"answer_rate_pct": "64.07",
"avg_talk_time_sec": "184.5",
"total_talk_time_sec": "57563",
"unique_agents": 12,
"pdpl_blocked_count": 5
}
}GET /api/telesales/reports/agent-performance?campaign_id=43&date_from=...&date_to=... data[]) | Field | Type | Description | Valid values |
|---|---|---|---|
agent_id | integer | Agent user ID | |
name | string | Agent full name | |
username | string | Login name | |
dials | integer | Dial attempts in the window | >= 0 |
answers | integer | Answered calls | >= 0 |
avg_aht_sec | integer | AHT (Average Handle Time) - average per-call handling time (seconds) | >= 0 |
conversions | integer | Successful conversions | >= 0 |
conv_rate | number | conversions / answers x 100 (float, NOT string) | 0-100 |
{
"data": [
{
"agent_id": 3,
"name": "Nguyen Van A",
"username": "agent01",
"dials": 152,
"answers": 98,
"avg_aht_sec": 191,
"conversions": 28,
"conv_rate": 18.42
},
{
"agent_id": 4,
"name": "Tran Thi B",
"username": "agent02",
"dials": 140,
"answers": 86,
"avg_aht_sec": 176,
"conversions": 22,
"conv_rate": 15.71
}
]
}GET /api/telesales/reports/funnel?campaign_id=43&date_from=...&date_to=... Returns a flat array (not a nested object {stages, conversion_overall_pct}).
data[]) | Field | Type | Description | Valid values |
|---|---|---|---|
stage | string | Funnel stage name | Total leads / Dialed / Connected / Talked > 30s / Disposed / Converted |
count | integer | Number of leads/calls at this stage | >= 0 |
{
"data": [
{ "stage": "Total leads", "count": 487 },
{ "stage": "Dialed", "count": 312 },
{ "stage": "Connected", "count": 248 },
{ "stage": "Talked > 30s", "count": 198 },
{ "stage": "Disposed", "count": 290 },
{ "stage": "Converted", "count": 78 }
]
}There are 6 stages - including Disposed. The client computes conversion_overall_pct = Converted / Total_leads x 100 from the first and last stages.
GET /api/telesales/reports/disposition-breakdown?campaign_id=43&date_from=...&date_to=... data[]) | Field | Type | Description | Valid values |
|---|---|---|---|
code | string | Disposition code | |
label | string | Display label | |
category | string | Outcome group | contact / no_contact / callback / remove |
color | string | UI badge color | #RRGGBB |
count | integer | Calls with this disposition | >= 0 |
pct | string | Decimal string (2 digits) - percentage of total calls | "0.00"-"100.00" |
{
"data": [
{
"code": "no_answer",
"label": "No Answer",
"category": "no_contact",
"color": "#6B7280",
"count": 95,
"pct": "30.45"
},
{
"code": "sale",
"label": "Sale Success",
"category": "contact",
"color": "#10B981",
"count": 78,
"pct": "25.00"
}
]
}Each item includes category + color (the UI uses these to render badges). pct is a decimal string. There is no meta: {total_calls} wrapper - the client computes total = sum(count).
GET /api/telesales/reports/caller-id-health?date_from=...&date_to=... data[]) | Field | Type | Description | Valid values |
|---|---|---|---|
cid | string | Caller-ID number | |
display_name | string|null | Internal name | |
carrier | string | Telco | viettel / mobifone / vinaphone / vietnamobile / landline / other |
cli_type | string | Number type | mobile / fixed_02x / hotline_1900 / hotline_1800 / brandname |
pool_status | string | Status inside the rotation pool | active / cooldown / paused / disabled |
cooldown_until | datetime|null | When cooldown ends | ISO 8601 UTC |
daily_cap | integer | Daily call cap | >= 1 |
total_calls | integer | Total calls dialed in the window | >= 0 |
answered_calls | integer | Answered calls | >= 0 |
asr_pct | number | Answer-Seizure Ratio (%) - answered/total x 100 | 0-100 |
acd_sec | integer | Average Call Duration - average talk time (seconds) | >= 0 |
short_calls | integer | Calls shorter than the "meaningful conversation" threshold | >= 0 |
short_call_ratio_pct | number | Ratio of short calls / total | 0-100 |
rejected_calls | integer | Rejected calls | >= 0 |
health_status | string | Health classification | healthy / warning / suspected_block / no_data |
health_calls_today | integer | Calls today (used for cooldown evaluation) | >= 0 |
{
"data": [
{
"cid": "0900000012",
"display_name": "Sales Line 1",
"carrier": "viettel",
"cli_type": "fixed_02x",
"pool_status": "active",
"cooldown_until": null,
"daily_cap": 200,
"total_calls": 152,
"answered_calls": 98,
"asr_pct": 64.5,
"acd_sec": 121,
"short_calls": 28,
"short_call_ratio_pct": 18.4,
"rejected_calls": 2,
"health_status": "healthy",
"health_calls_today": 87
}
]
}GET /api/telesales/reports/hour-dow-heatmap?campaign_id=43&date_from=...&date_to=... Returns a flat array of 168 items (7 days x 24 hours), not nested by day-of-week.
data[]) | Field | Type | Description | Valid values |
|---|---|---|---|
day_of_week | integer | Day of the week | 0 = Sun, 1 = Mon, ..., 6 = Sat |
hour | integer | Hour of the day (per the timezone query param) | 0-23 |
dials | integer | Dial attempts in the slot | >= 0 |
answered | integer | Answered calls | >= 0 |
answer_rate_pct | number | Answer rate answered/dials x 100 | 0-100 |
{
"data": [
{ "day_of_week": 1, "hour": 0, "dials": 0, "answered": 0, "answer_rate_pct": 0 },
{ "day_of_week": 1, "hour": 1, "dials": 0, "answered": 0, "answer_rate_pct": 0 },
{ "day_of_week": 1, "hour": 9, "dials": 87, "answered": 56, "answer_rate_pct": 64.4 }
]
}The client pivots into a 7 x 24 matrix to render the heatmap.
GET /api/telesales/reports/lead-source-attribution?date_from=...&date_to=... data[]) | Field | Type | Description | Valid values |
|---|---|---|---|
source | string | Lead source - leads without a source are grouped under "(unknown)" | |
total_leads | integer | Total leads from this source in the window | >= 0 |
dialed_count | integer | Leads that have been dialed | >= 0 |
contacted_count | integer | Leads that picked up | >= 0 |
conv_count | integer | Successful conversions | >= 0 |
conv_rate_pct | string | Conversion rate conv_count/total_leads x 100 (decimal, 2 digits) | "0.00"-"100.00" |
{
"data": [
{
"source": "facebook_lead_ads",
"total_leads": 240,
"dialed_count": 230,
"contacted_count": 152,
"conv_count": 62,
"conv_rate_pct": "25.83"
},
{
"source": "(unknown)",
"total_leads": 5,
"dialed_count": 3,
"contacted_count": 2,
"conv_count": 0,
"conv_rate_pct": "0.00"
}
]
}GET /api/telesales/reports/time-series?campaign_id=43&group_by=day&date_from=...&date_to=... Returns a flat array - no {group_by, metrics, series} wrapper.
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
group_by | string | optional | Time grouping unit | day (default) / week / hour |
data[]) | Field | Type | Description | Valid values |
|---|---|---|---|
bucket | string | Time bucket - format depends on group_by | day -> YYYY-MM-DD; week -> YYYY-Www (ISO 8601); hour -> YYYY-MM-DD HH:00:00 |
dials | integer | Dial attempts in the bucket | >= 0 |
answers | integer | Answered calls | >= 0 |
talk_sec | integer | Total talk time (seconds) | >= 0 |
conversions | integer | Successful conversions | >= 0 |
{
"data": [
{ "bucket": "2026-06-01", "dials": 110, "answers": 68, "talk_sec": 9180, "conversions": 18 },
{ "bucket": "2026-06-02", "dials": 105, "answers": 71, "talk_sec": 10260, "conversions": 16 },
{ "bucket": "2026-06-05", "dials": 82, "answers": 55, "talk_sec": 6720, "conversions": 15 }
]
}GET /api/telesales/reports/abandon-rate?campaign_id=43 (realtime) This endpoint's response is richer than other reports - it contains current (live snapshot) + breaches (history of recent threshold breaches).
| Field | Type | Description | Valid values |
|---|---|---|---|
current.campaign_id | integer | Campaign ID | |
current.attempts_total | integer | Total attempts in the measurement window | >= 0 |
current.attempts_abandoned | integer | Attempts abandoned before the agent could speak | >= 0 |
current.abandon_rate_pct | number | Abandon rate (%) | 0-100 |
current.window_minutes | integer | Measurement window length (minutes, default 30 days = 43,200) | >= 1 |
current.window_from | datetime | Window start | ISO 8601 |
current.window_to | datetime | Window end | ISO 8601 |
current.campaign_name | string | Campaign name | |
current.campaign_status | string | Campaign status | draft / active / paused / completed |
current.limit_pct | number | Allowed PDPL/TCPA threshold | default 3 |
current.breached | bool | Whether the threshold is being breached now | |
breaches[] | array | History of snapshots that breached the threshold (empty if never) |
{
"data": {
"current": {
"campaign_id": 43,
"attempts_total": 1,
"attempts_abandoned": 0,
"abandon_rate_pct": 0,
"window_minutes": 43200,
"window_from": "2026-05-10 02:31:14",
"window_to": "2026-06-09 02:31:14",
"tenant_id": 1,
"campaign_name": "Outbound Sales Q3-2026 03",
"campaign_status": "active",
"limit_pct": 3,
"breached": false
},
"breaches": []
}
}When breached=true, the breaches array contains the history of breaching snapshots (time windows that exceeded the limit). The client uses this to render a warning banner and trend chart.
GET /api/telesales/reports/{report}/export?format=csv|xlsx{report} in {dashboard, disposition-breakdown, agent-performance, funnel, caller-id-health, hour-dow-heatmap, lead-source-attribution, time-series}.
Response: a file stream (Content-Type: text/csv or application/vnd.openxmlformats-officedocument.spreadsheetml.sheet).
The default export layout is the generic one. Per-customer Excel templates (column order, header labels, conditional formatting) are configured inside the separate per-customer configuration package.