API v1 Live All Plans

Phona Analytics API

Every Phona chat widget is also a passive analytics engine. Once the widget script is on a page, it silently records page views, traffic sources, visitor geography, device data, time-on-page, and every widget interaction. This API gives you programmatic access to all of it.

What the Phona widget tracks

The Phona embed script is a lightweight (~4 KB) snippet that loads asynchronously and never blocks page rendering. As well as powering the chat widget, it silently collects the following signals on every page where it is installed:

Page-level data

  • Full URL of the page viewed
  • Page title
  • Time on page in seconds (sent via exit beacon when visitor leaves)
  • Timestamp (UTC)

Visitor data

  • IP address — hashed (SHA-256, first 16 chars) for GDPR compliance
  • Country code (ISO 3166-1, from CDN headers)
  • Device type (mobile / tablet / desktop)
  • Browser name (Chrome, Safari, Firefox, Edge, etc.)
  • Operating system (Windows, macOS, iOS, Android, Linux)
  • Screen resolution (width × height)
  • Browser language (e.g. en-GB)

Traffic source

  • Referrer URL (full URL of the referring page)
  • Source classification — Google, Bing, Yahoo, DuckDuckGo, Facebook, Twitter/X, LinkedIn, Instagram, direct, referral
  • Medium — organic, social, referral
  • UTM parameters — utm_source, utm_medium, utm_campaign, utm_content, utm_term (captured from page URL if present)

Widget interactions

  • Widget opened (widget_open)
  • Widget closed with duration in seconds (widget_close)
  • Conversation started / ended (conversation_start, conversation_end)
  • Inquiry submitted (inquiry_submit)
  • Which page the widget was on when each event fired
Privacy note: All data is collected in accordance with GDPR. Visitor IPs are hashed by default. The widget respects Do-Not-Track headers and browser cookie consent signals. Raw IP data is only available on the Agency plan and requires a valid DPA with Phona.

The tracking script

If the Phona chat widget is already installed on a site, analytics tracking is already active — no additional code is needed. The same embed snippet that powers the widget also fires analytics events.

The standard embed snippet looks like this:

HTML embed
<!-- Phona Chat Widget -->
<script src="https://app.phona.app/widget.js"
        data-assistant-id="YOUR_ASSISTANT_ID"
        async></script>

Your unique data-assistant-id is shown in your Phona dashboard under the Install tab for each assistant. Analytics tracking is automatic — no extra configuration needed.

Authentication

All API requests must include your API key as a Bearer token in the Authorization header.

Request header
Authorization: Bearer phona_live_xxxxxxxxxxxxxxxxxxxx

Getting your API key

  1. Log into your Phona dashboard at app.phona.app
  2. Go to Settings → API Access
  3. Click Generate API Key
  4. Copy the key — it is only shown once

Key management endpoint

Use your Supabase session token (not your API key) to manage API keys programmatically:

GET/.netlify/functions/generate-api-key— list active keys
POST/.netlify/functions/generate-api-key— create a new key
DELETE/.netlify/functions/generate-api-key?id=KEY_ID— revoke a key

The full key is returned only on creation. Only the SHA-256 hash is stored. List/delete return the key prefix (first 18 chars + ...) for identification.

API access is available on all plans. Each key is scoped to your own account — you can only query analytics for assistants you own.

Base URL & versioning

https://app.phona.app/api/v1

All responses are JSON. Dates are ISO 8601 strings in UTC. Pagination uses limit and offset query parameters.

The API version is included in the URL path. Breaking changes will increment the version. The current version is v1.

API Reference

All endpoints require the Authorization header unless stated otherwise.

Assistants

GET /assistants

Returns all assistants in your account. Use the id from each assistant to filter analytics by assistant.

Response 200
{
  "data": [
    {
      "id": "ast_01hx...",
      "name": "Acme Corp Website",
      "type": "website_widget",
      "website_url": "https://acme.com",
      "created_at": "2026-01-15T10:30:00Z"
    }
  ],
  "meta": { "total": 3, "limit": 20, "offset": 0 }
}

Page views

GET /analytics/pageviews

Aggregate page view counts over a time range, grouped by day or hour.

Query parameters

Parameter Type Required Description
assistant_id string required ID of the Phona assistant
start_date date required ISO 8601 date, e.g. 2026-01-01
end_date date required ISO 8601 date, inclusive
granularity string optional day (default) or hour
page_url string optional Filter to a specific page path, e.g. /blog/seo-tips
Response 200
{
  "data": [
    { "date": "2026-04-01", "pageviews": 142, "unique_visitors": 98 },
    { "date": "2026-04-02", "pageviews": 189, "unique_visitors": 121 }
  ],
  "summary": {
    "total_pageviews": 331,
    "total_unique_visitors": 219,
    "avg_pageviews_per_day": 165.5
  }
}

Sessions

GET /analytics/sessions

Returns individual visitor sessions with full metadata: entry page, exit page, pages viewed, time on site, traffic source, device, location, and widget interaction summary.

Query parameters

Parameter Type Required Description
assistant_idstringrequiredID of the assistant
start_datedaterequiredStart of date range
end_datedaterequiredEnd of date range
countrystringoptionalISO 3166-1 alpha-2 country code, e.g. GB
sourcestringoptionalFilter by source: google, bing, direct, social, email, referral
device_typestringoptionalmobile, tablet, or desktop
widget_engagedbooleanoptionalIf true, only sessions where the visitor opened the widget
limitintegeroptionalMax records (default 50, max 500)
offsetintegeroptionalPagination offset
Response 200 — one session object
{
  "visitor_id": "v_a3f1b2...",
  "session_date": "2026-04-15",
  "started_at": "2026-04-15T14:23:11Z",
  "ended_at": "2026-04-15T14:31:47Z",
  "duration_seconds": 516,
  "pages_viewed": 4,
  "entry_page": "/blog/seo-guide",
  "exit_page": "/pricing",
  "traffic_source": {
    "source": "google",
    "medium": "organic",
    "referrer_url": "https://www.google.com/",
    "utm_source": null,
    "utm_medium": null,
    "utm_campaign": null,
    "utm_content": null,
    "utm_term": null
  },
  "visitor": {
    "ip_hash": "a3f1b2c4d5e6f7a8",
    "country": "GB",
    "device_type": "desktop",
    "browser": "Chrome",
    "os": "macOS",
    "screen_width": 1440,
    "screen_height": 900,
    "language": "en-GB"
  },
  "widget": {
    "engaged": true,
    "widget_session_seconds": 148
  }
}

Traffic sources

GET /analytics/traffic-sources

Breakdown of sessions by traffic source over the requested period. Useful for understanding how much traffic comes from organic search vs paid vs social vs direct.

Response 200
{
  "data": [
    { "source": "google",   "medium": "organic", "sessions": 412, "pct": 54.2 },
    { "source": "direct",   "medium": null,      "sessions": 189, "pct": 24.9 },
    { "source": "bing",     "medium": "organic", "sessions": 71,  "pct": 9.3  },
    { "source": "twitter",  "medium": "social",  "sessions": 44,  "pct": 5.8  },
    { "source": "email",    "medium": "email",   "sessions": 44,  "pct": 5.8  }
  ],
  "total_sessions": 760
}

Per-page analytics

GET /analytics/pages

Returns a ranked list of pages with view counts, average time on page, and widget engagement rate. Invaluable for SEO audits — you can see exactly which pages are getting traffic, how long people stay, and whether they interact with the widget.

Response 200
{
  "data": [
    {
      "page_url": "/blog/seo-guide",
      "page_title": "Ultimate SEO Guide 2026",
      "pageviews": 892,
      "unique_visitors": 741,
      "avg_time_on_page_seconds": 187,
      "widget_open_rate_pct": 8.3,
      "top_sources": ["google", "direct"]
    }
  ],
  "meta": { "total": 47, "limit": 50, "offset": 0 }
}

Widget events

GET /analytics/widget-events

Raw event stream of widget interactions. Each event has a type, timestamp, and session context. Use this to build custom funnels or trigger downstream actions.

Event types

widget_open widget_close conversation_start conversation_end inquiry_submit

widget_close events include a duration_seconds field (clamped 1–14400 s) indicating how long the widget was open.

Response 200
{
  "data": [
    {
      "id": "uuid...",
      "event_type": "widget_open",
      "visitor_id": "v_a3f1b2...",
      "session_id": "s_01hx...",
      "page_url": "/pricing",
      "duration_seconds": null,
      "created_at": "2026-04-15T14:28:00Z"
    },
    {
      "id": "uuid...",
      "event_type": "widget_close",
      "visitor_id": "v_a3f1b2...",
      "session_id": "s_01hx...",
      "page_url": "/pricing",
      "duration_seconds": 148,
      "created_at": "2026-04-15T14:30:28Z"
    }
  ]
}

Geography

GET /analytics/geography

Session counts grouped by country (ISO 3166-1 alpha-2 code, from CDN headers).

Response 200
{
  "data": [
    { "country": "GB", "sessions": 312, "pct": 41.1 },
    { "country": "US", "sessions": 201, "pct": 26.4 },
    { "country": "AU", "sessions": 88,  "pct": 11.6 }
  ],
  "total_sessions": 760
}

Devices

GET /analytics/devices

Session breakdown by device type, browser, and operating system.

Response 200
{
  "device_types": [
    { "type": "desktop", "sessions": 412, "pct": 54.2 },
    { "type": "mobile",  "sessions": 289, "pct": 38.0 },
    { "type": "tablet",  "sessions": 59,  "pct": 7.8  }
  ],
  "browsers": [
    { "browser": "Chrome",  "sessions": 390, "pct": 51.3 },
    { "browser": "Safari",  "sessions": 241, "pct": 31.7 }
  ],
  "operating_systems": [
    { "os": "Windows", "sessions": 298, "pct": 39.2 },
    { "os": "iOS",     "sessions": 201, "pct": 26.4 },
    { "os": "macOS",   "sessions": 171, "pct": 22.5 }
  ]
}

Data models

TrafficSource
FieldTypeValues
sourcestring | nullgoogle, bing, yahoo, duckduckgo, twitter, facebook, linkedin, instagram, email, direct, referral
mediumstring | nullorganic, social, email, referral
referrer_urlstring | nullFull URL of the referring page
utm_sourcestring | nulle.g. newsletter, google
utm_mediumstring | nulle.g. email, cpc
utm_campaignstring | nulle.g. april-2026
utm_contentstring | nullAd variant / creative identifier
utm_termstring | nullPaid search keyword
VisitorData
FieldTypeNotes
ip_hashstring | nullFirst 16 chars of SHA-256 hash of raw IP
countrystring | nullISO 3166-1 alpha-2 country code from CDN headers
device_typestring | nullmobile, tablet, desktop
browserstring | nullChrome, Safari, Firefox, Edge, etc.
osstring | nullWindows, macOS, iOS, Android, Linux
screen_widthinteger | nullScreen width in logical pixels
screen_heightinteger | nullScreen height in logical pixels
languagestring | nullBrowser language, e.g. en-GB
WidgetEvent
FieldTypeNotes
idstringUUID
event_typestringwidget_open, widget_close, conversation_start, conversation_end, inquiry_submit
visitor_idstringStable per-browser identifier
session_idstring | nullPer-page-load session token
page_urlstring | nullPage where the event fired
duration_secondsinteger | nullSeconds widget was open — only on widget_close, clamped 1–14400
created_atstringISO 8601 UTC timestamp

Rate limits

Plan Requests / minute Requests / day Max date range
All plans6010,000365 days

Rate limit headers are returned on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

Errors

All errors follow the same shape:

{
  "error": {
    "code": "INVALID_DATE_RANGE",
    "message": "end_date must be after start_date",
    "status": 400
  }
}
HTTP status Error code Meaning
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENAPI access requires Agency plan
400INVALID_DATE_RANGEend_date is before start_date, or range exceeds limit
400MISSING_PARAMETERA required query parameter was omitted
404ASSISTANT_NOT_FOUNDThe assistant_id does not belong to this account
429RATE_LIMITEDToo many requests — back off and retry after X-RateLimit-Reset
500INTERNAL_ERRORSomething went wrong on our end — contact support

Code examples

JavaScript (fetch)

javascript
const PHONA_API_KEY = 'phona_live_xxxxxxxxxxxxxxxxxxxx';
const ASSISTANT_ID = 'ast_01hx...';

async function getPageAnalytics(startDate, endDate) {
  const params = new URLSearchParams({
    assistant_id: ASSISTANT_ID,
    start_date: startDate,
    end_date: endDate,
    limit: '100'
  });

  const res = await fetch(
    `https://app.phona.app/api/v1/analytics/pages?${params}`,
    {
      headers: {
        'Authorization': `Bearer ${PHONA_API_KEY}`,
        'Content-Type': 'application/json'
      }
    }
  );

  if (!res.ok) {
    const err = await res.json();
    throw new Error(err.error.message);
  }

  return res.json();
}

// Usage
const data = await getPageAnalytics('2026-04-01', '2026-04-21');
console.log(data.data); // array of page analytics objects

Python (requests)

python
import requests

PHONA_API_KEY = "phona_live_xxxxxxxxxxxxxxxxxxxx"
ASSISTANT_ID  = "ast_01hx..."
BASE_URL      = "https://app.phona.app/api/v1"

headers = {"Authorization": f"Bearer {PHONA_API_KEY}"}

# Get traffic source breakdown
resp = requests.get(
    f"{BASE_URL}/analytics/traffic-sources",
    headers=headers,
    params={
        "assistant_id": ASSISTANT_ID,
        "start_date":   "2026-04-01",
        "end_date":     "2026-04-21",
    }
)
resp.raise_for_status()
sources = resp.json()["data"]

for src in sources:
    print(f"{src['source']:12} {src['sessions']:5} sessions ({src['pct']:.1f}%)")

cURL

shell
curl -X GET \
  "https://app.phona.app/api/v1/analytics/pages?assistant_id=ast_01hx...&start_date=2026-04-01&end_date=2026-04-21" \
  -H "Authorization: Bearer phona_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json"

Ready to integrate?

API access is available on all Phona plans. Create your account, install the widget, and generate your key in minutes.