trakily.

API

The public Trakily REST API.

The Trakily API gives your own code the numbers from your dashboard: visitors, pageviews, revenue, goals, breakdowns by channel or country, realtime visitors, subscription metrics and launches. It also accepts custom events and payments from your server, for things that happen outside the browser or providers we don't sync.

It's a JSON REST API at https://trakily.com/api/v1, authenticated with per-website API keys. API access is included in the Scale plan and the free trial.

Authentication

Send your key as a Bearer token in the Authorization header on every request. Requests without a valid key get a 401.

Authorization: Bearer trk_your_api_key
Keep keys on your server

A key can read all of a website's analytics and revenue, and read-write keys can add data. Store keys in environment variables, never in browser code, mobile apps or public repositories. The API sends no CORS headers, so browsers can't call it directly.

API keys

Website owners create keys in Settings → API. Each key belongs to one website and only sees that website. Give each integration its own key so you can revoke one without breaking the others.

  • Read-only keys can call every GET endpoint.
  • Read & write keys can also send events (POST /events) and payments (POST /payments).
  • The full key (trk_ followed by 40 characters) is shown once, when you create it. We store only a hash, so a lost key can't be recovered: create a new one.
  • The list shows each key's first characters, its permissions and when it was last used.
  • Revoking a key takes effect immediately. Revoked keys can then be deleted. A website can have up to 25 keys.

Plans

API access is included in the Scale plan and during the 14-day free trial, even though the trial otherwise runs with Growth limits. After the trial, only Scale includes it: if the plan behind a website changes to one without API access, or the account becomes inactive, its keys get a 403 (plan_required or account_locked) until it's on Scale again. Keys also stop working while the website's only owner has deleted their account (it's restored with the account). See Billing and limits.

Requests and responses

  • Successful responses are JSON with a data object; lists that page also have a pagination object.
  • Money is in the website's reporting currency (in currency), in major units: 49.99 means $49.99. Payments in other currencies are converted with daily ECB rates, the same way the dashboard does it. Revenue is net of refunds.
  • Rates are fractions: 0.0093 is 0.93 %. Durations are seconds.
  • Periods and chart buckets follow the website's timezone. Timestamps are ISO 8601 in UTC; buckets also carry a local label in the website's timezone.
  • Unknown query parameters are rejected with a 400, so a typo never silently returns unfiltered data.
  • Numbers match the dashboard: the API uses the same queries, including excluded countries and imported history (GA4, Plausible) for visitors, sessions, bounce rate and duration. Pageviews count Trakily-tracked data only.

Pagination

/stats/breakdown and /goals take limit (1–1000, default 100) and page (from 1). The response says where you are:

{
  "pagination": {
    "page": 1,
    "limit": 100,
    "total": 240,
    "has_more": true,
    "next_page": 2
  }
}

Rate limits

Each key can make 120 requests per minute, counted in fixed one-minute windows across all endpoints. Every authenticated response says where you stand:

Header
X-RateLimit-Limit
Meaning
Requests allowed per minute (120).
Header
X-RateLimit-Remaining
Meaning
Requests left in the current window.
Header
X-RateLimit-Reset
Meaning
When the window resets, in Unix seconds.
Header
Retry-After
Meaning
On 429 responses: seconds to wait before retrying.

Over the limit you get a 429 with code rate_limited. Wait for Retry-After seconds, then retry. Need more? Use one key per integration.

Errors

Every error has the same shape. details lists each invalid parameter when there is more than one thing to fix.

{
  "error": {
    "code": "invalid_request",
    "message": "granularity: Not available for this period. Use day or week.",
    "details": [
      {
        "param": "granularity",
        "message": "Not available for this period. Use day or week."
      }
    ]
  }
}
Status
400
Code
invalid_request
When
A parameter or body field is missing, unknown or invalid.
Status
401
Code
missing_api_key
When
No Authorization header.
Status
401
Code
invalid_api_key
When
The header is malformed or the key doesn't exist.
Status
401
Code
revoked_api_key
When
The key was revoked.
Status
403
Code
insufficient_scope
When
A read-only key called a write endpoint.
Status
403
Code
plan_required
When
The website's plan doesn't include API access.
Status
403
Code
account_locked
When
The website's Trakily plan is inactive.
Status
403
Code
collection_paused
When
Writes only: collection is paused for the website (billing grace period over, account deleted, or tracking paused in its settings).
Status
404
Code
not_found
When
Unknown endpoint, or a website other than the key's.
Status
413
Code
payload_too_large
When
Request body over 16 KB.
Status
415
Code
unsupported_media_type
When
Body isn't sent as application/json.
Status
429
Code
rate_limited
When
Too many requests this minute.
Status
500
Code
internal_error
When
Something failed on our side. Safe to retry.

GET /sites/{id}

The key's website: domain, timezone, reporting currency and connected payment providers. {id} is the website's id, public id (pls_…) or domain.

curl
curl "https://trakily.com/api/v1/sites/example.com" \
  -H "Authorization: Bearer trk_your_api_key"
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/sites/example.com", {
  headers: { Authorization: `Bearer ${process.env.TRAKILY_API_KEY}` },
});
const { data } = await res.json();
Example response · 200
{
  "data": {
    "id": "219e0982-904a-4bf1-9c02-7f53e8510896",
    "public_id": "pls_examplecom000000001",
    "domain": "example.com",
    "name": "example.com",
    "timezone": "Europe/Berlin",
    "currency": "EUR",
    "tracking_mode": "cookies",
    "allowed_hostnames": [
      "app.example.com"
    ],
    "payment_providers": [
      {
        "provider": "stripe",
        "status": "active"
      }
    ],
    "first_event_at": "2025-10-07T00:09:26.254Z",
    "created_at": "2025-10-05T16:47:20.453Z"
  }
}

