Skip to content

Instantly share code, notes, and snippets.

@jaamo
Created March 21, 2026 19:09
Show Gist options
  • Select an option

  • Save jaamo/2fa161338167c0c19a041c68aa845ca8 to your computer and use it in GitHub Desktop.

Select an option

Save jaamo/2fa161338167c0c19a041c68aa845ca8 to your computer and use it in GitHub Desktop.
tinytrail api
# TinyTrail API Documentation
API reference for TinyTrail — privacy-focused, cookie-less website analytics engine.
**Base URL:** `http://localhost:8000` (default)
---
## Table of Contents
- [Authentication](#authentication)
- [Response Format](#response-format)
- [Error Codes](#error-codes)
- [Rate Limiting](#rate-limiting)
- [Endpoints](#endpoints)
- [Health](#health)
- [Event Collection](#event-collection)
- [Accounts](#accounts)
- [Users](#users)
- [Websites](#websites)
- [Admin](#admin)
- [Reports](#reports)
- [Summary](#get-apiv1reportssummary)
- [Pages](#get-apiv1reportspages)
- [Sources](#get-apiv1reportssources)
- [Devices](#get-apiv1reportsdevices)
- [Flow](#get-apiv1reportsflow)
- [Funnel](#get-apiv1reportsfunnel)
- [Realtime](#get-apiv1reportsrealtime)
- [Custom Events](#get-apiv1reportscustom-events)
- [Timeseries](#get-apiv1reportstimeseries)
- [Static Assets](#static-assets)
---
## 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:**
```json
{
"status": "success",
"data": { ... }
}
```
**Error:**
```json
{
"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):**
```json
{
"status": "ok",
"db": "connected",
"version": "0.1.0"
}
```
**Response (503):**
```json
{
"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.
- **Auth:** None
- **Rate limit:** 10 req / 10s per IP
- **Content-Type:** `application/json`
**Request Body:**
| 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 |
| `properties` | object | No | Arbitrary custom event data (JSONB) |
| `utm_source` | string | No | UTM source parameter |
| `utm_medium` | string | No | UTM medium parameter |
| `utm_campaign` | string | No | UTM campaign parameter |
**Default event types:** `page_view`, `close_tab`, `scroll_25`, `scroll_50`, `scroll_75`, `outbound_link`, `custom`
**Example:**
```json
{
"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
}
```
**Responses:**
| Status | Description |
| ------ | ----------------------------------------------------------------- |
| 202 | `{"status":"accepted"}` — Event stored successfully |
| 204 | Silent drop (invalid `website_id`, bot, or excluded traffic) |
| 400 | `INVALID_PAYLOAD` — Missing required fields |
| 429 | `RATE_LIMITED` — Rate limit exceeded |
**Server-side processing pipeline:**
1. Validate `website_id` exists (silent 204 drop if invalid)
2. Extract IP and User-Agent from request headers
3. Parse User-Agent → `browser_family`, `os_family`, `device_type`
4. Parse UTM parameters from `url` query string
5. Classify `referrer` → `referrer_channel`
6. Generate `ip_hash` = HMAC-SHA256(DailySalt, IP + UA + WebsiteID)
7. Check IP against `website.excluded_ips` → set `is_excluded`
8. Run bot detection → set `is_bot`
9. Match path against `website.content_groups` patterns
10. Store event row
---
### Accounts
All account endpoints require **admin** role.
#### `GET /api/v1/accounts`
List all accounts.
- **Auth:** Admin
**Response (200):**
```json
{
"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):**
```json
{
"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):**
```json
{
"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):**
```json
{
"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):**
```json
{
"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):**
```json
{
"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):**
```json
{
"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
```json
{
"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_period` or `yoy`)
**Response (200):**
```json
{
"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_visitors": { "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 `compare` is not set, each metric contains only `value` (no `previous` or `change_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):**
```json
{
"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):**
```json
{
"rows": [
{ "channel": "Organic Search", "visits": 4500 },
{ "channel": "Direct", "visits": 3200 },
{ "channel": "Social", "visits": 1800 }
]
}
```
**Response — `group_by=domain` (200):**
```json
{
"rows": [
{ "referrer": "google.com", "visits": 3200 },
{ "referrer": "twitter.com", "visits": 1100 }
]
}
```
**Response — `group_by=utm` (200):**
```json
{
"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.) |
| Email | `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):**
```json
{
"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):**
```json
{
"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 or event type. |
**Example request:**
```
GET /api/v1/reports/funnel?website_id=...&steps[]=/pricing&steps[]=/signup&steps[]=/onboarding
```
**Response (200):**
```json
{
"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):**
```json
{
"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):**
```json
{
"status": "success",
"data": {
"period": { "start": "2026-02-23", "end": "2026-03-01" },
"page": 1,
"per_page": 50,
"rows": [
{ "name": "signup_complete", "count": 342 },
{ "name": "video_play", "count": 218 }
]
}
}
```
**CSV columns:** `name`, `count`
---
#### `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):**
```json
{
"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 |
---
### Static Assets
#### `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:**
```html
<script defer data-id="YOUR_WEBSITE_UUID" src="https://your-api.com/js/tracker.js"></script>
```
**Custom events API:**
```javascript
window.analytics.track("signup_complete", { plan: "pro", source: "pricing_page" });
```
**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 | `/js/tracker.js` | No | No | Tracker script |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment