Skip to content

Reports

Reporting endpoints for AutoCall campaigns — KPIs, trend, comparison, hangup analysis, heatmap, DTMF analytics, funnel, and Excel export.

Required permission

Every endpoint below requires autocall_api_access. The export endpoint additionally requires autocall_export_report.

GET /api/autocall/campaigns/{id}/reports/kpis — KPI summary

Query parameters

FieldTypeRequiredDescriptionValid values
fromdatetimeoptionalWindow startISO 8601. Default: campaign start time
todatetimeoptionalWindow endISO 8601. Default: now
timezonestringoptionalAggregation timezoneDefault: Asia/Ho_Chi_Minh

Response 200

json
{
  "data": {
    "total_leads": 5000,
    "dialed": 4823,
    "answered": 3120,
    "no_answer": 1234,
    "busy": 287,
    "failed": 182,
    "abandoned": 0,
    "answer_rate_pct": "64.69",
    "avg_call_duration_sec": 42,
    "total_talk_time_sec": 131040,
    "dtmf_distribution": {
      "1": 1840,
      "2": 950,
      "0": 215,
      "no_input": 115
    },
    "transfer_to_queue_count": 1840,
    "transfer_success_rate_pct": "98.21",
    "computed_at": "2026-06-30T08:00:00+00:00"
  }
}

Field reference

FieldDescription
total_leadsTotal leads in the campaign
dialedNumber of attempts executed (a single lead may have many attempts)
answered / no_answer / busy / failedBreakdown by result
answer_rate_pctanswered ÷ dialed × 100 — the main quality metric for the pool
avg_call_duration_secAverage billsec for answered calls
dtmf_distributionDistribution of pressed keys (key = digit, value = count)
transfer_to_queue_countNumber of calls successfully transferred to an agent queue

GET /api/autocall/campaigns/{id}/reports/trend — Daily trend

Query parameters

FieldTypeRequiredDescriptionValid values
fromdateoptionalStart dateYYYY-MM-DD
todateoptionalEnd dateYYYY-MM-DD
granularitystringoptionalBucket sizeday (default), hour

Response 200

json
{
  "data": [
    {
      "bucket": "2026-06-28",
      "dialed": 1200,
      "answered": 780,
      "answer_rate_pct": "65.00",
      "avg_duration_sec": 41,
      "transfer_count": 460
    },
    {
      "bucket": "2026-06-29",
      "dialed": 1612,
      "answered": 1098,
      "answer_rate_pct": "68.11",
      "avg_duration_sec": 43,
      "transfer_count": 642
    }
  ]
}

Use this for a line chart that tracks campaign performance over time.

GET /api/autocall/campaigns/comparison — Compare several campaigns

Cross-campaign endpoint

Unlike other endpoints (which are nested under a campaign), this one lives directly at /campaigns/comparison so you can compare N campaigns at once.

Query parameters

FieldTypeRequiredDescription
campaign_idsstringYesComma-separated campaign IDs (e.g. 5,8,12)
metricstringoptionalMetric to compare: answer_rate_pct (default) / avg_duration_sec / transfer_rate_pct

Response 200

json
{
  "data": [
    { "campaign_id": 5,  "campaign_name": "Renewal Q3",      "metric_value": "62.30" },
    { "campaign_id": 8,  "campaign_name": "Cross-sell Q3",   "metric_value": "55.18" },
    { "campaign_id": 12, "campaign_name": "Win-back churned", "metric_value": "48.92" }
  ]
}

GET /api/autocall/campaigns/{id}/reports/hangup — Hangup reason analysis

Helps operations understand why a campaign is failing so they can optimize it.

Response 200

json
{
  "data": {
    "total_calls": 4823,
    "by_hangup_cause": [
      { "hangup_cause": "NORMAL_CLEARING",   "count": 3120, "pct": "64.69" },
      { "hangup_cause": "NO_ANSWER",         "count": 1234, "pct": "25.59" },
      { "hangup_cause": "USER_BUSY",         "count": 287,  "pct": "5.95" },
      { "hangup_cause": "CALL_REJECTED",     "count": 95,   "pct": "1.97" },
      { "hangup_cause": "RECOVERY_ON_TIMER", "count": 87,   "pct": "1.80" }
    ]
  }
}

Hangup code reference: Hangup cause codes.

GET /api/autocall/campaigns/{id}/reports/heatmap — Hour × day heatmap

Visualize the busiest hours of the week to adjust the schedule.

