Swayzio Core API v1 — base path /api/swayzio/v1
Core APIAnalytics

Analytics

Referral and share‑link analytics powered by Dub. These endpoints expose click / geo / device / conversion rollups for the links Swayzio creates when you share packs and tracks. They are owner‑scoped and native‑only — they are served by the Vercel runtime and never proxy to the Fastify Core API (see Conventions).

All routes live under https://app.swayzio.com/api/swayzio/v1/analytics and require a Clerk actor. The server forces tenantId = ownerId on every Dub query, so a caller can only ever see their own links — you cannot pass an owner/tenant id to read another account’s analytics.

Endpoints

MethodPathPurposeAuthRequestResponse
GET/v1/analyticsAggregate link analytics (counts, timeseries, dimensions)Clerk actor(query, below)dubAnalyticsResponseSchema
GET/v1/analytics/eventsRaw, PII‑free event streamClerk actor(query, below)dubAnalyticsEventsResponseSchema
POST/v1/analytics/conversionRecord a share‑link signup (lead) from the click cookieClerk actor(none){ tracked, reason? }
⚠️

Revenue is redacted for non‑staff. sales and saleAmount are referral‑attribution figures that expose Swayzio’s per‑subscription pricing. The server zeroes them for any caller that is not verified internal staff (a confirmed @swayzio.com email) and sets revenueVisible: false on the response. This is enforced server‑side, not merely hidden in the UI — non‑staff clients never receive the real numbers.

When Dub isn’t connected or the workspace plan doesn’t include analytics, the response is not an error: it returns available: false with a reason (one of dub_disabled, plan_gated, rate_limited, provider_error). Only a hard Dub rate‑limit surfaces as 429 { "error": "analytics_rate_limited" }, and an unexpected provider failure on the aggregate route is 502 { "error": "analytics_provider_error" }.

GET /v1/packs/:id/analytics (see Packs) is a separate, native‑event rollup — the view / play / download funnel recorded on the public share page. That surface is Swayzio’s own product telemetry; this Analytics surface is Dub’s click / geo / conversion data. They do not share a schema.

GET /v1/analytics

Aggregate analytics. Pick what to count with event, how to slice it with groupBy, and the window with either interval or an explicit start+end (+ optional timezone).

Query paramValuesDefault
eventclicks · leads · sales · compositecomposite
groupBycount · timeseries · continents · countries · regions · cities · devices · browsers · os · triggers · referers · referer_urls · top_links · top_urlscount
interval24h · 7d · 30d · 90d · mtd · qtd · ytd · 1y · all30d
start / endISO dates (use instead of interval)
timezoneIANA‑style zone, max 64 chars

Filters (any combination): browser, city, continent, country, linkId, os, referer, region, trigger. Plus externalId — your provider external id; the server auto‑prefixes it with ext_ for Dub if not already present.

curl -s "https://app.swayzio.com/api/swayzio/v1/analytics?event=clicks&groupBy=countries&interval=30d" \
  -H "Authorization: Bearer $SWAYZIO_TOKEN"
{
  "available": true,
  "event": "clicks",
  "groupBy": "countries",
  "range": { "interval": "30d" },
  "revenueVisible": false,
  "items": [
    { "label": "United States", "country": "US", "clicks": 412, "leads": 18, "sales": 0, "saleAmount": 0 },
    { "label": "United Kingdom", "country": "GB", "clicks": 96, "leads": 4, "sales": 0, "saleAmount": 0 }
  ],
  "retrievedAt": "2026-06-10T00:00:00.000Z"
}

Response shape by groupBy:

  • countcount: { clicks, leads, sales, saleAmount }
  • timeseriestimeseries: [{ start, clicks, leads, sales, saleAmount }]
  • any dimension → items: [{ label, country?, region?, city?, linkId?, shortLink?, url?, entity?, clicks, leads, sales, saleAmount }]

When groupBy=top_links, items are enriched with the local entity they map to, so you can resolve which pack or track a Dub link belongs to:

{ "entityType": "pack | track | split_sheet", "entityId": "…", "entityName": "…", "localLinkType": "…", "localLinkId": "…" }

GET /v1/analytics/events

A raw, ordered event stream (most recent first). Intended for an activity feed, not aggregation.

Query paramValuesDefault
eventclicks · leads · salesclicks
interval / start+end / timezoneas above30d
limit110050
filterssame filter set as the aggregate route

Events are deliberately PII‑free: no IP, no user agent, no customer identity, and only the referer domain (never the full URL). On a hard Dub limit the route returns 429 analytics_rate_limited.

curl -s "https://app.swayzio.com/api/swayzio/v1/analytics/events?event=leads&limit=20" \
  -H "Authorization: Bearer $SWAYZIO_TOKEN"
{
  "available": true,
  "revenueVisible": false,
  "events": [
    {
      "event": "lead",
      "timestamp": "2026-06-09T18:02:11.000Z",
      "country": "US",
      "region": "California",
      "city": "Los Angeles",
      "device": "mobile",
      "browser": "Mobile Safari",
      "os": "iOS",
      "refererDomain": "instagram.com",
      "linkId": "link_…",
      "shortLink": "https://swyz.io/abc",
      "conversionEventName": "Share link signup"
    }
  ],
  "retrievedAt": "2026-06-10T00:00:00.000Z"
}

For non‑staff callers, saleAmount is stripped from every event (and revenueVisible is false).

POST /v1/analytics/conversion

Records a Dub lead (“Share link signup”) attributed to the signed‑in owner. The client calls this once after sign‑up when the dub_id click cookie is present — i.e. the user arrived via a Swayzio share link.

This is best‑effort and never errors: if there is no click cookie or Dub rejects the call, it returns { tracked: false, reason } so the client can stop retrying. Dub dedupes on (customer, eventName), so repeat calls are harmless. The body is empty; the click id is read from the request cookie, and the customer is the authenticated owner.

curl -s -X POST https://app.swayzio.com/api/swayzio/v1/analytics/conversion \
  -H "Authorization: Bearer $SWAYZIO_TOKEN" \
  -H "Cookie: dub_id=<click-id-from-share-link>"
{ "tracked": true }
// no click cookie present
{ "tracked": false, "reason": "no_click_id" }

See Data Contracts for the full dubAnalyticsResponseSchema and dubAnalyticsEventsResponseSchema field detail.