Analytics
Every search made through SearchStack is counted; send a click when a visitor picks a result, then read what people searched for, what found nothing and what they clicked.
- The console shows the same report under Insights → Analytics.
- Clicks also feed reranker lift, user generated suggestions and related-result ranking (list-route clicks only).
require_clickreads clicks sent to the list route, including ones carrying a group search’squery_id. Group-route clicks are not read.
Record a click
| Route | Use for |
|---|---|
POST /search/analytics/click/list/{account-name}/{list-name} | A hit from a list search. |
POST /search/analytics/click/group/{account-name}/{group-name} | A hit from a group search. |
| Body field | What to send |
|---|---|
query_id | The query_id from the search response. Not checked against a stored search. Suggest and related responses carry none. |
result_id | The clicked hit’s name — not its id. |
list_name | Group clicks: the hit’s list_name. Without it the click counts toward click-through but not reranker lift. A list outside the group is a 400. Ignored on list clicks. |
- Answers
204with no body. - An empty or all-space
query_idorresult_id, or a list or group that doesn’t exist, also gets a204, but nothing is recorded. Omitting either field is a400. - Needs
search-result:readon the list or group. - A click sent to the list route counts in that list’s report, even one carrying a group search’s
query_id, and is then not in the group’s report.require_clickreads only list-route clicks, so a group site choosing it moves those clicks into the list report. - Every API key on an account shares one per-minute search rate limit, and clicks spend it too, so one click per search uses it twice as fast. Over it, clicks and searches both get a
429withRetry-After(seconds) and a problem body naming the same wait.
Read a report
| Route | Report for |
|---|---|
GET /analytics/list/{account-name}/{list-name}?from=&to= | One list. |
GET /analytics/group/{account-name}/{group-name}?from=&to= | One group. |
fromandtoare UTC dates (yyyy-MM-dd), both inclusive.todefaults to today and is capped at today.fromdefaults to 29 days beforeto(a 30-day range). Afromaftertobecomesto.- A range is at most 366 days; an earlier
fromis raised to fit. The response’sfromandtoshow the range used. - A list that does not exist is a
404.
Response fields
| Field | Meaning |
|---|---|
account, scope, target | The account, list or group, and its name. |
from, to | The range used, after defaults and caps. |
total_searches | Searches in the range. |
zero_result_searches | Searches that ran and returned nothing. |
total_clicks | Clicks recorded in the range. |
click_through_rate | total_clicks ÷ total_searches as a fraction (0.25 = 25%); 0 when there were no searches. Can pass 1 when searches get several clicks. |
top_searches | Up to 20 {term, count}, most frequent first. |
top_zero_result_searches | Up to 20 terms that found nothing. |
daily | One point per UTC day (date, searches, zero_result_searches, clicks), zero-filled. |
reranked_searches | Searches a reranker ran on. |
reranker_top_changed_rate | Fraction (0–1) of reranked searches whose top hit changed. |
reranker_mean_displacement | Mean places the results shown moved up. A reorder within the page nets 0, so this measures results pulled up from further down. The console calls it Pulled up from below. |
reranker_click_lift | Mean positions the reranker moved clicked results up; positive is better, 0 with no sample. |
reranker_lift_sample_size | Clicks behind reranker_click_lift. |
catching_up | true while older searches are still being counted, a raw read was throttled, or a busy hour ran past the request’s time — read again shortly. |
What is counted
- Each search that ran counts once: list and group searches, image searches and Ask.
- Suggest (search-as-you-type) and related-result calls are not counted.
- Refused, failed and cancelled searches are not counted.
- Later pages of a search (
skipabove 0) are not counted as searches or terms, so a page past the end never adds a zero-result term. - Search text counts as a term. Terms are compared ignoring case and cut to 100 characters; each day keeps its top 500.
- A group search counts in the group’s report only. A list report leaves out searches that came through a group.
- Eval (including Ask evals), judge and watch searches are not counted.
- A list on an attached index counts only searches made through SearchStack; searches your app sends straight to Azure AI Search are not seen. Image search and related results there are refused with a
400and not counted. - Days counted before these rules shipped can also include suggest and related calls, later pages, and refused, failed or cancelled searches.
- Counting is best-effort: events can be lost when a server crashes or restarts, or when analytics storage is down for more than 10 minutes.
Permissions
- Reading a list report needs
analytics:readon the list or the account. - A group report needs it on the account or on every list in the group today. The report still covers the group’s whole history, including searches made while it held other lists.
- A refused list report names the permission needed (
analytics:readon the account or this list); so does a refused click (search-result:read). - A
503means analytics storage is unavailable or slow; try again shortly (noRetry-After). A group report whose account read fails is also a503, for every caller. - A missing group is a
404for an account-wide key and a403for any other key. - The default Search and read API key can record clicks but not read reports; Read all can do both.
Retention
- Raw search and click events are kept; no automatic purge runs.
- Deleting a list, group or account deletes its analytics.
- List and group deletes purge again about 15 minutes later, catching events still being written when the delete ran.
MCP tools
| Tool | Does |
|---|---|
analytics_get_list | Reads a list report. |
analytics_get_group | Reads a group report. |
record_list_click | Records a click on a list hit. |
record_group_click | Records a click on a group hit. |