WarpLink
API Reference

Analytics

GET /v1/analytics returns everything the dashboard Analytics page shows: overview, one link, breakdowns, key numbers and headline, trend, links leaderboard, and link comparison, from the same query service.

GET /v1/analytics reads one of seven surfaces through the same authorized query service behind the dashboard's Analytics page and the MCP query_analytics tool. A REST call and an MCP call with the same input return identical numbers. The dashboard's breakdown grid is the one exception: a filtered card keeps every value of its own dimension visible instead of narrowing it, so a chosen row never hides its siblings, and its rows can differ from the same filter sent to REST or MCP.

Authentication and Permissions

Requires an API key with the analytics:read or links:read scope. SDK keys are refused. Retention and the Free-tier usage gate apply exactly as they do on the dashboard: a window outside your plan's retention, or a Free organization over its monthly click limit, comes back as a named state rather than a silent zero.

Surfaces

surfaceAnswers
overviewOrganization-wide totals for a window
linkOne link's detail (link_id required)
exploreA single-dimension breakdown, with optional filter and compare
changeA comparison of 2 to 5 links (link_ids required)
summaryThe key numbers (clicks, unique clicks, installs, tap-to-install rate, app open share) with change and sparkline, the headline sentence, the top movers, and a spike flag
trendThe chart series by granularity (hour, day, or week), with the previous period aligned bucket by bucket
leaderboardLinks by change (tab=movers), links created in the window (tab=new), or active links with no taps (tab=silent)

A parameter that does not apply to the chosen surface, and any unrecognized query parameter, returns 400 VALIDATION_ERROR.

Query Analytics

GET/analytics

Answer an analytics question

Reads one of seven analytics surfaces through the same authorized query service the dashboard and the MCP query_analytics tool use: overview, one link's detail, a breakdown of one dimension (explore), a comparison of 2-5 links (change), and the analytics page's own signals, summary (KPIs, headline sentence, top movers, spike flag), trend (the chart series by hour, day or week with the previous period aligned bucket by bucket) and leaderboard (links by change, new links, active links with no taps). Requires an API key with the analytics:read or links:read scope. SDK keys are refused. Retention and the Free-tier click limit apply exactly as on the dashboard. Reports accepted_taps, unique_clicks, installs (observed_attributed_installs, or linked_installs when a filter or own_links scope narrows the view) and, on summary and trend, tap_to_install_rate and app_open_share as numerator and denominator. Days, weeks and hours are labeled in the organization's reporting time zone unless timezone is sent. A parameter not applicable to the chosen surface, and any unrecognized query parameter, is a 400 VALIDATION_ERROR. A parameter other than filter sent more than once is also a 400 VALIDATION_ERROR, and so is org: the API key's own organization is always the one queried. On the leaderboard surface, insight.silent_through and insight.silent_state say whether silence can be claimed for the silent tab. A sparkline point that cannot be computed, such as a rate with no denominator, is null.

Parameters

NameTypeInDescription
surface*"overview" | "link" | "explore" | "change" | "summary" | "trend" | "leaderboard"queryWhich question to answer.
window_preset"last_7_days" | "last_30_days" | "this_month" | "previous_month"queryA relative window. Omit and pass from/to instead for an explicit range.
fromstringqueryStart of an explicit window, inclusive. Required together with to when window_preset is omitted.
tostringqueryEnd of an explicit window, exclusive.
timezonestringqueryIANA timezone that labels every day, week and hour and resolves this_month/previous_month. Defaults to the organization's reporting time zone (Settings), else UTC.
link_idstringqueryRequired when surface=link.
link_idsstringqueryComma-separated link ids (2-5 distinct). Required when surface=change.
dimension"country" | "device" | "os" | "browser" | "referrer" | "utm_source" | "source" | "app"queryRequired when surface=explore.
filterstring[]queryRepeatable dimension:value filter, e.g. filter=country:US. An empty value (country:) selects Unknown. Repeat the parameter for one dimension to match any of its values (filter=country:US&filter=country:DE), at most 25 values per dimension; different dimensions combine with AND. Accepted on explore, summary, trend and leaderboard.
compare"previous" | "none" | "true" | "false"queryAlso read the equal-length period just before (previous, or true) or not (none, or false). Defaults to previous on summary, trend and leaderboard and to none on explore. Not accepted on overview, link and change.
granularity"hour" | "day" | "week"querysummary, trend, leaderboard, and explore with row_series=true. Chart bucket size. hour needs a window of at most 7 days, week at least 14 days; day always works. A refused choice is a 400 INVALID_GRANULARITY naming the limit. Defaults to day.
metric"taps" | "unique" | "installs" | "install_rate" | "app_share"querytrend only. The KPI the caller is asking about, echoed in the response. Every metric is returned on every row.
tab"movers" | "new" | "silent"queryleaderboard only. movers lists links by change, new lists links created in the window, silent lists active links with no taps under the same filters. Defaults to movers.
searchstringqueryexplore only. Literal text matched against a row's value or app name; % and _ are ordinary characters. With limit it also sets how many named rows are listed.
row_seriesbooleanqueryexplore only. Add each row's share of the scan, previous count, change and sparkline.
cursorstringqueryAn opaque cursor from a prior response. Reauthorized against the current request; a stale or mismatched cursor is refused.
limitintegerqueryPage size.

Responses

200The query result: schema_version, the query receipt, totals/units, rows, and next_cursor
400Invalid request (unknown surface, missing window, a filter or granularity the surface can't honor)
FieldTypeDescription
error*object
401Unauthorized
FieldTypeDescription
error*object
403Forbidden (including a link outside a member's own_links scope), missing scope, or the Free-tier click limit is used up (ANALYTICS_GATED)
FieldTypeDescription
error*object
404Not found (a link outside the org, or a nonexistent link/org)
FieldTypeDescription
error*object

The window is either a relative window_preset (last_7_days, last_30_days, this_month, previous_month) or an explicit from/to range; sending both is a 400. dimension is required on surface=explore (country, device, os, browser, referrer, utm_source, source, or app). filter is repeatable and only accepted on surface=explore, as dimension:value (an empty value, country:, selects Unknown); this endpoint accepts one value per dimension, unlike the dashboard's own multi-select filters. compare=true on surface=explore also reads the equal-length period immediately before the window.

timezone is an IANA zone (for example America/New_York). It labels every day, week, and hour, and resolves the calendar boundaries of this_month and previous_month. It defaults to the organization's reporting time zone, set in Settings. filter is repeatable (filter=country:US&filter=country:DE): values of one dimension match any, up to 25 per dimension, and different dimensions must all match. compare defaults to previous on summary, trend, and leaderboard; send compare=none to turn it off. Hourly buckets need a window of at most 7 days, and weekly buckets at least 14 days.

The response reports accepted_taps, unique_clicks, and installs: observed_attributed_installs, or linked_installs when a filter or a member's own-links scope narrows the view. tap_to_install_rate and app_open_share are returned as a numerator and a denominator, never as a rounded percent. A value that is gated or outside retention is reported as a named state rather than a plain number, the same way the dashboard shows it.

Worked Example

Comparing two links over the same window:

For a full CSV of raw click rows instead of an aggregated answer, see Exports.

On this page