voice

Calls, abandons and recoveries by dealer, campaign and local day

One row per (dealer, campaign, LOCAL DAY) over a bounded window, plus a per-(dealer, campaign) rollup of the same window. Requires the voice:bridge scope.

⛔ THIS FEATURE IS GATED AND A 404 IS THE GATE, NOT A TYPO. Two conditions are checked before any database work: a server-wide master flag, and a REQUIRED per-organization allowlist. An unset or empty allowlist means NOBODY, never everybody. Both failures answer the identical 404, so a caller cannot tell "the feature is off" from "you are not on the list", that is the intended posture, not an omission. A window with no calls in it is a 200 carrying an empty days array, so a 404 never means "nothing happened".

⛔ THE WINDOW IS A RANGE OF INSTANTS; THE ROWS ARE CALENDAR DAYS IN THE DEALER'S OWN TIMEZONE. These are not the same thing and the difference shows up at both ends of every pull. from and to filter on the instant a call started, in UTC. Each returned row is then bucketed by that instant rendered in the dealer's IANA timezone (call_tracking_numbers.dealer_timezone, resolved through the ICU database so DST needs no special-casing on your side). So a window of from=2026-08-01 over a dealer in America/Los_Angeles opens at 17:00 on JULY 31 local, and the first row you get back can legitimately be dated 2026-07-31 and be a partial day. The timezone field on every row states which zone that row was bucketed in; a dealer with no timezone configured falls back to a server default (UTC unless the operator set one), which is a provisioning gap rather than a reason to drop the call. If you are reconciling day totals with a dealer, pad the window by a day at each end and discard the edges.

DEFAULTS AND BOUNDS. Omitting to means now; omitting from means 30 days before now. A from later than to is a 400, and a window wider than 92 days is a 400 rather than a slow success, the report scans per-call rows before collapsing them, so an "all history" pull is refused instead of served badly. A date that does not parse is a 400 naming the two accepted spellings. A bare YYYY-MM-DD is read as MIDNIGHT UTC, so send a full ISO-8601 instant with an offset if you mean a local midnight.

⛔ THERE IS NO PAGINATION, AND truncated IS HOW YOU FIND OUT YOU LOST DATA. The underlying scan stops at 5000 calls. When it hits that ceiling truncated comes back true and EVERY NUMBER BELOW IT IS AN UNDERCOUNT, there is no cursor to follow and no page two. The remedy is a narrower window or a partner_dealer_id / partner_campaign_id filter, not a retry. Treat truncated:true as an error in your pipeline, not as a footnote.

ONLY TRACKED CALLS APPEAR, AND TEST TRAFFIC DOES NOT. Ordinary voice-bridge calls are structurally excluded (the report joins through the tracking number, so a call with no tracking number cannot enter it). Numbers flagged as test are excluded too: our end-to-end harness dials a real partner organization on purpose, and without that exclusion a robot call nobody answered would be reported to you as one of your dealers missing a lead.

⛔ recoveries IS ZERO ON EVERY ROW TODAY, BY CONSTRUCTION AND NOT BY MEASUREMENT, and recoveries_wired: false at the top of the response is the field that says so. The abandon → SMS → call-back corridor is held closed pending a legal decision on consent, and nothing links a recovery conversation back to a tracked call yet. Read a 0 here as "not wired", never as "we won nobody back". When it becomes true the counts start meaning what their name says; until then, do not chart this field.

⛔ dealer_voicemail IS ALSO EFFECTIVELY ZERO, FOR A DIFFERENT AND MORE MISLEADING REASON. That bucket is populated only by an answering-machine verdict on the dealer leg, and premium answering-machine detection is deliberately OFF in the current product: the forward is dialled without it so the consumer does not wait through the analysis window. With it off we cannot prove a human answered, so A FORWARD THAT WENT TO A DEALERSHIP VOICEMAIL BOX IS COUNTED IN connected. If your dealers dispute a connected count, this is the first thing to check, and it is a product setting rather than a defect in the count.

dealer_no_answer IS ONE BUCKET FOR FOUR OUTCOMES. No answer, busy, unreachable and rejected are distinguishable only while the call is live; the durable record does not keep the carrier cause, so they are indistinguishable at reporting time and share one column. abandoned is the CONSUMER hanging up before the two legs were joined, which is a different event from the dealer not picking up, and the two are never merged.

THE FOUR BUCKETS ALWAYS SUM TO calls_total: connected + abandoned + dealer_no_answer + dealer_voicemail. If your own arithmetic disagrees, you have summed across days that were bucketed in different timezones.

calls_scanned IS NOT THE SUM OF calls_total. It counts the call rows this request read; a row whose start instant cannot be parsed is dropped rather than misfiled under some arbitrary day, so the sum of the day rows can be lower. Use calls_scanned to reason about the ceiling, and the day rows for anything you show a dealer.

avg_forwarded_seconds IS AN HONEST NULL, NOT A ZERO. It averages the billed dealer-leg seconds over the CONNECTED calls in that bucket that already have a cost row, so a bucket where nothing has been rated yet reports null rather than a 0 that reads as "they talked for no time". Those seconds come from a SECOND lookup keyed on this window's dealer legs, and that lookup is best-effort: if it does not come back the report still returns 200 with every avg_forwarded_seconds null. A null across the whole response, on a window you know contains connected calls, means the durations were not retrieved, not that the calls were silent. Narrower windows are more likely to carry durations.

get/voice/call-tracking/report

Request

  • Base URL: https://corebee.ai/api/v1
  • URL: https://corebee.ai/api/v1/voice/call-tracking/report
  • Auth: HTTP bearer

Query parameters

fromstring

Inclusive lower bound on the instant a call started. YYYY-MM-DD (read as midnight UTC) or a full ISO-8601 instant. Defaults to 30 days ago. Unparseable is a 400.

tostring

Inclusive upper bound, same spelling as from. Defaults to now. from later than to is a 400.

partner_dealer_idstring

Exact-match filter on YOUR dealer id as it was recorded on the tracking number. Not validated against a list: an id that matches nothing is a 200 with no rows, not a 404. Echoed back under filters.

partner_campaign_idstring

Exact-match filter on YOUR campaign id, same semantics as partner_dealer_id. Combining both narrows to their intersection.

Response

OK

Example response

{
  "data": {
    "calls_scanned": 412,
    "days": [
      {
        "day": "2026-08-19",
        "timezone": "America/Los_Angeles",
        "campaign_label": "USA Today 0% financing",
        "calls_total": 34,
        "connected": 21,
        "abandoned": 6,
        "dealer_no_answer": 7,
        "avg_forwarded_seconds": 184.3
      }
    ],
    "totals": [
      {
        "days": 4,
        "calls_total": 128,
        "connected": 79,
        "abandoned": 22,
        "dealer_no_answer": 27
      }
    ]
  }
}

Changes

No recorded changes to this operation across all 1 revision of this API.