Query parameters

FieldTypeRequiredDescription
metricstringoptionaldialed (default) / answered / answer_rate_pct

Response 200

json
{
  "data": [
    { "day_of_week": 1, "hour": 9,  "dialed": 156, "answered": 102, "answer_rate_pct": "65.38" },
    { "day_of_week": 1, "hour": 10, "dialed": 178, "answered": 121, "answer_rate_pct": "67.98" },
    { "day_of_week": 2, "hour": 14, "dialed": 201, "answered": 134, "answer_rate_pct": "66.67" }
  ]
}

day_of_week: 0 = Sunday, 1 = Monday, ... 6 = Saturday. Flat array of 168 entries (7 days × 24 hours).

GET /api/autocall/campaigns/{id}/reports/dtmf — Detailed DTMF analysis

Response 200

json
{
  "data": {
    "total_answered": 3120,
    "with_dtmf_input": 3005,
    "no_input": 115,
    "no_input_rate_pct": "3.69",
    "by_digit": [
      { "digit": "1", "count": 1840, "pct": "61.23", "label": "Accept transfer to agent" },
      { "digit": "2", "count": 950,  "pct": "31.61", "label": "Not interested" },
      { "digit": "0", "count": 215,  "pct": "7.16",  "label": "Replay" }
    ],
    "by_script_path": [
      { "path": "intro -> press_1 -> queue_sales", "count": 1840 },
      { "path": "intro -> press_2 -> playback_polite -> hangup", "count": 950 }
    ]
  }
}

label is taken from the script's dtmf_actions[].label.

GET /api/autocall/campaigns/{id}/reports/funnel — Conversion funnel

Response 200

json
{
  "data": [
    { "stage": "Imported lead",       "count": 5000, "drop_rate_pct": null    },
    { "stage": "Dialed",              "count": 4823, "drop_rate_pct": "3.54"  },
    { "stage": "Answered",            "count": 3120, "drop_rate_pct": "35.31" },
    { "stage": "Pressed valid DTMF",  "count": 3005, "drop_rate_pct": "3.69"  },
    { "stage": "Transferred to agent","count": 1840, "drop_rate_pct": "38.77" },
    { "stage": "Agent disposed",      "count": 1640, "drop_rate_pct": "10.87" }
  ]
}

drop_rate_pct: drop ratio versus the previous stage.

POST /api/autocall/campaigns/{id}/reports/export — Export to Excel

Request body

FieldTypeRequiredDescriptionValues
report_typestringYesReport kindkpis / trend / hangup / dtmf / funnel / full
fromdateoptionalWindow fromYYYY-MM-DD
todateoptionalWindow toYYYY-MM-DD
formatstringoptionalFile formatxlsx (default) / csv

Request body (JSON)

json
{
  "report_type": "full",
  "from": "2026-06-01",
  "to": "2026-06-30",
  "format": "xlsx"
}

Response 202 (Accepted, async)

json
{
  "data": {
    "job_id": "export_a7b3c5d2",
    "status": "queued",
    "report_type": "full",
    "estimated_seconds": 30
  }
}

Polling for status

GET /api/autocall/campaigns/{id}/reports/export/{job_id}
json
{
  "data": {
    "job_id": "export_a7b3c5d2",
    "status": "ready",
    "download_url": "https://app.zorio.vn/api/public/exports/...?signature=...",
    "expires_in_seconds": 3600,
    "file_size": 542600,
    "completed_at": "2026-06-30T08:01:23+00:00"
  }
}

status enum: queued / running / ready / failed / expired.

Download

download_url is a signed URL with a TTL of 1 hour — follow the redirect to download the .xlsx file.

Best practice

Cache reports

KPIs / Trend change slowly (aggregates update once a minute). Cache for 1-5 minutes in your CRM to reduce load.

Realtime vs Reports

  • Realtime (lead-level updates): subscribe to the autocall.lead.* webhooks.
  • Reports (aggregates): poll these endpoints on an interval (every 1-5 minutes).

Do NOT poll Reports every second — it wastes rate limit budget and is unnecessary.

Exporting large volumes

For campaigns with > 50k leads, full exports can take 1-2 minutes. To avoid blocking the UI:

  1. Submit the job via POST -> receive a job_id.
  2. Poll status every 5 seconds.
  3. On ready -> trigger an auto-download or show a "Download file" button.

Cấp phép theo điều khoản sử dụng của Zorio.