GET /stats/summary

Totals for a period: the numbers at the top of your dashboard, plus pageviews and sessions.

Parameter
period
Type
string
Description
One of now, today, yesterday, 24h, 7d, 30d, 3m, 6m, 12m, wtd, mtd, ytd, all. Default 30d. Ranges end now and follow the website's timezone. Like the dashboard, ranges start no earlier than the history the plan shows (Starter 1 year, Growth 3, Scale 5): the response's period.visible_since gives that start and period.history_limited is true when your range was cut.
Parameter
from
Type
date
Description
Start of a custom range, YYYY-MM-DD, inclusive. Use with to instead of period (at most 1,900 days).
Parameter
to
Type
date
Description
End of a custom range, YYYY-MM-DD, inclusive.
Parameter
offset
Type
integer
Description
Shift the period back this many periods (0–500). period=7d&offset=1 is the 7 days before the last 7.
Parameter
filter
Type
string, repeatable
Description
Narrow to sessions matching dimension:value, e.g. filter=country:US. Values are the value keys the breakdown endpoint returns. Operators go after the dimension: country!:US (is not), page~:/blog (contains, case-insensitive), page!~:/blog (does not contain). Up to 14; all must match. Responses echo each filter as { dimension, operator, value }. Imported Google Analytics or Plausible history is included only when the filters can be answered from it (all on one imported dimension); the response's imported object says whether it was (included), up to which day, which metrics it adds to and, in breakdowns, whether the rows include it (in_rows). It's null when the period has no imported data.
Parameter
site_id
Type
string
Description
Optional. The website's id, public id or domain. Keys only work for their own website; anything else is a 404.
Parameter
compare
Type
boolean
Description
Add previous (the period before) and change (relative change, 0.25 = +25 %, null when the previous value was 0).
Metric
visitors
Meaning
Unique visitors.
Metric
pageviews
Meaning
Pageviews.
Metric
sessions
Meaning
Visits.
Metric
bounce_rate
Meaning
Share of sessions with one pageview and under 10 seconds of engagement.
Metric
avg_session_duration
Meaning
Average engaged time per session, in seconds.
Metric
revenue
Meaning
Revenue net of refunds, in the reporting currency.
Metric
customers
Meaning
New paying customers (first payments).
Metric
conversion_rate
Meaning
customers ÷ visitors.
Metric
revenue_per_visitor
Meaning
revenue ÷ visitors.
curl
curl "https://trakily.com/api/v1/stats/summary?period=30d&compare=true" \
  -H "Authorization: Bearer trk_your_api_key"
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/stats/summary?period=30d&compare=true", {
  headers: { Authorization: `Bearer ${process.env.TRAKILY_API_KEY}` },
});
const { data } = await res.json();
Example response · 200
{
  "data": {
    "site_id": "219e0982-904a-4bf1-9c02-7f53e8510896",
    "period": {
      "name": "30d",
      "from": "2026-09-09T00:00:00.000Z",
      "to": "2026-10-08T09:12:44.120Z",
      "timezone": "UTC",
      "visible_since": "2023-10-08T09:12:44.120Z",
      "history_limited": false
    },
    "currency": "USD",
    "filters": [],
    "metrics": {
      "visitors": 7879,
      "pageviews": 16719,
      "sessions": 8390,
      "bounce_rate": 0.3369,
      "avg_session_duration": 115.9,
      "revenue": 9092.02,
      "customers": 73,
      "conversion_rate": 0.0093,
      "revenue_per_visitor": 1.15
    },
    "previous": {
      "period": {
        "from": "2026-08-10T00:00:00.000Z",
        "to": "2026-09-09T00:00:00.000Z",
        "timezone": "UTC"
      },
      "metrics": {
        "visitors": 6861,
        "pageviews": 15080,
        "sessions": 7600,
        "bounce_rate": 0.3507,
        "avg_session_duration": 114.1,
        "revenue": 6447.1,
        "customers": 50,
        "conversion_rate": 0.0073,
        "revenue_per_visitor": 0.94
      }
    },
    "change": {
      "visitors": 0.1484,
      "pageviews": 0.1087,
      "sessions": 0.1039,
      "bounce_rate": -0.0393,
      "avg_session_duration": 0.0158,
      "revenue": 0.4102,
      "customers": 0.46,
      "conversion_rate": 0.274,
      "revenue_per_visitor": 0.2234
    }
  }
}

