Dashboards are useful until you need to combine deep link analytics with data from other systems. An analytics API lets you pull click data, conversion metrics, and campaign performance into your own tools: custom dashboards, BI platforms, Slack bots, or automated reporting pipelines.
This guide covers how to integrate deep link analytics via API. For data export options, see exporting deep link analytics data. For analytics fundamentals, see deep link analytics: measuring what matters.
The analytics dashboard with date range selector, filters, charts, and breakdowns.
API Design Patterns
REST API for Analytics
A typical analytics REST API exposes endpoints for querying click data, conversions, and aggregated metrics:
GET /v1/analytics/clicks # Raw click events
GET /v1/analytics/clicks/summary # Aggregated click metrics
GET /v1/analytics/conversions # Conversion events
GET /v1/analytics/campaigns # Campaign-level metrics
GET /v1/analytics/routes # Route-level metrics
Authentication
Analytics APIs typically use API keys or OAuth tokens:
// API key authentication
const headers = {
'Authorization': `Bearer ${process.env.ANALYTICS_API_KEY}`,
'Content-Type': 'application/json'
};
// Make an authenticated request
async function queryAnalytics(endpoint: string, params: Record<string, string>) {
const url = new URL(`https://api.example.com/v1/analytics/${endpoint}`);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
const response = await fetch(url.toString(), { headers });
if (!response.ok) {
throw new Error(`Analytics API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
Common Query Patterns
Click Summary by Date Range
The most common query: how many clicks, opens, and conversions occurred in a date range?
interface ClickSummary {
total_clicks: number;
app_opens: number;
open_rate: number;
fallbacks: number;
fallback_rate: number;
conversions: number;
conversion_rate: number;
}
async function getClickSummary(
start: string,
end: string,
filters?: { campaign?: string; route?: string; platform?: string }
): Promise<ClickSummary> {
const params: Record<string, string> = { start, end };
if (filters?.campaign) params.campaign = filters.campaign;
if (filters?.route) params.route = filters.route;
if (filters?.platform) params.platform = filters.platform;
return queryAnalytics('clicks/summary', params);
}
// Usage
const summary = await getClickSummary('2026-07-01', '2026-07-19', {
campaign: 'summer-promo'
});
console.log(`${summary.total_clicks} clicks, ${summary.conversion_rate}% conversion`);
Time Series Data
Get metrics over time for charting:
interface TimeSeriesPoint {
date: string;
clicks: number;
app_opens: number;
conversions: number;
}
async function getTimeSeries(
start: string,
end: string,
granularity: 'hour' | 'day' | 'week' | 'month'
): Promise<TimeSeriesPoint[]> {
return queryAnalytics('clicks/timeseries', {
start,
end,
granularity
});
}
Campaign Comparison
Compare performance across campaigns:
interface CampaignMetrics {
campaign: string;
clicks: number;
app_opens: number;
open_rate: number;
conversions: number;
conversion_rate: number;
cpa: number;
}
async function compareCampaigns(
start: string,
end: string
): Promise<CampaignMetrics[]> {
return queryAnalytics('campaigns', {
start,
end,
sort: 'conversions',
order: 'desc'
});
}
Funnel Analysis
Query step-by-step conversion data:
interface FunnelStep {
step: string;
count: number;
rate: number;
drop_off: number;
}
async function getFunnel(
campaign: string,
start: string,
end: string
): Promise<FunnelStep[]> {
return queryAnalytics('funnels', {
campaign,
start,
end,
steps: 'click,app_open,target_screen,conversion'
});
}
Building Custom Dashboards
Fetching Data for a Dashboard
interface DashboardData {
summary: ClickSummary;
timeSeries: TimeSeriesPoint[];
topCampaigns: CampaignMetrics[];
topRoutes: RouteMetrics[];
platformBreakdown: PlatformMetrics[];
}
async function fetchDashboardData(dateRange: { start: string; end: string }): Promise<DashboardData> {
// Fetch all dashboard data in parallel
const [summary, timeSeries, topCampaigns, topRoutes, platformBreakdown] = await Promise.all([
getClickSummary(dateRange.start, dateRange.end),
getTimeSeries(dateRange.start, dateRange.end, 'day'),
compareCampaigns(dateRange.start, dateRange.end),
queryAnalytics('routes', { start: dateRange.start, end: dateRange.end, limit: '10' }),
queryAnalytics('clicks/summary', {
start: dateRange.start,
end: dateRange.end,
group_by: 'platform'
})
]);
return { summary, timeSeries, topCampaigns, topRoutes, platformBreakdown };
}
Caching API Responses
Analytics data for past dates does not change. Cache aggressively:
const cache = new Map<string, { data: any; expires: number }>();
async function cachedQuery(
endpoint: string,
params: Record<string, string>,
ttlMs: number = 300000 // 5 minutes default
): Promise<any> {
const cacheKey = `${endpoint}:${JSON.stringify(params)}`;
const cached = cache.get(cacheKey);
if (cached && cached.expires > Date.now()) {
return cached.data;
}
const data = await queryAnalytics(endpoint, params);
// Cache historical data longer (1 hour) vs recent data (5 minutes)
const isHistorical = new Date(params.end) < new Date(Date.now() - 86400000);
const ttl = isHistorical ? 3600000 : ttlMs;
cache.set(cacheKey, { data, expires: Date.now() + ttl });
return data;
}
Automated Reporting
Slack Integration
Post daily analytics summaries to Slack:
async function postDailySlackReport(webhookUrl: string) {
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
const summary = await getClickSummary(yesterday, yesterday);
const previousDay = new Date(Date.now() - 172800000).toISOString().split('T')[0];
const previous = await getClickSummary(previousDay, previousDay);
const clickChange = ((summary.total_clicks - previous.total_clicks) / previous.total_clicks * 100).toFixed(1);
const convChange = ((summary.conversions - previous.conversions) / previous.conversions * 100).toFixed(1);
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `*Daily Deep Link Report: ${yesterday}*\n` +
`Clicks: ${summary.total_clicks.toLocaleString()} (${clickChange}% vs prev day)\n` +
`App Opens: ${summary.app_opens.toLocaleString()} (${summary.open_rate}% rate)\n` +
`Conversions: ${summary.conversions.toLocaleString()} (${convChange}% vs prev day)\n` +
`Conversion Rate: ${summary.conversion_rate}%`
})
});
}
Email Report Generation
Generate HTML email reports from API data:
async function generateEmailReport(
recipients: string[],
dateRange: { start: string; end: string }
) {
const data = await fetchDashboardData(dateRange);
const html = `
<h2>Deep Link Performance: ${dateRange.start} to ${dateRange.end}</h2>
<table>
<tr><td>Total Clicks</td><td>${data.summary.total_clicks.toLocaleString()}</td></tr>
<tr><td>App Opens</td><td>${data.summary.app_opens.toLocaleString()} (${data.summary.open_rate}%)</td></tr>
<tr><td>Conversions</td><td>${data.summary.conversions.toLocaleString()} (${data.summary.conversion_rate}%)</td></tr>
</table>
<h3>Top Campaigns</h3>
<table>
<tr><th>Campaign</th><th>Clicks</th><th>Conversions</th><th>Rate</th></tr>
${data.topCampaigns.slice(0, 5).map(c =>
`<tr><td>${c.campaign}</td><td>${c.clicks}</td><td>${c.conversions}</td><td>${c.conversion_rate}%</td></tr>`
).join('')}
</table>
`;
await sendEmail({ to: recipients, subject: `Deep Link Report: ${dateRange.start}`, html });
}
Alerting
Trigger alerts when metrics cross thresholds:
interface AlertRule {
metric: string;
condition: 'above' | 'below';
threshold: number;
window: string; // e.g., '1h', '24h'
notify: string[]; // Slack channels, email addresses
}
async function checkAlerts(rules: AlertRule[]) {
for (const rule of rules) {
const end = new Date().toISOString();
const start = subtractDuration(end, rule.window);
const summary = await getClickSummary(start, end);
const value = summary[rule.metric as keyof ClickSummary] as number;
const triggered = rule.condition === 'above'
? value > rule.threshold
: value < rule.threshold;
if (triggered) {
await sendAlert(rule, value);
}
}
}
// Example rules
const alertRules: AlertRule[] = [
{
metric: 'open_rate',
condition: 'below',
threshold: 50,
window: '1h',
notify: ['#deep-links-alerts']
},
{
metric: 'fallback_rate',
condition: 'above',
threshold: 40,
window: '1h',
notify: ['#deep-links-alerts', '[email protected]']
}
];
Error Handling
Retry Logic
Analytics APIs may return temporary errors. Implement retry with exponential backoff:
async function queryWithRetry(
endpoint: string,
params: Record<string, string>,
maxRetries: number = 3
): Promise<any> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(buildUrl(endpoint, params), { headers });
if (response.status === 429) {
// Rate limited: wait and retry
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
await sleep(retryAfter * 1000);
continue;
}
if (response.status >= 500) {
// Server error: exponential backoff
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
} catch (error) {
if (attempt === maxRetries) throw error;
await sleep(Math.pow(2, attempt) * 1000);
}
}
}
Handling Incomplete Data
Analytics data may have delays. Account for data freshness:
interface QueryMetadata {
data_freshness: string; // ISO timestamp of latest data
is_complete: boolean; // Whether the date range has complete data
warning?: string; // e.g., "Data for today may be incomplete"
}
async function queryWithFreshness(
endpoint: string,
params: Record<string, string>
): Promise<{ data: any; metadata: QueryMetadata }> {
const response = await queryAnalytics(endpoint, params);
return {
data: response.data,
metadata: {
data_freshness: response.metadata.freshness,
is_complete: response.metadata.complete,
warning: response.metadata.warning
}
};
}
Third-Party Integrations
BI Tool Integration
Connect analytics APIs to BI tools like Looker, Tableau, or Metabase:
| BI Tool | Connection Method | Notes |
|---|---|---|
| Looker | Custom connector or REST API data source | Real-time queries |
| Tableau | Web data connector | Scheduled refresh |
| Metabase | Direct database connection or API | Point to your data warehouse |
| Google Data Studio | Sheets or BigQuery connector | Export to Sheets/BigQuery first |
Data Pipeline Tools
Use ETL/ELT tools to automate data movement:
| Tool | Approach |
|---|---|
| Airbyte | Custom connector for analytics API |
| Fivetran | Custom connector or webhook ingestion |
| dbt | Transform data after loading to warehouse |
| Custom script | Direct API calls on a cron schedule |
Tolinku for Analytics API
Tolinku's analytics API provides programmatic access to click data, campaign metrics, and conversion events. Query analytics data for custom dashboards, automated reports, and data warehouse integration. See the API reference for available endpoints.
For data export options, see exporting deep link analytics data. For building short links programmatically, see short link APIs: programmatic link creation.
Get deep linking tips in your inbox
One email per week. No spam.