Analytics
Pull the same KPIs and clustered visitor questions your B2B dashboard shows: for a BI tool, a scheduled export, or an AI agent that needs to answer "how are we doing" without opening the dashboard.
Both endpoints here are read-only and delegate to the exact same engines the dashboard uses (the KPI board and the Top Questions page), so numbers you pull over the API never diverge from what you see in the app.
Get KPIs
GET /analytics/kpis returns key metrics for a period, each with its trend against the previous equivalent period.
Required scope: analytics:read
| Query parameter | Type | Description |
|---|---|---|
period | string | today, 7_days, 30_days, 90_days, year, total, or custom. Defaults to 30_days. |
start_date | ISO 8601 | Required when period=custom. |
end_date | ISO 8601 | Required when period=custom. |
kpis | string | Comma-separated KPI ids. Defaults to conversations,visitor_messages,add_to_cart,conversion_rate,revenue_generated,csat. An unknown id rejects the whole request; the error lists every valid id. |
Available KPI ids
conversations, visitor_messages, add_to_cart, conversion_rate, revenue_generated, csat, csat_ai, csat_human, interaction_rate, support_conversations, website_visits, appointments_booked, new_contacts, tickets_created, tickets_resolved, product_page_views, conversations_offensive. This list can grow; treat error.details.available on a 400 as the source of truth if you hardcode it.
website_visits and interaction_rate on long periods
Up to 90_days (and its previous-period trend), website_visits counts distinct visitors over the whole window. For year, total and custom ranges reaching further back than 180 days, both KPIs (and the trend) are computed from a daily roll-up: a visitor who came back on several days counts once per day, so the value is slightly higher than a whole-window distinct count. Sessions and conversations are unaffected.
Request
curl "https://api.thehumind.com/public/v1/analytics/kpis?period=30_days&kpis=conversations,revenue_generated,csat" \
-H "Authorization: Bearer hmd_live_..."Response 200 OK
{
"data": {
"conversations": { "total": 842, "change_percent": 12.4, "format": "number" },
"revenue_generated": { "total": 15420.5, "change_percent": -3.1, "format": "currency" },
"csat": { "total": 87.5, "change_percent": 2.0, "format": "percent", "sample": 96 }
},
"period": {
"id": "30_days",
"start": "2026-05-08T00:00:00.000Z",
"end": "2026-06-07T23:59:59.999Z"
}
}| Field | Type | Description |
|---|---|---|
data.<kpi_id>.total | number | The metric's value for the period. |
data.<kpi_id>.change_percent | number | null | Percent change vs. the previous equivalent period. null when there's no comparable previous period (e.g. period=total). |
data.<kpi_id>.format | string | number, percent, or currency: how to render total. |
data.<kpi_id>.sample | integer | Number of underlying observations. Present only on sample-based KPIs (csat, csat_ai, csat_human): render an em dash placeholder instead of the value when sample is 0, since a 0% CSAT with zero ratings would be misleading. |
A custom window:
curl "https://api.thehumind.com/public/v1/analytics/kpis?period=custom&start_date=2026-01-01&end_date=2026-03-31" \
-H "Authorization: Bearer hmd_live_..."Get top questions
GET /analytics/top-questions returns clustered visitor questions over a date range: volume, trend, a sparkline timeline, unanswered rate, and sample questions per group, the "what are my customers asking, and what am I failing to answer" view.
Required scope: analytics:read
| Query parameter | Type | Description |
|---|---|---|
start_date | ISO 8601 | Required. Start of the window. |
end_date | ISO 8601 | Required. End of the window. |
languages | string | Comma-separated ISO 639-1 codes (e.g. fr,en). |
entry_points | string | Comma-separated: launcher, product_page, gift_generator, shop_banner, quiz. |
search | string | Free-text narrowing on the question groups (min 2 chars). |
answered | true | false | true = only answered groups; false = only coverage gaps. |
category | string | Filter to one question category. |
sort_by | string | count (default), recency, or opportunity: sort by the biggest unanswered gaps first. |
page | integer | Page index, 0-based. Defaults to 0. |
limit | integer | Groups per page (1 to 50). Defaults to 20. |
include_stats | true | false | Add period-level stats to the response (slower). Defaults to false. |
Find the coverage gaps worth fixing first
sort_by=opportunity&answered=false surfaces the unanswered question clusters with the highest volume, the fastest way to prioritize what to add to your Knowledge base.
Request
curl "https://api.thehumind.com/public/v1/analytics/top-questions?start_date=2026-05-01&end_date=2026-06-01&sort_by=opportunity&answered=false&limit=10" \
-H "Authorization: Bearer hmd_live_..."Response 200 OK
{
"data": [
{
"_id": "65f1ab9c8e7d4a2b1c3d4e90",
"title": "Do you ship to Canada?",
"label": "shipping_canada",
"title_is_thematic": false,
"category": "shipping",
"count": 34,
"trend": { "direction": "growing", "change_percent": 41.2 },
"timeline": [
{ "date": "2026-05-01T00:00:00.000Z", "count": 3 },
{ "date": "2026-05-02T00:00:00.000Z", "count": 5 }
],
"timeline_unit": "day",
"unanswered_count": 22,
"actionable_unanswered_count": 20,
"unanswered_percentage": 64.7,
"sample_questions": [
"Do you ship to Canada?",
"Is international shipping to Canada available?"
],
"conversation_ids": ["65f1ab9c8e7d4a2b1c3d4e5f", "65f1ab9c8e7d4a2b1c3d4e61"]
}
],
"pagination": { "page": 0, "limit": 10, "total": 27 }
}| Field | Type | Description |
|---|---|---|
_id | string | Internal group id. |
title | string | Display title: an AI-generated label when available, else the raw representative question text. |
label | string | null | AI canonical label. null until the labeling pass has processed the group. |
title_is_thematic | boolean | true when title is an AI-generated thematic reformulation of a diverse cluster rather than a single canonical question. |
category | string | Question category. |
count | integer | Occurrences in the window. |
languages | object[] | Language distribution: { code, count, percentage }. |
entry_points | object[] | Entry-point distribution: { action, count, percentage }. |
trend | object | { direction: "growing" | "stable" | "declining" | "new", change_percent }. change_percent is null when direction is new (no occurrence in the previous period). |
timeline | object[] | Occurrence counts bucketed over the whole window, zero-filled: { date, count }. |
timeline_unit | string | day, week, or month: the timeline's bucket size. |
unanswered_count | integer | Occurrences the quality classifier flagged as a coverage gap. |
actionable_unanswered_count | integer | Subset of unanswered_count with an actionable gap reason. |
unanswered_percentage | number | null | unanswered_count / evaluated, 0-100. null when nothing was evaluated. |
unanswered_reasons | object[] | Why answers failed, most frequent first: { reason, count }. |
avg_satisfaction | number | null | Average CSAT on threads behind this group, when available. |
avg_conversion_intent | number | null | Average AI-estimated conversion intent. |
cart_rate | number | null | Share of occurrences that led to an add-to-cart. |
handoff_rate | number | null | Share of occurrences that led to a human handoff. |
first_seen_at / last_seen_at | ISO 8601 | First and last occurrence in the window. |
sample_questions | string[] | Up to 5 distinct sample question texts from this group. |
conversation_ids | string[] | Sample of up to 10 conversation humind_ids behind this group; pass to GET /conversations/{id}. |
With include_stats=true, a stats object is added alongside data:
{
"stats": {
"total_questions": 512,
"total_groups": 27,
"grouped_percentage": 91.2,
"product_linked_percentage": 38.4,
"unanswered_count": 96,
"unanswered_percentage": 18.8,
"evaluated_count": 512,
"evaluated_questions": 512,
"previous_period": {
"total_questions": 470,
"total_groups": 24,
"product_linked_percentage": 35.1,
"unanswered_percentage": 21.3
}
}
}previous_period is null when the previous-window scan failed or timed out; treat that as unknown, not as a real zero.
Common errors
| Status | Code | When | Fix |
|---|---|---|---|
400 | validation_failed | Bad/missing dates, period=custom without start_date/end_date, or an unknown kpis / entry_points value. details lists what's valid. | Fix the parameter and resend. |
401 | missing_credentials, invalid_key, revoked | Auth header missing, malformed, or the key is no longer active. | See Authentication. |
403 | insufficient_scope | Key lacks analytics:read. | Create a key with the analytics:read scope. |
429 | rate_limited | Too many requests for this key. | Back off using the Retry-After header. See Rate limits. |
Next
- Authentication: generate an
analytics:readkey. - Conversations: pull the full transcript behind a
conversation_idsentry. - Settings: read and update your store configuration over the API.
- MCP server: let an AI agent query KPIs and top questions directly.