GET /stats/timeseries

Metrics per hour, day, week or month, for charts. Buckets cover the whole period; the current one is marked partial.

Parameter
period
Type
string
Description
One of now, today, yesterday, 24h, 7d, 30d, 3m, 6m, 12m, wtd, mtd, ytd, all. Default 30d. Ranges end now and follow the website's timezone. Like the dashboard, ranges start no earlier than the history the plan shows (Starter 1 year, Growth 3, Scale 5): the response's period.visible_since gives that start and period.history_limited is true when your range was cut.
Parameter
from
Type
date
Description
Start of a custom range, YYYY-MM-DD, inclusive. Use with to instead of period (at most 1,900 days).
Parameter
to
Type
date
Description
End of a custom range, YYYY-MM-DD, inclusive.
Parameter
offset
Type
integer
Description
Shift the period back this many periods (0–500). period=7d&offset=1 is the 7 days before the last 7.
Parameter
filter
Type
string, repeatable
Description
Narrow to sessions matching dimension:value, e.g. filter=country:US. Values are the value keys the breakdown endpoint returns. Operators go after the dimension: country!:US (is not), page~:/blog (contains, case-insensitive), page!~:/blog (does not contain). Up to 14; all must match. Responses echo each filter as { dimension, operator, value }. Imported Google Analytics or Plausible history is included only when the filters can be answered from it (all on one imported dimension); the response's imported object says whether it was (included), up to which day, which metrics it adds to and, in breakdowns, whether the rows include it (in_rows). It's null when the period has no imported data.
Parameter
site_id
Type
string
Description
Optional. The website's id, public id or domain. Keys only work for their own website; anything else is a 404.
Parameter
granularity
Type
string
Description
minute (period now only), hour, day, week or month. Each period allows a sensible subset (e.g. 30d: day or week); the error lists the allowed ones. Defaults to the dashboard's choice.
Parameter
metrics
Type
comma list
Description
Any of visitors, new_visitors, pageviews, sessions, bounce_rate, avg_session_duration, revenue, refunds, customers, conversion_rate. Default: all.
curl
curl "https://trakily.com/api/v1/stats/timeseries?period=7d&granularity=day&metrics=visitors,revenue" \
  -H "Authorization: Bearer trk_your_api_key"
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/stats/timeseries?period=7d&granularity=day&metrics=visitors,revenue", {
  headers: { Authorization: `Bearer ${process.env.TRAKILY_API_KEY}` },
});
const { data } = await res.json();
Example response · 200
{
  "data": {
    "site_id": "219e0982-904a-4bf1-9c02-7f53e8510896",
    "period": {
      "name": "7d",
      "from": "2026-10-02T00:00:00.000Z",
      "to": "2026-10-08T09:12:44.120Z",
      "timezone": "UTC"
    },
    "granularity": "day",
    "currency": "USD",
    "filters": [],
    "metrics": [
      "visitors",
      "revenue"
    ],
    "points": [
      {
        "start": "2026-10-02T00:00:00.000Z",
        "local": "2026-10-02",
        "partial": false,
        "visitors": 257,
        "revenue": 205.51
      },
      {
        "start": "2026-10-03T00:00:00.000Z",
        "local": "2026-10-03",
        "partial": false,
        "visitors": 206,
        "revenue": 87
      },
      {
        "start": "2026-10-08T00:00:00.000Z",
        "local": "2026-10-08",
        "partial": true,
        "visitors": 98,
        "revenue": 49
      }
    ]
  }
}

