Stats API Reference

Public API for accessing analytics data programmatically

Stats API

The Stats API provides programmatic access to your analytics data for external integrations, custom dashboards, and third-party tools.

Authentication

All Stats API requests require authentication using an API key. API keys can be created in your account dashboard under Settings > API Keys.

Creating an API Key

  1. Navigate to Settings > API Keys
  2. Click Create New API Key
  3. Give your key a descriptive name (e.g., “WordPress Plugin”, “Looker Studio”)
  4. Select permissions (read, write, admin)
  5. Save the API key immediately - you won’t be able to see it again!

Using Your API Key

Include your API key in the Authorization header using the Bearer token format:

Authorization: Bearer zta_your_api_key_here

Base URL

https://your-domain.netlify.app/api/stats-api

Rate Limits

  • 100 requests per minute per API key
  • Rate limit headers are included in all responses:
    • X-RateLimit-Limit: Maximum requests per window
    • X-RateLimit-Remaining: Remaining requests in current window
    • X-RateLimit-Reset: Unix timestamp when the rate limit resets

When rate limited, you’ll receive a 429 Too Many Requests response with a Retry-After header.

Request Format

All requests use the GET method with query parameters:

GET /api/stats-api?site_id=xxx&period=7d&metrics=visitors,pageviews

Required Parameters

ParameterTypeDescription
site_idstringYour site identifier (required)

Optional Parameters

ParameterTypeDefaultDescription
periodstring7dTime period (see Period Options)
date_fromstring-Start date for custom period (YYYY-MM-DD)
date_tostring-End date for custom period (YYYY-MM-DD)
metricsstringvisitors,pageviewsComma-separated metrics (see Metrics)
propertystring-Property breakdown (see Properties)
filtersstring-Filter results (see Filters)

Period Options

PeriodDescription
realtimeLast 5 minutes
dayToday (since midnight)
7dLast 7 days
30dLast 30 days
6moLast 6 months
12moLast 12 months
customCustom date range (requires date_from and date_to)

Metrics

MetricDescription
visitorsUnique visitors
pageviewsTotal pageviews
bounce_rateBounce rate percentage
visit_durationAverage visit duration in seconds
views_per_visitAverage pageviews per visit

You can request multiple metrics by separating them with commas:

metrics=visitors,pageviews,bounce_rate

Properties (Breakdowns)

Use the property parameter to break down metrics by a specific dimension:

PropertyDescription
pageBreak down by page URL
sourceBreak down by referrer/traffic source
countryBreak down by country
deviceBreak down by device type
browserBreak down by browser
osBreak down by operating system

Example:

GET /api/stats-api?site_id=xxx&period=7d&property=page&metrics=visitors,pageviews

Filters

Filter results using the filters parameter. Multiple filters can be combined with semicolons.

Filter Syntax

property==value

Wildcard Support

Use * for wildcard matching:

filters=page==/blog/*

Multiple Filters

Combine filters with semicolons:

filters=page==/blog/*;country==US

Response Format

All successful responses return JSON with the following structure:

{
  "results": [
    {
      "date": "2024-12-11",
      "visitors": 150,
      "pageviews": 420
    },
    {
      "date": "2024-12-10",
      "visitors": 145,
      "pageviews": 398
    }
  ],
  "query": {
    "site_id": "xxx",
    "period": "7d",
    "metrics": ["visitors", "pageviews"]
  }
}

Response Fields

  • results: Array of data points (time series or property breakdown)
  • query: Echo of your query parameters for verification

Property Breakdown Response

When using the property parameter, results are grouped by that property:

{
  "results": [
    {
      "page": "/",
      "visitors": 520,
      "pageviews": 850
    },
    {
      "page": "/blog",
      "visitors": 310,
      "pageviews": 645
    }
  ],
  "query": {
    "site_id": "xxx",
    "period": "7d",
    "property": "page",
    "metrics": ["visitors", "pageviews"]
  }
}

Error Responses

All errors return JSON with an error field:

{
  "error": "Error message description"
}

HTTP Status Codes

CodeDescription
200Success
400Bad Request (invalid parameters)
401Unauthorized (missing or invalid API key)
403Forbidden (no access to this site)
405Method Not Allowed (use GET only)
429Too Many Requests (rate limit exceeded)
500Internal Server Error

Code Examples

cURL

curl -X GET \
  'https://your-domain.netlify.app/api/stats-api?site_id=xxx&period=7d&metrics=visitors,pageviews' \
  -H 'Authorization: Bearer zta_your_api_key_here'

JavaScript (Node.js)

const fetch = require('node-fetch');

async function getStats() {
  const response = await fetch(
    'https://your-domain.netlify.app/api/stats-api?site_id=xxx&period=7d&metrics=visitors,pageviews',
    {
      headers: {
        'Authorization': 'Bearer zta_your_api_key_here'
      }
    }
  );

  const data = await response.json();
  console.log(data);
}

getStats();

JavaScript (Browser)

fetch('https://your-domain.netlify.app/api/stats-api?site_id=xxx&period=7d&metrics=visitors,pageviews', {
  headers: {
    'Authorization': 'Bearer zta_your_api_key_here'
  }
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Python

import requests

url = 'https://your-domain.netlify.app/api/stats-api'
params = {
    'site_id': 'xxx',
    'period': '7d',
    'metrics': 'visitors,pageviews'
}
headers = {
    'Authorization': 'Bearer zta_your_api_key_here'
}

response = requests.get(url, params=params, headers=headers)
data = response.json()
print(data)

Python (with error handling)

import requests
from datetime import datetime, timedelta

class ZTAClient:
    def __init__(self, api_key, base_url='https://your-domain.netlify.app'):
        self.api_key = api_key
        self.base_url = base_url
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {api_key}'
        })

    def get_stats(self, site_id, period='7d', metrics=None, **kwargs):
        """
        Get stats for a site

        Args:
            site_id (str): Site identifier
            period (str): Time period (realtime, day, 7d, 30d, 6mo, 12mo, custom)
            metrics (list): List of metrics to retrieve
            **kwargs: Additional query parameters

        Returns:
            dict: Stats data
        """
        params = {
            'site_id': site_id,
            'period': period
        }

        if metrics:
            params['metrics'] = ','.join(metrics)

        params.update(kwargs)

        response = self.session.get(
            f'{self.base_url}/api/stats-api',
            params=params
        )

        response.raise_for_status()
        return response.json()

# Usage
client = ZTAClient('zta_your_api_key_here')

# Get basic stats
stats = client.get_stats('site_xxx', period='7d', metrics=['visitors', 'pageviews'])
print(f"Total visitors: {sum(r['visitors'] for r in stats['results'])}")

# Get page breakdown
pages = client.get_stats('site_xxx', period='30d', property='page')
print(f"Top page: {pages['results'][0]['page']}")

# Custom date range
stats = client.get_stats(
    'site_xxx',
    period='custom',
    date_from='2024-12-01',
    date_to='2024-12-10',
    metrics=['visitors', 'pageviews', 'bounce_rate']
)

PHP

<?php

function getStats($apiKey, $siteId, $period = '7d', $metrics = 'visitors,pageviews') {
    $url = 'https://your-domain.netlify.app/api/stats-api?' . http_build_query([
        'site_id' => $siteId,
        'period' => $period,
        'metrics' => $metrics
    ]);

    $options = [
        'http' => [
            'header' => "Authorization: Bearer $apiKey\r\n",
            'method' => 'GET'
        ]
    ];

    $context = stream_context_create($options);
    $response = file_get_contents($url, false, $context);

    return json_decode($response, true);
}

// Usage
$apiKey = 'zta_your_api_key_here';
$siteId = 'xxx';
$stats = getStats($apiKey, $siteId, '7d', 'visitors,pageviews');

print_r($stats);
?>

Ruby

require 'net/http'
require 'json'
require 'uri'

class ZTAClient
  def initialize(api_key, base_url = 'https://your-domain.netlify.app')
    @api_key = api_key
    @base_url = base_url
  end

  def get_stats(site_id, period: '7d', metrics: nil, **options)
    params = {
      site_id: site_id,
      period: period
    }

    params[:metrics] = metrics.join(',') if metrics
    params.merge!(options)

    uri = URI("#{@base_url}/api/stats-api")
    uri.query = URI.encode_www_form(params)

    request = Net::HTTP::Get.new(uri)
    request['Authorization'] = "Bearer #{@api_key}"

    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    JSON.parse(response.body)
  end
end

# Usage
client = ZTAClient.new('zta_your_api_key_here')
stats = client.get_stats('site_xxx', period: '7d', metrics: ['visitors', 'pageviews'])
puts stats

Use Cases

WordPress Plugin

Fetch stats to display in the WordPress admin dashboard:

async function fetchStatsForWordPress(siteId, apiKey) {
  const response = await fetch(
    `https://your-domain.netlify.app/api/stats-api?site_id=${siteId}&period=30d&metrics=visitors,pageviews`,
    {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }
  );

  const data = await response.json();

  // Display in WordPress dashboard
  const totalVisitors = data.results.reduce((sum, day) => sum + day.visitors, 0);
  const totalPageviews = data.results.reduce((sum, day) => sum + day.pageviews, 0);

  return { totalVisitors, totalPageviews, dailyStats: data.results };
}

Looker Studio Connector

Create a custom connector for Looker Studio (formerly Google Data Studio):

// Looker Studio connector example
function getData(request) {
  const apiKey = 'zta_your_api_key_here';
  const siteId = request.configParams.siteId;
  const period = request.dateRange || '30d';

  const url = `https://your-domain.netlify.app/api/stats-api?site_id=${siteId}&period=${period}&metrics=visitors,pageviews,bounce_rate`;

  const response = UrlFetchApp.fetch(url, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const data = JSON.parse(response.getContentText());

  // Transform to Looker Studio format
  return {
    schema: [
      { name: 'date', dataType: 'STRING' },
      { name: 'visitors', dataType: 'NUMBER' },
      { name: 'pageviews', dataType: 'NUMBER' },
      { name: 'bounce_rate', dataType: 'NUMBER' }
    ],
    rows: data.results.map(row => ({
      values: [row.date, row.visitors, row.pageviews, row.bounce_rate]
    }))
  };
}

Custom Dashboard

Build a custom analytics dashboard:

async function buildDashboard(siteId, apiKey) {
  // Fetch multiple datasets in parallel
  const [overview, pages, sources, countries] = await Promise.all([
    fetch(`https://your-domain.netlify.app/api/stats-api?site_id=${siteId}&period=7d&metrics=visitors,pageviews,bounce_rate`, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }).then(r => r.json()),

    fetch(`https://your-domain.netlify.app/api/stats-api?site_id=${siteId}&period=7d&property=page&metrics=visitors,pageviews`, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }).then(r => r.json()),

    fetch(`https://your-domain.netlify.app/api/stats-api?site_id=${siteId}&period=7d&property=source&metrics=visitors`, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }).then(r => r.json()),

    fetch(`https://your-domain.netlify.app/api/stats-api?site_id=${siteId}&period=7d&property=country&metrics=visitors`, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    }).then(r => r.json())
  ]);

  return {
    overview: overview.results,
    topPages: pages.results.slice(0, 10),
    topSources: sources.results.slice(0, 10),
    topCountries: countries.results.slice(0, 10)
  };
}

Best Practices

1. Cache Responses

To avoid hitting rate limits, cache API responses:

const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function getCachedStats(siteId, apiKey, period = '7d') {
  const cacheKey = `${siteId}:${period}`;
  const cached = cache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  const response = await fetch(
    `https://your-domain.netlify.app/api/stats-api?site_id=${siteId}&period=${period}`,
    { headers: { 'Authorization': `Bearer ${apiKey}` } }
  );

  const data = await response.json();
  cache.set(cacheKey, { data, timestamp: Date.now() });

  return data;
}

2. Handle Rate Limits Gracefully

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After') || 60;
      console.log(`Rate limited. Retrying after ${retryAfter}s...`);
      await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      continue;
    }

    return response;
  }

  throw new Error('Max retries exceeded');
}

3. Batch Requests

Minimize API calls by requesting multiple metrics at once:

// Good: Single request
const stats = await fetch(
  'https://your-domain.netlify.app/api/stats-api?site_id=xxx&period=7d&metrics=visitors,pageviews,bounce_rate,visit_duration',
  { headers: { 'Authorization': `Bearer ${apiKey}` } }
);

// Avoid: Multiple requests
const visitors = await fetch('...&metrics=visitors');
const pageviews = await fetch('...&metrics=pageviews');
const bounceRate = await fetch('...&metrics=bounce_rate');

4. Secure Your API Keys

  • Never commit API keys to version control
  • Store API keys in environment variables
  • Rotate API keys regularly
  • Use separate keys for different integrations
  • Revoke unused or compromised keys immediately

Support

For API support or feature requests: