API · Statistics — pull your numbers into the tools you already use
The statistics endpoints return the same numbers you see in the Statistics section — leads, conversions and per-publication performance — so you can take them to Looker Studio, Google Sheets, n8n or your own backend.
This page covers only those endpoints. How to get the key, how to authenticate, the response shape, the rate limit and the errors shared by the whole API live in ZeroChats API. Start there if this is your first call.
The base URL is:
https://app.zerochats.com/api/statistics/v1Every call is a GET and the key travels in the Authorization header:
curl -H "Authorization: Bearer zcst_your_key" \ https://app.zerochats.com/api/statistics/v1/overviewOn these endpoints meta always comes back, and it tells you which period and which time zone produced the numbers:
{ "success": true, "data": {}, "meta": { "startDay": "2026-07-01", "endDay": "2026-07-30", "timezone": "Europe/Madrid" }}Endpoints
Section titled “Endpoints”| Endpoint | Takes dates | What it returns |
|---|---|---|
/me | No | The business the key belongs to, and its time zone. |
/overview | Yes | Lifetime totals and the funnel by stage, split into inbound and outbound. |
/leads/daily | Yes | One row per day: leads received and how far they got. |
/leads/by-weekday | No | Lifetime distribution of inbound leads across the days of the week. |
/leads/by-hour | No | Lifetime distribution of inbound leads across the hours of the day. |
/conversions/goals | Yes | Per sales link and send day: in Proposal and converted. |
/conversions/tags | Yes | Leads converted (Booked or Paid) per tag and day. |
/conversions/contents | Yes | Per content piece and send day: in Proposal and converted. |
/performance | Yes | Performance per publication, ad or origin, with a link back to the post. |
/welcome-messages | Yes | Welcome messages sent, answered and the response rate, per day. |
The three endpoints that don’t take dates (/me, /leads/by-weekday and /leads/by-hour) cover the whole history of the business. Sending them startDay and endDay is not an error: they simply ignore them, and you can tell because meta comes back without dates.
/overview
Section titled “/overview”{ "lifetime": { "totalLeads": 1840, "positiveLeads": 213 }, "funnel": { "inbound": [ { "state": "OPEN_CONVERSATION", "count": 48 }, { "state": "LEAD", "count": 21 }, { "state": "CLIENT", "count": 7 } ], "outbound": [{ "state": "IN_PROGRESS", "count": 12 }] }}lifetime is historical and does not depend on the range you ask for: totalLeads is every lead you have and positiveLeads the ones sitting in Proposal, Booked or Paid (the Proposal made figure, not conversions).
funnel does cover the range, and it separates inbound leads (they wrote to you) from outbound ones (you started the conversation). They travel apart on purpose: they are two different acquisition motions, and blending them hides which one converts.
Each stage arrives under its English name, exactly like in the Custom Webhook and the leads endpoints, and for the same reason: the names are identical across every account whatever language you work in, so a filter built on them doesn’t break.
| Stage in the app | state |
|---|---|
| Open | OPEN_CONVERSATION |
| Discovering | IN_PROGRESS |
| Qualifying | QUALIFYING |
| Proposal | LEAD |
| Booked | BOOKED |
| Paid | CLIENT |
| Postponed | POSTPONED |
| Unqualified | NO_QUALIFY |
| Lost | LOST |
/leads/daily
Section titled “/leads/daily”[{ "date": "2026-07-01", "total": 34, "qualified": 6, "booked": 2, "paid": 1 }]| Field | What it is |
|---|---|
date | The day, as YYYY-MM-DD. |
total | That day’s conversations: leads your AI talked to, on the day of their last state change. |
qualified | Of those, the ones now sitting in Proposal. |
booked | Of those, the ones now sitting in Booked. |
paid | Of those, the ones now sitting in Paid. |
Days with no activity come back too, filled with zeros, so you can plot the series as-is without patching gaps.
/leads/by-weekday and /leads/by-hour
Section titled “/leads/by-weekday and /leads/by-hour”[{ "dayOfWeek": 0, "day": "MONDAY", "total": 240, "qualified": 31, "booked": 14 }][{ "hour": 9, "total": 88, "qualified": 12, "booked": 5 }]dayOfWeek runs from 0 (Monday) to 6 (Sunday) and day gives you the name so you never have to remember the convention. hour runs from 0 to 23. Both always come back complete: all seven days and all twenty-four hours, even the empty ones.
Like the day-of-week and hour-of-day charts in Statistics, they only count leads who wrote to you (inbound) and talked to your AI, at the moment they wrote. Here qualified means leads in Proposal and booked groups Booked and Paid together. Don’t try to reconcile these two endpoints row by row against /leads/daily: they answer a different question — what day and time people write to you — not how a cohort converted.
/conversions/goals, /conversions/tags and /conversions/contents
Section titled “/conversions/goals, /conversions/tags and /conversions/contents”All three return the same shape, one row per entity and day:
[ { "date": "2026-07-14", "id": "9f1c8a4e-2b77-4d0a-9d1c-3f2b6a5c7e10", "name": "Assessment call", "leads": 4, "clients": 1 }]name comes already resolved — the name of the sales link, the tag or the content piece — so you don’t need a second call or your own lookup table.
A conversion is a lead that is in Booked or Paid today; Proposal does not count.
- In
/conversions/goalsand/conversions/contents, each row is a send day:leadsare the people who received the link or content that day and are in Proposal today, andclientsthe ones in Booked or Paid today — the conversions. - In
/conversions/tags,leadsare the leads with that tag in Booked or Paid today, andclientsequalsleads: a tag is either assigned or it isn’t, so there is no lead/client split to report. Until 15 October 2026 this endpoint also counted leads in Proposal.
/performance
Section titled “/performance”One row per publication, ad or lead origin in the period:
[ { "kind": "media", "id": "17900000000000000", "name": null, "mediaId": "17900000000000000", "mediaType": "REELS", "url": "https://www.instagram.com/reel/CxYzAbCdEfG/", "leads": 120, "answered": 86, "qualified": 24, "booked": 9, "paid": 4, "rates": { "answered": 71.67, "qualified": 20, "booked": 7.5, "paid": 3.33 } }]| Field | What it is |
|---|---|
kind | media (a publication or an ad), personalized (an origin you defined), inbound, outbound. |
id | Identifier of the row. On media rows it is the Instagram publication’s. |
name | The name of the personalized origin. Empty on every other row. |
mediaId | Publication identifier. null when the row is not a publication. |
mediaType | STORY, FEED, REELS or AD. null when the row is not a publication. |
url | Public link to the post on Instagram. null when we don’t have it. |
leads | Leads that engaged with that publication or origin. |
answered | Of those, the ones who sent you a DM after a message of yours. Comments don’t count. |
qualified | Of those, the ones that reached Proposal, Booked or Paid. |
booked | The ones now in Booked. |
paid | The ones now in Paid. |
rates | Those same four numbers as a percentage of leads, to two decimals. |
url is what turns a report into something actionable: every row of your dashboard can link to the real post, so whoever reads it goes from the number to the content in one click. It’s a normal Instagram link — stable, public and it doesn’t expire.
qualified includes those who booked and those who paid, so it reads as a funnel: qualified is wider than booked and paid, which exclude each other (a lead in Paid doesn’t count in booked). answered uses the same “Replied” rule as the app —a DM from the lead after a message of yours, whether or not it later ended up Lost—, explained in Statistics and reports. The outbound row is dated by the day the welcome was sent; the rest by the last state change.
/welcome-messages
Section titled “/welcome-messages”[{ "date": "2026-07-01", "sent": 40, "responded": 11, "responseRate": 27.5 }]responseRate is the percentage of sent to two decimals, and it is 0 on days when you sent nothing. Each welcome counts on the day it was sent, and responded follows the definition of Replied: a day’s sends never change, but its replies can still go up.
Date ranges
Section titled “Date ranges”Endpoints marked “Takes dates” accept two parameters:
| Parameter | Format | What it is |
|---|---|---|
startDay | YYYY-MM-DD | First day, included. |
endDay | YYYY-MM-DD | Last day, included. |
curl -H "Authorization: Bearer zcst_your_key" \ "https://app.zerochats.com/api/statistics/v1/leads/daily?startDay=2026-07-01&endDay=2026-07-31"There are four rules:
- Both days are calendar days in your business’s time zone, not timestamps. If your business is in Madrid, July 1st runs from 00:00 to 23:59 Madrid time even if you fire the query from Mexico. That’s why
meta.timezonetravels in every response. - Send both or send neither. Sending just one is an error rather than a guess at the other.
- If you send neither, you get the last 30 days, today included.
- The maximum is 366 days. A longer range is an error; split the query into chunks.
An example of the first two rules together: asking for startDay=2026-07-01&endDay=2026-07-31 gives you the whole of July, all 31 days, first and last row included. Asking for startDay=2026-07-31&endDay=2026-07-01 is an error, because the start has to come before the end. And asking for only startDay=2026-07-01 is an error too, with the message startDay and endDay must be provided together.
All of those errors arrive as a 400 with error.code set to invalid_request: bad date format, only one of the two days, reversed order, or a range longer than 366 days.
If you need many days, ask for one wide range in a single call rather than a day per request: the daily endpoints already return the full series, and one 30-day call is faster and cheaper than thirty one-day calls.
Worked example: a weekly content report
Section titled “Worked example: a weekly content report”A complete, real case: every Monday you want to know which publications brought you clients last week, with a link to each post so you can review them.
-
Work out the range. Last Monday to last Sunday, as
YYYY-MM-DD. For examplestartDay=2026-08-17andendDay=2026-08-23. -
Call
/performancewith that range.Ventana de terminal curl -H "Authorization: Bearer zcst_your_key" \"https://app.zerochats.com/api/statistics/v1/performance?startDay=2026-08-17&endDay=2026-08-23" -
Keep the
kind: "media"rows. Those are the publications and ads;inboundandoutboundare the catch-all buckets and won’t help in this report. -
Sort by
paid— or byrates.paidif you care more about efficiency than volume — and keep the top five. -
Build the message using
url. Every line carries the link to the post, so whoever reads it can open it without hunting for it.Top content, 17–23 August1. REELS — 120 leads · 4 clients (3.33%) → https://www.instagram.com/reel/CxYzAbCdEfG/2. FEED — 86 leads · 3 clients (3.49%) → https://www.instagram.com/p/CxAbCdEfGhI/ -
Publish it wherever you’ll actually read it: a Slack channel, an email, or a new row in a spreadsheet.
If you want business context in that same report, also call /leads/daily with the same range for the week’s total volume, and /conversions/goals to see which sales link closed those clients.
Best practices
Section titled “Best practices”- Store the time zone from
metaalongside the data if you keep it. A number with no record of which calendar it was measured in can’t be compared to another one later. - Use
idas your key, notname. Tag, content and sales-link names can be changed from the app; identifiers can’t. - Ask for ranges, not single days. One 30-day call is faster and cheaper than thirty one-day calls.
Common problems
Section titled “Common problems”Problems with the key — a 401 on a freshly copied key, a key that stops working — are covered in ZeroChats API. These are the ones specific to statistics.
The numbers don’t match what I see in Statistics
Section titled “The numbers don’t match what I see in Statistics”It’s almost always the range. Check you’re asking for the same days — remember both ends are included — and look at meta.timezone: if your tool works out dates in its own time zone and your business is in another, you’re comparing slightly different periods.
/leads/by-weekday and /leads/by-hour also apply a deliberately different criterion, explained above: booked there includes the leads that have already paid.
/performance returns more leads than I have
Section titled “/performance returns more leads than I have”That’s expected and explained above: a lead who engaged several publications counts in all of them. For the business total, use /overview or /leads/daily.
Some /performance rows have url set to null
Section titled “Some /performance rows have url set to null”It means we don’t have the public link for that publication — usually old or deleted content. Show the row anyway, using mediaType as its label, just without a link.