GET /stats/breakdown

Visitors, revenue and new customers per channel, referrer, page, country and more, sorted by visitors. Revenue is credited to the session that brought the buyer, the same way the dashboard does it.

Parameter
dimension
Type
string, required
Description
channel, referrer, campaign, page, entry, exit, hostname, country, region, city, browser, os, device, goal.
Parameter
period
Type
string
Description
One of now, today, yesterday, 24h, 7d, 30d, 3m, 6m, 12m, wtd, mtd, ytd, all. Default 30d. Ranges end now and follow the website's timezone. Like the dashboard, ranges start no earlier than the history the plan shows (Starter 1 year, Growth 3, Scale 5): the response's period.visible_since gives that start and period.history_limited is true when your range was cut.
Parameter
from
Type
date
Description
Start of a custom range, YYYY-MM-DD, inclusive. Use with to instead of period (at most 1,900 days).
Parameter
to
Type
date
Description
End of a custom range, YYYY-MM-DD, inclusive.
Parameter
offset
Type
integer
Description
Shift the period back this many periods (0–500). period=7d&offset=1 is the 7 days before the last 7.
Parameter
filter
Type
string, repeatable
Description
Narrow to sessions matching dimension:value, e.g. filter=country:US. Values are the value keys the breakdown endpoint returns. Operators go after the dimension: country!:US (is not), page~:/blog (contains, case-insensitive), page!~:/blog (does not contain). Up to 14; all must match. Responses echo each filter as { dimension, operator, value }. Imported Google Analytics or Plausible history is included only when the filters can be answered from it (all on one imported dimension); the response's imported object says whether it was (included), up to which day, which metrics it adds to and, in breakdowns, whether the rows include it (in_rows). It's null when the period has no imported data.
Parameter
site_id
Type
string
Description
Optional. The website's id, public id or domain. Keys only work for their own website; anything else is a 404.
Parameter
limit
Type
integer
Description
Rows per page, 1–1000 (default 100).
Parameter
page
Type
integer
Description
Page number, from 1.

Each row has a value (the raw key, usable in filter) and a readable label. Countries are ISO codes (US); regions and cities are country|name (US|California); ?? means unknown. With dimension=goal, rows also have completions, and revenue is what visitors who completed the goal paid in the period.

curl
curl "https://trakily.com/api/v1/stats/breakdown?dimension=channel&period=30d&limit=2" \
  -H "Authorization: Bearer trk_your_api_key"
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/stats/breakdown?dimension=channel&period=30d&limit=2", {
  headers: { Authorization: `Bearer ${process.env.TRAKILY_API_KEY}` },
});
const { data } = await res.json();
Example response · 200
{
  "data": {
    "site_id": "219e0982-904a-4bf1-9c02-7f53e8510896",
    "period": {
      "name": "30d",
      "from": "2026-09-09T00:00:00.000Z",
      "to": "2026-10-08T09:12:44.120Z",
      "timezone": "UTC",
      "visible_since": "2023-10-08T09:12:44.120Z",
      "history_limited": false
    },
    "dimension": "channel",
    "currency": "USD",
    "filters": [],
    "total_visitors": 7879,
    "rows": [
      {
        "value": "Social",
        "label": "Social",
        "visitors": 5291,
        "revenue": 5610,
        "customers": 45
      },
      {
        "value": "Direct",
        "label": "Direct",
        "visitors": 1132,
        "revenue": 632,
        "customers": 7
      }
    ]
  },
  "pagination": {
    "page": 1,
    "limit": 2,
    "total": 6,
    "has_more": true,
    "next_page": 2
  }
}

