Sites
Create, inspect, configure and delete the websites you track. Every other resource hangs off a site, so start here to obtain a siteId.
5 endpoints, all relative to https://analytics.appfor.you. Shared rules live in Conventions, Rate limits and Error codes.
List sites
/api/v2/sitesscope: readReturns the sites owned by the API key's user plus the sites where they are an accepted team member, newest first. If the key was created with a site restriction, only those sites are returned.
- `total` is the full accessible count before pagination is applied.
- Owned sites and sites you joined as a team member are interleaved into ONE global `createdAt` descending order, and `limit`/`offset` are applied by the database — so paging walks the whole accessible set consistently instead of restarting per source.
- Backward compatible with the pre-scopes API: every previously returned field is unchanged. `trackingId` and `isPublic` are new additions.
Query parameters
| Name | Type | Description |
|---|---|---|
| limit | integer | Rows to return. Default 50, minimum 1, maximum 500. |
| offset | integer | Rows to skip, for paging. Default 0. |
Example request
curl -H "Authorization: Bearer $ANALYTICS_API_KEY" \ "https://analytics.appfor.you/api/v2/sites?limit=25"
Example response
{
"sites": [
{
"siteId": "6507f1f77bcf86cd799439011",
"trackingId": "a1b2c3d4e5",
"name": "My Website",
"domain": "example.com",
"isActive": true,
"isPublic": false,
"createdAt": "2025-01-15T10:30:00.000Z"
}
],
"total": 1,
"limit": 25,
"offset": 0
}Create a site
/api/v2/sitesscope: writeCreates a site with a freshly generated 10-character public `trackingId`. Every setting other than name and domain takes its schema default: isActive true, isPublic false, dataRetentionDays 90, botDetectionEnabled true, botLogEnabled true, recordingEnabled false, recordingSamplingRate 100, recordingMaxDuration 600, recordingPrivacyLevel "standard". The response includes the ready-to-paste tracking snippet.
- A site-restricted API key (one created with an explicit site list) cannot create sites and receives 403.
- No plan-based site-count limit is enforced, matching the dashboard behaviour.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Display name. Trimmed; must be non-empty. |
| domain | string | Yes | Domain being tracked, e.g. "example.com" (no protocol, no trailing slash). Trimmed; must be non-empty. |
Example request
curl -X POST "https://analytics.appfor.you/api/v2/sites" \
-H "Authorization: Bearer $ANALYTICS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Marketing site", "domain": "example.com" }'Example response (201)
{
"siteId": "6507f1f77bcf86cd799439011",
"trackingId": "a1b2c3d4e5",
"name": "Marketing site",
"domain": "example.com",
"isActive": true,
"isPublic": false,
"publicSlug": null,
"publicUrl": null,
"dataRetentionDays": 90,
"maxRetentionDays": 90,
"plan": "free",
"botDetectionEnabled": true,
"botLogEnabled": true,
"recordingEnabled": false,
"recordingSamplingRate": 100,
"recordingMaxDuration": 600,
"recordingPrivacyLevel": "standard",
"role": "owner",
"createdAt": "2025-03-21T09:00:00.000Z",
"updatedAt": "2025-03-21T09:00:00.000Z",
"tracking": {
"scriptUrl": "https://analytics.appfor.you/api/sites/a1b2c3d4e5/tracker.js",
"snippet": "<script defer data-site=\"a1b2c3d4e5\" src=\"https://analytics.appfor.you/api/sites/a1b2c3d4e5/tracker.js\"></script>"
}
}Get site detail
/api/v2/sites/{siteId}scope: readReturns identity, active/public state, data-retention settings with the owner's plan cap, bot-detection flags, session-recording settings, the caller's role on the site, and the tracker script URL plus ready-to-paste snippet.
- Returns 404 (never 403) when the site does not exist or you have no access, so other users’ sites are never revealed.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
Example request
curl -H "Authorization: Bearer $ANALYTICS_API_KEY" \ "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011"
Example response
{
"siteId": "6507f1f77bcf86cd799439011",
"trackingId": "a1b2c3d4e5",
"name": "My Website",
"domain": "example.com",
"isActive": true,
"isPublic": true,
"publicSlug": "sunny-otter-42",
"publicUrl": "/share/sunny-otter-42",
"dataRetentionDays": 365,
"maxRetentionDays": 365,
"plan": "starter",
"botDetectionEnabled": true,
"botLogEnabled": true,
"recordingEnabled": false,
"recordingSamplingRate": 100,
"recordingMaxDuration": 600,
"recordingPrivacyLevel": "standard",
"role": "owner",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-03-20T12:00:00.000Z",
"tracking": {
"scriptUrl": "https://analytics.appfor.you/api/sites/a1b2c3d4e5/tracker.js",
"snippet": "<script defer data-site=\"a1b2c3d4e5\" src=\"https://analytics.appfor.you/api/sites/a1b2c3d4e5/tracker.js\"></script>"
}
}Update a site
/api/v2/sites/{siteId}scope: writeEvery field is optional but at least one must be supplied. This one endpoint consolidates what the dashboard splits across site settings, sharing, retention, bot detection and session recording.
- Team members with the `viewer` role are read-only and receive 403.
- `dataRetentionDays` is capped by the SITE OWNER's plan — free 90, starter 365, growth 730, business 1095. Exceeding it returns 400 with a message naming the plan and its limit.
- `isPublic` is OWNER-ONLY. A PATCH carrying it from an accepted team member — including one with the `admin` role — returns 403, and that check runs before any field is applied, so a mixed PATCH cannot partially succeed. Publishing a site exposes its analytics on an unauthenticated `/share/<slug>` URL, so this mirrors the dashboard sharing route, which resolves only for the owner.
- Setting `isPublic` to true mints a `publicSlug` if the site has none; setting it to false clears the slug and revokes the public dashboard URL.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
Request body
Every field is optional, but at least one must be supplied.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | No | New display name (trimmed, non-empty). |
| domain | string | No | New tracked domain (trimmed, non-empty). |
| isActive | boolean | No | false pauses the site. |
| isPublic | boolean | No | Publish or unpublish the read-only public dashboard. OWNER ONLY — a team member of any role, admin included, receives 403 for a PATCH carrying this field. |
| dataRetentionDays | integer | No | Days of raw data to keep, 30–1095, further capped by the owner plan. |
| botDetectionEnabled | boolean | No | Toggle automatic bot filtering. |
| botLogEnabled | boolean | No | Toggle logging of detected bot traffic. |
| recordingEnabled | boolean | No | Toggle session-recording capture. |
| recordingSamplingRate | number | No | Percentage of sessions to record, 0–100. |
| recordingMaxDuration | integer | No | Maximum recording length in seconds, 60–3600. |
| recordingPrivacyLevel | 'standard' | 'strict' | No | "strict" masks more input content in recordings. |
Example request
curl -X PATCH "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011" \
-H "Authorization: Bearer $ANALYTICS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Marketing site (EU)", "dataRetentionDays": 365, "isPublic": true }'Example response
{
"siteId": "6507f1f77bcf86cd799439011",
"trackingId": "a1b2c3d4e5",
"name": "Marketing site (EU)",
"domain": "example.com",
"isActive": true,
"isPublic": true,
"publicSlug": "sunny-otter-42",
"publicUrl": "/share/sunny-otter-42",
"dataRetentionDays": 365,
"maxRetentionDays": 365,
"plan": "starter",
"role": "owner",
"updatedAt": "2025-03-21T09:14:00.000Z"
}Delete a site
/api/v2/sites/{siteId}scope: adminDestructiveRequires the `admin` scope, requires the caller to be the site OWNER (accepted team members with the admin role receive 403), and requires an explicit confirmation. Pass `confirm=<site id>` as a query parameter or as a `confirm` field in the JSON body, repeating whichever identifier you used in the URL (the Mongo _id or the public trackingId).
Irreversible. Requires the admin scope and confirm={siteId} — see Destructive operations.
- Cascade — every child document is removed before the Site itself, across all 23 collections listed in `cascaded`: RecordingChunk, PageView, Event, Goal, Funnel, TeamMember, UserProfile, SessionRecording, SessionSummary, RevenueEvent, WebVital, BotEvent, BotRule, Segment, Alert, Webhook, Dashboard, Annotation, ReportConfig, Insight, Integration, CustomDimension, CustomDimensionValue. No visitor PII (session recordings, visitor profiles, revenue events, Web Vitals) is left orphaned.
- RecordingChunk is keyed by recording, not by site, so it is resolved from the site’s SessionRecording ids and cleared first.
- Children are deleted BEFORE the Site document, so a failure part-way through leaves the site intact and the call safe to retry.
- API keys restricted to this site are deliberately NOT rewritten. An empty site list means "all of the owner’s sites", so pulling the deleted id out of a single-site key would silently widen it. A stale id simply never matches again.
- `deletedCounts` reports the number of documents removed per collection and `totalDeleted` their sum; the Site document itself is not counted.
- Without a confirmation you get 400 naming the exact value to send.
Path parameters
| siteId | string | The Mongo _id returned as `siteId` by GET /api/v2/sites, or the public `trackingId` embedded in the tracking snippet. Both are accepted everywhere. |
Query parameters
| Name | Type | Description |
|---|---|---|
| confirm | string | Destructive-operation confirmation. Must exactly equal the `siteId` in the URL. May be sent as a `confirm` field in the JSON body instead. |
Request body
Optional. Either send no body and use `?confirm=<siteId>`, or send `{ "confirm": "<siteId>" }`.
| Field | Type | Required | Description |
|---|---|---|---|
| confirm | string | No | Must exactly equal the site identifier used in the URL. |
Example request
curl -X DELETE "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011?confirm=6507f1f77bcf86cd799439011" \ -H "Authorization: Bearer $ANALYTICS_API_KEY"
Example response
{
"deleted": true,
"siteId": "6507f1f77bcf86cd799439011",
"trackingId": "a1b2c3d4e5",
"cascaded": ["RecordingChunk","PageView","Event","Goal","Funnel","TeamMember","UserProfile","SessionRecording","SessionSummary","RevenueEvent","WebVital","BotEvent","BotRule","Segment","Alert","Webhook","Dashboard","Annotation","ReportConfig","Insight","Integration","CustomDimension","CustomDimensionValue"],
"deletedCounts": {
"RecordingChunk": 18402,
"PageView": 152110,
"Event": 4820,
"Goal": 3,
"Funnel": 1,
"TeamMember": 2,
"UserProfile": 9120,
"SessionRecording": 640,
"SessionSummary": 640,
"RevenueEvent": 212,
"WebVital": 30110,
"BotEvent": 880,
"BotRule": 0,
"Segment": 4,
"Alert": 2,
"Webhook": 1,
"Dashboard": 1,
"Annotation": 7,
"ReportConfig": 1,
"Insight": 12,
"Integration": 1,
"CustomDimension": 2,
"CustomDimensionValue": 5504
},
"totalDeleted": 222475
}