Webinar Tracking API

← Dashboard

Every number on this dashboard is computed server-side and served over /api/*. These are the same endpoints the front end calls, so anything documented here already carries the dashboard's rules — promo windows, same-day show merging, ad-sourced denominators, and the difference between zero and not measured.

Authentication Call report Fields How to read it Other endpoints

Authentication

Every /api/* route needs one of two credentials. There is no anonymous access.

CallerCredential
A person in a browser The ima_session cookie you already have from signing in. Opening an API URL in a tab you're logged into just works.
A script or another service The header x-service-token: <SERVICE_TOKEN>. Ask an admin for the value — it is deliberately not printed on this page.
Anything without a credential gets 401, including this endpoint. Don't put the service token in a browser URL, a shared doc, or a client-side script — it is a full-access credential, not a per-user one.

Call report

GET/api/call-report

The booked-call report for one show or a date window: what happened to every call that was booked, and how the setters handled them. One object per show, newest first, plus a pooled totals block.

A same-day pair of funnels (Ads + Organic) is one show here, as it is on the Sales tab — the setters worked one day, not two funnel pages.

Parameters

ParamMeaning
widA webinar id. Returns the show that webinar belongs to — its twin funnel is merged in, never dropped. Unknown id → 404.
dateYYYY-MM-DD — the show on that date. (promoEnd is accepted as an alias.)
start, endYYYY-MM-DD, inclusive, on the webinar date. Either may be given alone.
format=textReturns text/plain instead of JSON — a ready-to-paste report. The JSON always carries the same string as .text.

No parameters returns every tracked show. Dates are UTC, matching every other window in this system.

Examples

# one show, as pasteable text
curl -H "x-service-token: $TOKEN" \
  "https://track.tijarahinstitute.com/api/call-report?date=2026-08-09&format=text"

# a date window, as JSON
curl -H "x-service-token: $TOKEN" \
  "https://track.tijarahinstitute.com/api/call-report?start=2026-07-01&end=2026-08-16"

# one show by webinar id (its same-day twin is included)
curl -H "x-service-token: $TOKEN" \
  "https://track.tijarahinstitute.com/api/call-report?wid=wb_4p8kz00w"

Text output

August 9 · 2026-08-09
  Calls booked        475
  Never dialed        14
  No answer           24
  Voicemail           85
  Reached             352   (74.1% of booked)
  Confirmed           193   (40.6% of booked)
  Cancelled           220   (46.3% of booked)
  Setters             19
  Bookings dialed     461
  Median dial speed   1h 46m
  Setter confirm rate 37.3%   (168 of 450 booked leads)

JSON shape

{
  "filter":  { "wid": null, "date": "2026-08-09", "start": null, "end": null,
               "basis": "webinar date (promoEnd), UTC, inclusive" },
  "minTalkSec": 30,
  "shows":  [ { …one per show, newest first… } ],
  "totals": { …the same figures pooled over the shows returned… },
  "text":   "…the format=text rendering…",
  "notes":  [ "…the caveats below, so a machine reader gets them too…" ]
}

Fields on a show

FieldUnitMeaning
name, dateThe webinar's name and its date (promoEnd). id is the primary funnel; ids lists every funnel merged into this row.
bookingsbookingsTotal calls booked. A rebook is a second booking — see below.
neverDialedbookingsNobody rang them at all.
noAnswerbookingsDialed, never picked up — rang out, busy, blocked.
voicemailbookingsPicked up, but talked for less than minTalkSec.
reached / reachPctbookings / %Answered and talked ≥ minTalkSec. reachPct = reached ÷ bookings.
confirmed / confirmPctbookings / %The lead's Close status moved Unconfirmed → Potential. Counted over all bookings, so it divides by bookings.
cancelled / cancelPctbookings / %The booking carries iClosed's cancelled flag. Counted over all bookings.
confirmedOfReached
cancelledOfReached
bookingsThe same two, narrowed to the conversations that actually happened. This is the split the dashboard's Calls subtab shows.
setters / setterNamespeopleHow many distinct setters worked this show, and who. Excludes the (not dialed) / (unknown) buckets — those are not people.
dialedbookingsBookings rung at least once — not dial attempts. See the caveat below.
neverRungbookingsbookings − dialed. Someone asked for a call and got silence.
medianDial / medianDialMslabel / msMedian time from the booking to the first outbound dial. p90DialMs rides alongside.
setterConfirmleads / %{ bookedLeads, confirmedLeads, pct } — the setter confirm rate. A different denominator: leads, not bookings.
scanned, unscanned, partialbookings / boolHow many bookings have a contact verdict, and whether the row is complete enough to state its dial stages.
minTalkSecsecondsThe talk-time threshold the stages were computed with (currently 30). Echoed rather than hard-coded, so it can be retuned without this doc going stale.

totals carries the same fields pooled over the shows returned, plus shows, partialShows and stageBookings. Every percentage there is Σ numerator ÷ Σ denominator — never an average of the per-show rates, which would weight a 4-booking show like a 563-booking one.

How to read it

The four stages partition the bookings. neverDialed + noAnswer + voicemail + reached === bookings on a complete row, so nothing can hide in a gap between two columns. If they come up short, unscanned is the shortfall and it is stated rather than absorbed.
“Answered” is not contact. Close marks a voicemail pickup and an instant hang-up answered exactly like a conversation — measured over 1,025 answered dials, 55% ran under 10 seconds. So reached requires talk time ≥ minTalkSec (30s). Counting every “answered” would report roughly double the conversations that happened. The voicemail bucket is reported separately rather than folded into a neighbour: it is neither a failure to reach the phone nor a conversation.
The unit is the BOOKING, not the person. A rebook is a second call somebody has to ring, so it is a second row — the same denominator the dashboard's Speed-to-lead uses. setterConfirm is the one field that switches to leads, because Close's confirmation flip happens to a lead, not to a calendar entry. Over the whole cohort that is 3,899 bookings against 3,703 leads. Never divide one block into the other.
cancelled includes reschedules. It is iClosed's cancelled flag, and ~62% of bookings carry it — far too many to be leads walking away. A moved call is a cancelled row plus a new one. Read it as booking-level churn, never as a walk-away or DQ rate; there is no separate DQ signal in the data.
dialed counts bookings, not dial attempts. Close's per-dial rows are scanned incrementally (a 3-day cursor with a 14-day reach-back), so no lifetime attempt count per booking exists to report. “Total dials” here means bookings rung at least once. A true attempt count would need a new field on the sync and a full re-sweep of Close's call history.
Not measured is not zero. A show whose bookings aren't materially scanned returns partial: true and its four dial stages, reachPct, dialed and the median come back null — the stages are supposed to add up to bookings, and a partial row silently doesn't. confirmed and cancelled survive a partial row: they come from the status-change scan and the iClosed flag, neither of which depends on the dial scan. Treat null as “unknown”, never as 0.
Freshness. Everything here is precomputed by the sales sync, which runs every 15 minutes; responses are cached for 60 seconds. This endpoint makes no calls to Close or iClosed of its own, so polling it is cheap — but it can never be fresher than the last sync.

Other endpoints

The rest of the dashboard's API. Same authentication; each returns JSON. These are the read paths — anything that writes is admin-only and not listed here.

EndpointReturns
/api/webinarsEvery tracked webinar: id, name, type, promo window. Start here to get a wid.
/api/overallThe whole funnel, one row per show — visitors → opt-ins → forms → WhatsApp → attended → booked → sales → cash → spend, with per-ad-account sub-rows.
/api/metrics?type=&start=&end=Funnel Economics: per landing-page counts, rates and allocated spend.
/api/adsAd-by-Ad: opt-ins, forms and exact ad-level Meta spend per creative. hourly=1 for the hourly timeline, perf=1 for delivery rates.
/api/callsThe Calls subtab's own shape — the same booking stages, without the setter half.
/api/settersOne row per setter pooled across shows: dial speed distribution and confirm rate.
/api/closersClose rate per closer, ads-only vs organic-only.
/api/sales, /api/marketingThe sales board and its per-source breakdown.
/api/revenue, /api/leaderboardWhere all the money came from over a range; the month-to-date source ranking (format=text for a pasteable board).
/api/registrants?wid=Every opt-in for one webinar, with its Close journey. Contains personal data — treat it accordingly.
/api/organic, /api/daily, /api/emailYouTube/organic attribution, daily opt-ins, email opt-ins.
/api/statusSync health: what ran, when, and what is stale or erroring.
Prefer an endpoint over the database. The business logic is not in the database — promo windows, same-day merging, ad-sourced denominators and the null-vs-zero rules all live in these handlers. A query that re-derives a number from raw tables will quietly disagree with the dashboard.

Want Claude to read these for you instead of writing scripts? See Connect your AI — the same data, through the MCP server.