GET /realtime

Visitors active in the last 5 minutes, newest first. limit (0–200, default 50) caps the recent list; visitors is always the full count.

curl
curl "https://trakily.com/api/v1/realtime?limit=1" \
  -H "Authorization: Bearer trk_your_api_key"
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/realtime?limit=1", {
  headers: { Authorization: `Bearer ${process.env.TRAKILY_API_KEY}` },
});
const { data } = await res.json();
Example response · 200
{
  "data": {
    "site_id": "219e0982-904a-4bf1-9c02-7f53e8510896",
    "window_minutes": 5,
    "visitors": 14,
    "customers": 1,
    "recent": [
      {
        "visitor_id": "krhbxpdrd9bn70lxvatiekq05knv998d",
        "customer": false,
        "customer_name": null,
        "page": "/pricing",
        "referrer": "news.ycombinator.com",
        "country": "DE",
        "city": "Berlin",
        "browser": "Chrome",
        "device": "desktop",
        "session_started_at": "2026-10-08T09:10:02.000Z"
      }
    ]
  }
}

GET /goals

Goals completed in the period with completions, unique visitors, conversion rate and the revenue from those visitors. Takes the period and filter parameters plus limit and page. payment is the built-in goal for first payments.

curl
curl "https://trakily.com/api/v1/goals?period=7d" \
  -H "Authorization: Bearer trk_your_api_key"
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/goals?period=7d", {
  headers: { Authorization: `Bearer ${process.env.TRAKILY_API_KEY}` },
});
const { data } = await res.json();
Example response · 200
{
  "data": {
    "site_id": "219e0982-904a-4bf1-9c02-7f53e8510896",
    "period": {
      "name": "7d",
      "from": "2026-10-02T00:00:00.000Z",
      "to": "2026-10-08T09:12:44.120Z",
      "timezone": "UTC"
    },
    "currency": "USD",
    "filters": [],
    "goals": [
      {
        "name": "signup",
        "label": "Sign up",
        "completions": 76,
        "visitors": 76,
        "conversion_rate": 0.0578,
        "revenue": 412
      },
      {
        "name": "payment",
        "label": "Payment",
        "completions": 12,
        "visitors": 12,
        "conversion_rate": 0.0091,
        "revenue": 1203.5
      }
    ]
  },
  "pagination": {
    "page": 1,
    "limit": 100,
    "total": 2,
    "has_more": false,
    "next_page": null
  }
}

GET /saas

Subscription metrics from your connected payment provider: MRR, ARR, churn, retention, LTV and the MRR movements per bucket. Takes the period parameters, granularity and compare. Filters can narrow customers by first touch with channel, referrer or country only. When no provider is connected and there's no payment data, connected is false and metrics is null.

curl
curl "https://trakily.com/api/v1/saas?period=30d" \
  -H "Authorization: Bearer trk_your_api_key"
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/saas?period=30d", {
  headers: { Authorization: `Bearer ${process.env.TRAKILY_API_KEY}` },
});
const { data } = await res.json();
Example response · 200
{
  "data": {
    "site_id": "219e0982-904a-4bf1-9c02-7f53e8510896",
    "connected": true,
    "period": {
      "name": "30d",
      "from": "2026-09-09T00:00:00.000Z",
      "to": "2026-10-08T09:12:44.120Z",
      "timezone": "UTC",
      "visible_since": "2023-10-08T09:12:44.120Z",
      "history_limited": false
    },
    "granularity": "day",
    "currency": "USD",
    "filters": [],
    "metrics": {
      "mrr": 2794.28,
      "arr": 33531.36,
      "net_new_mrr": 819.66,
      "active_subscribers": 105,
      "arpu": 26.61,
      "customer_churn_rate": 0.0526,
      "revenue_churn_rate": 0.0841,
      "net_revenue_retention": 0.9261,
      "ltv": 488.37,
      "ltv_capped": false,
      "trial_conversion_rate": 0.5,
      "refund_rate": 0.0274
    },
    "mrr_series": [
      {
        "start": "2026-10-07T00:00:00.000Z",
        "local": "2026-10-07",
        "partial": false,
        "mrr": 2760.28,
        "new": 58,
        "expansion": 0,
        "reactivation": 0,
        "contraction": 0,
        "churn": -29,
        "net": 29
      }
    ],
    "plans": [
      {
        "name": "Pro",
        "interval": "month",
        "subscribers": 71,
        "trialing": 4,
        "mrr": 2059
      }
    ]
  }
}

GET /export

The same ZIP as Settings → Export: daily stats and every breakdown, raw events, goal completions and payments as CSV files. See Exporting your data for the columns. The response is application/zip, streamed as it's built; errors are the usual JSON.

Parameter
from
Type
date
Description
Required. First day, YYYY-MM-DD in the website's timezone, inclusive.
Parameter
to
Type
date
Description
Required. Last day, inclusive. Dates outside the history the plan shows are left out; the X-Export-From and X-Export-To headers give the dates actually exported.
Parameter
parts
Type
string
Description
Comma-separated: stats, events, payments, goals. Default: all.
Parameter
site_id
Type
string
Description
Optional, as on the other endpoints.

One request can hold up to 100,000 raw rows and 400 days of daily stats. Bigger exports return 413 payload_too_large: ask for fewer days at a time.

curl
curl "https://trakily.com/api/v1/export?from=2026-09-01&to=2026-09-30&parts=events,payments" \
  -H "Authorization: Bearer trk_your_api_key" \
  -o trakily-september.zip

GET /launches

Every saved launch with its match rules, measured window, metrics (visitors, sign-ups, customers, revenue, revenue per visitor), a daily series for the days after it, its rank by revenue per visitor and the verdict line the dashboard shows. Newest first. Each launch has its own window, so the period and filter parameters don't apply. Takes site_id and suggestions=true to also list suggested launches found in mentions, UTM campaigns and referrer spikes. Launches are part of the Growth and Scale plans.

curl
curl "https://trakily.com/api/v1/launches" \
  -H "Authorization: Bearer trk_your_api_key"
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/launches", {
  headers: { Authorization: `Bearer ${process.env.TRAKILY_API_KEY}` },
});
const { data } = await res.json();
Example response · 200
{
  "data": {
    "site_id": "219e0982-904a-4bf1-9c02-7f53e8510896",
    "currency": "USD",
    "visible_since": "2023-10-08T09:12:44.120Z",
    "best_launch_id": "5d0c2f7e-3a52-4c3b-9b8e-0f4f3c1d2a10",
    "launches": [
      {
        "id": "5d0c2f7e-3a52-4c3b-9b8e-0f4f3c1d2a10",
        "name": "Build-in-public thread on X",
        "platform": "x",
        "url": null,
        "launched_at": "2026-09-14T00:03:11.970Z",
        "window_days": 7,
        "goal": "signup",
        "rules": [
          {
            "type": "tco",
            "code": "trjwnkcf7z"
          }
        ],
        "source": "mention",
        "window": {
          "from": "2026-09-14T00:03:11.970Z",
          "to": "2026-09-21T00:03:11.970Z",
          "in_progress": false,
          "history_limited": false,
          "outside_history": false
        },
        "metrics": {
          "visitors": 253,
          "signups": 11,
          "customers": 1,
          "revenue": 169,
          "revenue_per_visitor": 0.67
        },
        "daily": [
          {
            "day": 1,
            "start": "2026-09-14T00:03:11.970Z",
            "visitors": 26,
            "signups": 2,
            "revenue": 0,
            "partial": false
          }
        ],
        "rank": 1,
        "verdict": "$0.67 per visitor. Your best launch. Do more of these."
      }
    ]
  }
}

POST /events

Record a custom event or goal from your server, e.g. a signup confirmed by email or an action in a background job. Needs a read & write key. Events count toward your monthly event allowance and trigger goal alerts like browser events.

Field
name
Type
string, required
Description
Goal name, 1–64 characters, e.g. signup.
Field
props
Type
object
Description
Up to 10 properties. Keys: 1–32 characters of a-z 0-9 _ -. Values: strings (up to 255 characters), numbers or booleans (stored as text).
Field
visitor_id
Type
string
Description
The visitor's id, so the event is credited to the channel that brought them: the part of the _trakily_vid cookie before the first | (see Revenue). Your server can read it from the request cookies.
Field
session_id
Type
string
Description
Attach to a specific session. Must belong to visitor_id when both are sent.
Field
path
Type
string
Description
Page path to record, starting with /. Defaults to the session's last page.
Field
timestamp
Type
date-time
Description
When it happened (ISO 8601 with a timezone). Defaults to now; up to 7 days in the past.

With a visitor_id we know, the event joins that visitor's latest session and inherits its source, location and device (attributed: true). Without one, the event still counts toward the goal; anonymous events share one server-side visitor per day, shown as Direct traffic.

curl
curl -X POST "https://trakily.com/api/v1/events" \
  -H "Authorization: Bearer trk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"signup","visitor_id":"krhbxpdrd9bn70lxvatiekq05knv998d","props":{"plan":"pro"}}'
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/events", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRAKILY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "signup",
    "visitor_id": "krhbxpdrd9bn70lxvatiekq05knv998d",
    "props": {
      "plan": "pro"
    }
  }),
});
const { data } = await res.json();
Example response · 202
{
  "data": {
    "accepted": true,
    "name": "signup",
    "visitor_id": "krhbxpdrd9bn70lxvatiekq05knv998d",
    "session_id": "q2lk3x9a0b7cd8e1f4g5h6",
    "attributed": true,
    "timestamp": "2026-10-08T09:14:03.000Z"
  }
}

POST /payments

Send a payment or refund from a provider we don't sync (or your own billing). Needs a read & write key. Payments appear in revenue, customers and breakdowns exactly like synced ones and are matched to the visitor by visitor_id, session_id or the email of an identified visitor. If you use one of the supported providers, connect it instead so refunds and subscriptions sync on their own.

Field
external_id
Type
string, required
Description
Your id for this payment (order or invoice id), up to 200 characters. Makes the request idempotent.
Field
amount
Type
number, required
Description
Positive amount in major units: 49.99. No more decimals than the currency has (none for JPY).
Field
currency
Type
string, required
Description
ISO 4217 code, e.g. USD, EUR.
Field
type
Type
string
Description
new (default, a new customer), renewal or refund.
Field
email
Type
string
Description
Buyer's email; matched to visitors you identified with the same email.
Field
visitor_id
Type
string
Description
The buyer's visitor id: the most reliable way to attribute.
Field
session_id
Type
string
Description
The session the purchase happened in (the _trakily_sid cookie).
Field
customer_id
Type
string
Description
Your customer id.
Field
subscription_id
Type
string
Description
Groups renewals; renewals inherit the first payment's visitor.
Field
interval
Type
string
Description
one_time, day, week, month or year.
Field
refund_of
Type
string
Description
For refunds: external_id of the original payment (inherits its visitor).
Field
paid_at
Type
date-time
Description
When it was paid (ISO 8601). Defaults to now; up to 5 years back for imports.

Idempotency

external_id identifies the payment. The first request creates it (201, duplicate: false). Any later request with the same external_id changes nothing and returns the stored payment (200, duplicate: true), so retrying after a timeout is always safe.

curl
curl -X POST "https://trakily.com/api/v1/payments" \
  -H "Authorization: Bearer trk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"external_id":"order_1001","amount":49.99,"currency":"USD","email":"[email protected]","visitor_id":"krhbxpdrd9bn70lxvatiekq05knv998d"}'
JavaScript (fetch, server-side)
const res = await fetch("https://trakily.com/api/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRAKILY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "external_id": "order_1001",
    "amount": 49.99,
    "currency": "USD",
    "email": "[email protected]",
    "visitor_id": "krhbxpdrd9bn70lxvatiekq05knv998d"
  }),
});
const { data } = await res.json();
Example response · 201
{
  "data": {
    "id": "937e5b75-70dc-488e-b6c6-bfc21e8b1fe0",
    "external_id": "order_1001",
    "type": "new",
    "amount": 49.99,
    "currency": "USD",
    "email": "[email protected]",
    "paid_at": "2026-10-08T09:14:30.273Z",
    "visitor_id": "krhbxpdrd9bn70lxvatiekq05knv998d",
    "session_id": "q2lk3x9a0b7cd8e1f4g5h6",
    "attributed_by": "metadata",
    "duplicate": false
  }
}

Versioning

The version is in the path: /api/v1. Within v1 we only make additive changes: new endpoints, new optional parameters and new fields in responses. Write clients that ignore fields they don't know. Anything that would break existing code (removing or renaming fields, changing meanings) ships as a new version, and v1 keeps working alongside it with advance notice before it's retired.