TinyTrail API Documentation
API reference for TinyTrail — privacy-focused, cookie-less website analytics engine.
Base URL: http://localhost:8888 (default)
Table of Contents
Authentication
Protected endpoints require an API key passed via the X-API-KEY header.
X-API-KEY: your-api-key-here
Two roles exist:
| Role | Access |
|---|---|
admin |
Full system access (accounts, users, all websites, aggregation) |
user |
Scoped to own account (websites and reports within that account) |
Cross-account access by a user role returns 404 NOT_FOUND (not 403) to prevent resource enumeration.
Response Format
All endpoints use a consistent JSON envelope (except /health).
Success:
{
"status": "success",
"data": { ... }
}
Error:
{
"status": "error",
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description"
}
}
Error Codes
| HTTP Status | Code | Description |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Malformed JSON or missing/invalid fields |
| 401 | UNAUTHORIZED |
Missing or invalid X-API-KEY header |
| 403 | FORBIDDEN |
Valid API key but insufficient permissions |
| 404 | NOT_FOUND |
Resource does not exist (or access denied) |
| 429 | RATE_LIMITED |
Rate limit exceeded |
| 500 | INTERNAL_ERROR |
Unexpected server error |
Rate Limiting
| Scope | Limit | Key |
|---|---|---|
POST /api/v1/collect |
10 requests per 10 seconds | IP |
| All protected routes | 60 requests per 60 seconds | API key |
Rate-limited responses include a Retry-After header (seconds).
Endpoints
Health
GET /health
Health check for load balancers and uptime monitoring. No authentication required.
Response (200):
{
"status": "ok",
"db": "connected",
"version": "0.1.0"
}
Response (503):
{
"status": "degraded",
"db": "disconnected"
}
Note: This endpoint does not use the standard response envelope.
Event Collection
OPTIONS /api/v1/collect
CORS preflight handler.
- Auth: None
- Response:
204 No Content - Headers:
Access-Control-Allow-Origin,Access-Control-Allow-Methods: POST, OPTIONS,Access-Control-Allow-Headers: Content-Type,Access-Control-Allow-Credentials: true,Vary: Origin
POST /api/v1/collect
Public event ingestion endpoint. No authentication required.
Accepts either a single event object or an array of events (batch mode, up to 100 events per request). A batch counts as a single request against the rate limiter.
- Auth: None
- Rate limit: 10 req / 10s per IP (a batch = 1 request)
- Content-Type:
application/json
Request Body (single event):
| Field | Type | Required | Description |
|---|---|---|---|
website_id |
string | Yes | UUID of the target website |
event_type |
string | Yes | Event type (see below) |
url |
string | Yes | Full page URL including query string |
path |
string | Yes | URL pathname |
referrer |
string | No | Referrer URL |
screen_width |
number | No | Viewport width in pixels |
screen_height |
number | No | Viewport height in pixels |
custom_event_name |
string | No | Name for custom events. Required when event_type is custom. |
user_uuid |
string | No | Optional user identifier for logged-in users |
utm_source |
string | No | UTM source parameter |
utm_medium |
string | No | UTM medium parameter |
utm_campaign |
string | No | UTM campaign parameter |
Request Body (batch): A JSON array of event objects (each using the schema above). Maximum 100 events per request.
Default event types: page_view, close_tab, scroll_25, scroll_50, scroll_75, outbound_link, custom
Example (single event):
{
"website_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "page_view",
"url": "https://example.com/blog/post-1?utm_source=twitter",
"path": "/blog/post-1",
"referrer": "https://t.co/abc123",
"screen_width": 1920,
"screen_height": 1080
}
Example (batch):
[
{
"website_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "page_view",
"url": "https://example.com/",
"path": "/"
},
{
"website_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "custom",
"url": "https://example.com/pricing",
"path": "/pricing",
"custom_event_name": "signup_complete"
}
]
Responses (single event):
| Status | Description |
|---|---|
| 202 | {"status":"accepted"} — Event stored successfully |
| 204 | Silent drop (invalid website_id) |
| 400 | INVALID_PAYLOAD — Missing required fields |
| 429 | RATE_LIMITED — Rate limit exceeded |
Responses (batch):
| Status | Description |
|---|---|
| 200 | Batch processed — see body for per-item results |
| 400 | INVALID_PAYLOAD — Empty array or batch larger than 100 events |
| 429 | RATE_LIMITED — Rate limit exceeded |
Batch response body:
{
"status": "success",
"data": {
"received": 2,
"accepted": 1,
"dropped": 1,
"errors": 0,
"results": [
{ "status": "accepted" },
{ "status": "dropped" }
]
}
}
The results array preserves the order of the input events. Per-item status values:
accepted— event was storeddropped— silently dropped (e.g. unknownwebsite_id)error— event was rejected; the item also includeserror: { code, message }
A batch with mixed accepted/dropped/error items still returns HTTP 200; inspect results for per-event outcomes.
Server-side processing pipeline:
- Validate
website_idexists (silent 204 drop if invalid) - Extract IP and User-Agent from request headers
- Parse User-Agent →
browser_family,os_family,device_type - Parse UTM parameters from
urlquery string - Classify
referrer→referrer_channel - Generate
ip_hash= HMAC-SHA256(DailySalt, IP + UA + WebsiteID) - Check IP against
website.excluded_ips→ setis_excluded - Run bot detection → set
is_bot - Match path against
website.content_groupspatterns - Store
custom_event_namefor custom events - Store event row
Accounts
All account endpoints require admin role.
GET /api/v1/accounts
List all accounts.
- Auth: Admin
Response (200):
{
"status": "success",
"data": [
{
"id": "uuid",
"name": "Acme Corp",
"createdAt": "2026-01-15T10:30:00.000Z"
}
]
}
POST /api/v1/accounts
Create a new account.
- Auth: Admin
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Account name |
Response (201):
{
"status": "success",
"data": {
"id": "uuid",
"name": "Acme Corp",
"createdAt": "2026-01-15T10:30:00.000Z"
}
}
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Missing name field |
DELETE /api/v1/accounts/:id
Delete an account and all associated data (users, websites, events, aggregated data).
- Auth: Admin
Path Parameters:
| Param | Type | Description |
|---|---|---|
id |
UUID | Account ID |
Response (200): Deleted account object.
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Invalid UUID format |
| 404 | NOT_FOUND |
Account does not exist |
Users
All user endpoints require admin role.
GET /api/v1/users
List all users (API key field excluded from response).
- Auth: Admin
Response (200):
{
"status": "success",
"data": [
{
"id": "uuid",
"accountId": "uuid",
"email": "user@example.com",
"role": "user",
"createdAt": "2026-01-15T10:30:00.000Z"
}
]
}
POST /api/v1/users
Create a new user. Returns the generated API key (only time it's shown).
- Auth: Admin
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
account_id |
UUID | Yes | Account to assign the user to |
role |
string | Yes | "admin" or "user" |
email |
string | No | Optional label (not used for login) |
Response (201):
{
"status": "success",
"data": {
"id": "uuid",
"accountId": "uuid",
"email": "user@example.com",
"role": "user",
"apiKey": "tt_abc123...",
"createdAt": "2026-01-15T10:30:00.000Z"
}
}
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Missing role or account_id, invalid role, or account not found |
GET /api/v1/users/:id/api-key
Retrieve a user's API key.
- Auth: Admin
Path Parameters:
| Param | Type | Description |
|---|---|---|
id |
UUID | User ID |
Response (200):
{
"status": "success",
"data": {
"id": "uuid",
"apiKey": "tt_abc123..."
}
}
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Invalid UUID format |
| 404 | NOT_FOUND |
User does not exist |
DELETE /api/v1/users/:id
Delete a user.
- Auth: Admin
Path Parameters:
| Param | Type | Description |
|---|---|---|
id |
UUID | User ID |
Response (200): Deleted user object.
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Invalid UUID format |
| 404 | NOT_FOUND |
User does not exist |
Websites
Website endpoints require authentication. Non-admin users are scoped to their own account.
GET /api/v1/websites
List websites. Admins see all; users see only their account's websites.
- Auth: User+
Response (200):
{
"status": "success",
"data": [
{
"id": "uuid",
"accountId": "uuid",
"domain": "example.com",
"contentGroups": [],
"excludedIps": [],
"createdAt": "2026-01-15T10:30:00.000Z"
}
]
}
POST /api/v1/websites
Create a new website.
- Auth: User+
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string | Yes | Domain name (alphanumeric, hyphens, dots; max 255) |
content_groups |
array | No | Array of {name, pattern} objects (default: []) |
excluded_ips |
array | No | IP addresses/ranges to exclude (default: []) |
account_id |
UUID | No | Admin only — assign to specific account |
Response (201): Created website object.
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Missing/invalid domain, or duplicate domain in account |
PATCH /api/v1/websites/:id
Update website settings. At least one field must be provided.
- Auth: User+
Path Parameters:
| Param | Type | Description |
|---|---|---|
id |
UUID | Website ID |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string | No | New domain name |
content_groups |
array | No | Updated content group definitions |
excluded_ips |
array | No | Updated IP exclusion list |
Response (200): Updated website object.
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Invalid ID, invalid domain, or no fields provided |
| 404 | NOT_FOUND |
Website not found or access denied |
DELETE /api/v1/websites/:id
Delete a website and all associated events and aggregated data.
- Auth: User+
Path Parameters:
| Param | Type | Description |
|---|---|---|
id |
UUID | Website ID |
Response (200): Deleted website object.
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Invalid UUID format |
| 404 | NOT_FOUND |
Website not found or access denied |
Admin
POST /api/v1/admin/aggregate
Manually trigger the aggregation pipeline for a specific date.
- Auth: Admin
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
date |
string | No | Target date in YYYY-MM-DD format (default: yesterday UTC) |
Response (200):
{
"status": "success",
"data": {
"message": "Aggregation completed for 2026-03-18"
}
}
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Invalid date format |
Reports
All report endpoints require authentication and are scoped by account for non-admin users.
Common Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
website_id |
UUID | required | Target website |
period |
string | 7d |
Predefined: yesterday, today, 7d, 30d, 90d, 12m, ytd, all |
start_date |
string | — | Custom start date (YYYY-MM-DD). Overrides period. |
end_date |
string | — | Custom end date (YYYY-MM-DD). Overrides period. |
compare |
string | — | previous_period or yoy (year-over-year) |
include_bots |
string | false |
"true" to include bot traffic |
content_group |
string | — | Filter by content group name |
page |
number | 1 |
Pagination page (min: 1) |
per_page |
number | 50 |
Results per page (max: 500) |
format |
string | json |
json or csv |
When format=csv, the response uses Content-Type: text/csv with a Content-Disposition: attachment header.
Common Paginated Response Structure
{
"status": "success",
"data": {
"period": { "start": "YYYY-MM-DD", "end": "YYYY-MM-DD" },
"page": 1,
"per_page": 50,
"rows": [...]
}
}
Common Errors (all report endpoints):
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Missing website_id or invalid parameters |
| 401 | UNAUTHORIZED |
Missing or invalid API key |
| 404 | NOT_FOUND |
Website not found or no access |
GET /api/v1/reports/summary
High-level metrics overview with optional period comparison.
- Auth: User+
- Extra params:
compare(previous_periodoryoy)
Response (200):
{
"status": "success",
"data": {
"period": { "start": "2026-02-23", "end": "2026-03-01" },
"comparison": "2026-02-16 to 2026-02-22",
"metrics": {
"visits": { "value": 12450, "previous": 11200, "change_pct": 11.16 },
"unique_pageviews": { "value": 9870, "previous": 8930, "change_pct": 10.53 },
"pageviews": { "value": 34200, "previous": 30100, "change_pct": 13.62 },
"bounce_rate": { "value": 42.3, "previous": 44.1, "change_pct": -4.08 },
"avg_session_duration_sec": { "value": 185, "previous": 172, "change_pct": 7.56 }
}
}
}
When
compareis not set, each metric contains onlyvalue(nopreviousorchange_pct).
CSV columns: metric, value, previous, change_pct
GET /api/v1/reports/pages
Page-level traffic breakdown sorted by pageviews (descending).
- Auth: User+
- Paginated: Yes
Response (200):
{
"status": "success",
"data": {
"period": { "start": "2026-02-23", "end": "2026-03-01" },
"page": 1,
"per_page": 50,
"rows": [
{
"path": "/blog/getting-started",
"contentGroup": "Blog",
"pageviews": 5420,
"uniquePageviews": 3210
}
]
}
}
CSV columns: path, contentGroup, pageviews, uniquePageviews
GET /api/v1/reports/sources
Traffic source analysis with configurable grouping.
- Auth: User+
- Paginated: Yes
Extra Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
group_by |
string | channel |
channel, domain, or utm |
Response — group_by=channel (200):
{
"rows": [
{ "channel": "Organic Search", "visits": 4500 },
{ "channel": "Direct", "visits": 3200 },
{ "channel": "Social", "visits": 1800 }
]
}
Response — group_by=domain (200):
{
"rows": [
{ "referrer": "google.com", "visits": 3200 },
{ "referrer": "twitter.com", "visits": 1100 }
]
}
Response — group_by=utm (200):
{
"rows": [
{
"utmSource": "twitter",
"utmMedium": "social",
"utmCampaign": "launch",
"visits": 850
}
]
}
Referrer channel classifications:
| Channel | Rule |
|---|---|
| Organic Search | Referrer matches known search engines (google.*, bing.com, etc.) |
| Paid Search | Search engine + utm_medium contains cpc, ppc, or paid |
| Social | Referrer matches social platforms (t.co, facebook.com, etc.) |
utm_medium=email or referrer matches email providers |
|
| Referral | Referrer exists but doesn't match other channels |
| Direct | No referrer |
GET /api/v1/reports/devices
Device, browser, and OS breakdown.
- Auth: User+
- Paginated: No
Response (200):
{
"status": "success",
"data": {
"period": { "start": "2026-02-23", "end": "2026-03-01" },
"device_types": [
{ "deviceType": "desktop", "visits": 8500 },
{ "deviceType": "mobile", "visits": 3200 }
],
"browsers": [
{ "browserFamily": "Chrome", "visits": 6400 },
{ "browserFamily": "Safari", "visits": 3100 }
],
"os_families": [
{ "osFamily": "Windows", "visits": 5200 },
{ "osFamily": "macOS", "visits": 3800 }
],
"screen_resolutions": [
{ "bucket": "1025-1440", "visits": 4200 },
{ "bucket": "376-768", "visits": 2800 }
]
}
}
Screen resolution buckets: 320-375, 376-768, 769-1024, 1025-1440, 1441+, unknown
CSV columns: deviceType, visits (only device_types exported)
GET /api/v1/reports/flow
Landing and exit page analysis.
- Auth: User+
- Paginated: Yes (applies to both lists)
Response (200):
{
"status": "success",
"data": {
"period": { "start": "2026-02-23", "end": "2026-03-01" },
"landing_pages": [
{ "path": "/", "entries": 5200 },
{ "path": "/blog", "entries": 2100 }
],
"exit_pages": [
{ "path": "/pricing", "exits": 3400 },
{ "path": "/contact", "exits": 1800 }
]
}
}
CSV columns: path, entries (only landing_pages exported)
GET /api/v1/reports/funnel
Multi-step conversion funnel analysis.
- Auth: User+
Extra Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
steps[] |
string (array) | Yes | Ordered funnel steps (min 2). Each step is a path, event type, or custom event with event: prefix. |
Step patterns:
| Pattern | Matches |
|---|---|
/pricing |
Page view at /pricing |
event:user_signup |
Custom event named user_signup (any path) |
event:user_signup:/pricing |
Custom event named user_signup on /pricing only |
event:sign* |
Custom events matching wildcard (e.g. signup, signout) |
event:video_play:/blog/* |
Custom event video_play on paths under /blog/ |
page_view |
Any event with event_type = "page_view" |
/blog/* |
Any path under /blog/ |
Example request:
GET /api/v1/reports/funnel?website_id=...&steps[]=/pricing&steps[]=event:user_signup&steps[]=/onboarding
Response (200):
{
"status": "success",
"data": {
"period": { "start": "2026-02-23", "end": "2026-03-01" },
"funnel": [
{ "step": "/pricing", "sessions": 5000, "conversion_rate": 100 },
{ "step": "/signup", "sessions": 1200, "conversion_rate": 24.0 },
{ "step": "/onboarding", "sessions": 900, "conversion_rate": 75.0 }
]
}
}
Conversion rate: First step is always 100%. Subsequent steps: (sessions at step N / sessions at step N-1) * 100, rounded to 2 decimals.
Session logic: Grouped by IP hash with a 30-minute gap threshold.
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Fewer than 2 steps provided |
GET /api/v1/reports/realtime
Real-time traffic for the last 30 minutes. Queries raw events table directly.
- Auth: User+
- Paginated: No
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
website_id |
UUID | Yes | Target website |
Response (200):
{
"status": "success",
"data": {
"active_visitors": 42,
"top_pages": [
{ "path": "/", "visitors": 18 },
{ "path": "/blog/new-post", "visitors": 12 }
],
"events_by_type": [
{ "eventType": "page_view", "count": 87 },
{ "eventType": "scroll_50", "count": 34 }
]
}
}
- Active visitors: Unique IP hashes in 30-minute window
- Top pages: Limited to top 10
- Filters: Excludes bot and excluded traffic
GET /api/v1/reports/custom-events
Custom event tracking and analysis.
- Auth: User+
- Paginated: Yes
Extra Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
event_name |
string | No | Filter by custom event name |
Response (200):
{
"status": "success",
"data": {
"period": { "start": "2026-02-23", "end": "2026-03-01" },
"page": 1,
"per_page": 50,
"rows": [
{
"name": "signup_complete",
"count": 342,
"paths": [
{ "path": "/pricing", "count": 210 },
{ "path": "/blog/getting-started", "count": 132 }
]
},
{
"name": "video_play",
"count": 218,
"paths": [
{ "path": "/", "count": 150 },
{ "path": "/demos", "count": 68 }
]
}
]
}
}
Each row includes a paths array with per-path counts sorted by count descending.
CSV columns: name, count (flat totals only, no path breakdown)
GET /api/v1/reports/timeseries
Daily time-series data for a single metric over the selected period.
- Auth: User+
- Paginated: No
Extra Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
metric |
string | pageviews |
Metric to plot: visits, pageviews, unique_pageviews, bounce_rate, avg_session_duration, landing_page_entries, exit_page_exits |
path |
string | — | Filter to a specific page path (e.g. /about). Omit for site-wide totals. |
Rate metrics (bounce_rate, avg_session_duration) are averaged per day. Count metrics are summed.
Response (200):
{
"status": "success",
"data": {
"period": { "start": "2026-03-13", "end": "2026-03-19" },
"metric": "pageviews",
"path": "*",
"series": [
{ "date": "2026-03-13", "value": 1200 },
{ "date": "2026-03-14", "value": 1350 },
{ "date": "2026-03-15", "value": 980 },
{ "date": "2026-03-16", "value": 870 },
{ "date": "2026-03-17", "value": 1100 },
{ "date": "2026-03-18", "value": 1420 },
{ "date": "2026-03-19", "value": 1290 }
]
}
}
CSV columns: date, value
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PAYLOAD |
Invalid metric value |
GET /api/v1/reports/events
Raw event log for a given time range. Queries the events table directly.
- Auth: User+
- Paginated: Yes
Extra Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
event_type |
string | No | Filter by event type (e.g. page_view, custom) |
path |
string | No | Filter by exact page path (e.g. /blog/post-1) |
Response (200):
{
"status": "success",
"data": {
"period": { "start": "2026-03-24", "end": "2026-03-30" },
"page": 1,
"per_page": 50,
"rows": [
{
"id": "42",
"eventType": "page_view",
"url": "https://example.com/blog/post-1?utm_source=twitter",
"path": "/blog/post-1",
"referrer": "https://t.co/abc123",
"referrerChannel": "Social",
"utmSource": "twitter",
"utmMedium": "social",
"utmCampaign": "launch",
"browserFamily": "Chrome",
"osFamily": "macOS",
"deviceType": "desktop",
"screenWidth": 1920,
"screenHeight": 1080,
"isBot": false,
"userUuid": null,
"customEventName": null,
"createdAt": "2026-03-30T14:22:01.000Z"
}
]
}
}
CSV columns: id, eventType, url, path, referrer, referrerChannel, utmSource, utmMedium, utmCampaign, browserFamily, osFamily, deviceType, screenWidth, screenHeight, isBot, userUuid, customEventName, createdAt
Static Assets
GET /
This API documentation, rendered as an HTML page (text/html; charset=utf-8). The raw markdown is served as text/markdown at GET /docs/api.md.
- Auth: None
- Cache-Control:
public, max-age=3600(1 hour)
GET /js/tracker.js
Client-side tracking snippet (vanilla JavaScript).
- Auth: None
- Rate limit: None
- Content-Type:
application/javascript - Cache-Control:
public, max-age=86400(24 hours)
Installation:
<script defer data-id="YOUR_WEBSITE_UUID" src="https://your-api.com/js/tracker.js"></script>
User identification:
Optionally associate events with a known user by setting data-uuid on the script tag or calling identify() after login:
<script defer data-id="YOUR_WEBSITE_UUID" data-uuid="USER_UUID" src="https://your-api.com/js/tracker.js"></script>
window.tinytrail.identify("user-abc-123");
The identify() call overrides data-uuid for all subsequent events in the session. The user_uuid field is optional and not used in aggregation.
SPA page views:
// Call after each client-side route change
window.tinytrail.pageview();
Sends a page_view event with the current URL and resets scroll depth tracking.
Custom events API:
window.tinytrail.track("signup_complete");
Auto-tracked events:
| Event | Trigger |
|---|---|
page_view |
DOMContentLoaded |
close_tab |
visibilitychange → hidden |
scroll_25 |
25% scroll depth |
scroll_50 |
50% scroll depth |
scroll_75 |
75% scroll depth |
outbound_link |
Click on external link |
Endpoint Summary
| Method | Path | Auth | Rate Limited | Description |
|---|---|---|---|---|
| GET | /health |
No | No | Health check |
| OPTIONS | /api/v1/collect |
No | No | CORS preflight |
| POST | /api/v1/collect |
No | Yes (IP) | Event ingestion |
| GET | /api/v1/accounts |
Admin | Yes | List accounts |
| POST | /api/v1/accounts |
Admin | Yes | Create account |
| DELETE | /api/v1/accounts/:id |
Admin | Yes | Delete account |
| GET | /api/v1/users |
Admin | Yes | List users |
| POST | /api/v1/users |
Admin | Yes | Create user |
| GET | /api/v1/users/:id/api-key |
Admin | Yes | Get user API key |
| DELETE | /api/v1/users/:id |
Admin | Yes | Delete user |
| GET | /api/v1/websites |
User+ | Yes | List websites |
| POST | /api/v1/websites |
User+ | Yes | Create website |
| PATCH | /api/v1/websites/:id |
User+ | Yes | Update website |
| DELETE | /api/v1/websites/:id |
User+ | Yes | Delete website |
| POST | /api/v1/admin/aggregate |
Admin | Yes | Trigger aggregation |
| GET | /api/v1/reports/summary |
User+ | Yes | Summary metrics |
| GET | /api/v1/reports/pages |
User+ | Yes | Page traffic |
| GET | /api/v1/reports/sources |
User+ | Yes | Traffic sources |
| GET | /api/v1/reports/devices |
User+ | Yes | Device analytics |
| GET | /api/v1/reports/flow |
User+ | Yes | Landing/exit pages |
| GET | /api/v1/reports/funnel |
User+ | Yes | Funnel analysis |
| GET | /api/v1/reports/realtime |
User+ | Yes | Real-time activity |
| GET | /api/v1/reports/custom-events |
User+ | Yes | Custom events |
| GET | /api/v1/reports/timeseries |
User+ | Yes | Daily metric timeseries |
| GET | /api/v1/reports/events |
User+ | Yes | Raw event log |
| GET | / |
No | No | API docs (HTML) |
| GET | /docs/api.md |
No | No | API docs (markdown) |
| GET | /js/tracker.js |
No | No | Tracker script |