Goals
Conversion goals. A `pageview` goal converts when a visitor loads a pathname; an `event` goal converts when a named custom event fires.
5 endpoints, all relative to https://analytics.appfor.you. Shared rules live in Conventions, Rate limits and Error codes.
List goals
/api/v2/sites/{siteId}/goalsscope: readEach goal carries the number of unique converting sessions for the window. Available to the site owner and to accepted team members of any role.
- The resolved window is echoed as a `range` OBJECT — `{ preset, since, until }` — and only when `includeConversions` is not disabled. The earlier flat `range`/`since`/`until` fields no longer exist.
- Conversion counts for the whole page are computed with at most two aggregations regardless of `limit`, so requesting `limit=500` costs the same as `limit=10`.
- An unusable window (unparseable `from`/`to`, or a window that ends before it starts) returns 400 rather than silently reporting zero conversions. `GET /goals/{goalId}` and `GET /funnels/{funnelId}/stats` now behave identically.
- Date-only `from`/`to` cover WHOLE days: `to=2025-03-21` runs through `2025-03-21T23:59:59.999Z`, so `from=X&to=X` is a full 24-hour day. It previously produced an empty window that always reported zero.
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 |
|---|---|---|
| range | string | Relative window: `24h`, `7d`, `30d`, `90d` or `365d`. Default `30d`. Overridden by `from`/`to`. |
| from | string (ISO-8601 or YYYY-MM-DD) | Explicit window start, overriding `range`. A date-only `YYYY-MM-DD` value means that day from `T00:00:00.000Z`; a value carrying a time is an instant and is used verbatim. Supplying only `from` gives `[from, now]`. An unparseable value, or a window that ends before it starts, returns 400. |
| to | string (ISO-8601 or YYYY-MM-DD) | Explicit window end, overriding `range`. A date-only `YYYY-MM-DD` value covers the WHOLE day, through `T23:59:59.999Z` — the final day is included, and `from=X&to=X` is a full 24 hours rather than the zero-width window it used to be. A value carrying a time is used verbatim. An end in the future is clamped to now, except where clamping would invert the window. Supplying only `to` gives `[to - range, to]`. An unparseable value, or a window that ends before it starts, returns 400. |
| includeConversions | boolean | Set to `false` or `0` to skip the conversion aggregation (much faster). Any other value, and omitting it, counts as true. |
| 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/6507f1f77bcf86cd799439011/goals?range=30d"
Example response
{
"goals": [
{
"goalId": "6512aa01bcf86cd7994390a1",
"siteId": "6507f1f77bcf86cd799439011",
"name": "Signup completed",
"type": "pageview",
"target": "/welcome",
"conversions": 213,
"createdAt": "2025-02-01T08:00:00.000Z",
"updatedAt": "2025-02-01T08:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0,
"range": {
"preset": "30d",
"since": "2025-02-19T00:00:00.000Z",
"until": "2025-03-21T00:00:00.000Z"
}
}Create a goal
/api/v2/sites/{siteId}/goalsscope: writeType `pageview` matches a PageView whose pathname equals `target`; type `event` matches an Event whose name equals `target`. Call the events endpoint first if you need to discover valid event names.
- Team members with the `viewer` role receive 403.
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
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Display name. Non-empty after trimming. |
| type | 'pageview' | 'event' | Yes | What kind of activity converts this goal. |
| target | string | Yes | Pathname (type=pageview) or custom event name (type=event). Non-empty after trimming. |
Example request
curl -X POST "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/goals" \
-H "Authorization: Bearer $ANALYTICS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Signup completed", "type": "pageview", "target": "/welcome" }'Example response (201)
{
"goalId": "6512aa01bcf86cd7994390a1",
"siteId": "6507f1f77bcf86cd799439011",
"name": "Signup completed",
"type": "pageview",
"target": "/welcome",
"createdAt": "2025-03-21T09:00:00.000Z",
"updatedAt": "2025-03-21T09:00:00.000Z"
}Get a goal
/api/v2/sites/{siteId}/goals/{goalId}scope: readAccepts the same window parameters as the list endpoint.
- Returns 404 "Goal not found" when the id is malformed or belongs to another site.
- Unlike the list endpoint, this one echoes a FLAT `range` string alongside top-level `since` and `until` rather than a `range` object. Those three fields, and `conversions`, are omitted entirely when `includeConversions` is disabled.
- An unparseable or inverted `from`/`to` returns 400 here too, matching the list endpoint; it is no longer silently downgraded to the `range` preset. Date-only values cover whole days, so `from=X&to=X` is a full 24-hour day rather than an empty window.
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. |
| goalId | string | Identifier of the goal. |
Query parameters
| Name | Type | Description |
|---|---|---|
| range | string | Relative window: `24h`, `7d`, `30d`, `90d` or `365d`. Default `30d`. Overridden by `from`/`to`. |
| from | string (ISO-8601 or YYYY-MM-DD) | Explicit window start, overriding `range`. A date-only `YYYY-MM-DD` value means that day from `T00:00:00.000Z`; a value carrying a time is an instant and is used verbatim. Supplying only `from` gives `[from, now]`. An unparseable value, or a window that ends before it starts, returns 400. |
| to | string (ISO-8601 or YYYY-MM-DD) | Explicit window end, overriding `range`. A date-only `YYYY-MM-DD` value covers the WHOLE day, through `T23:59:59.999Z` — the final day is included, and `from=X&to=X` is a full 24 hours rather than the zero-width window it used to be. A value carrying a time is used verbatim. An end in the future is clamped to now, except where clamping would invert the window. Supplying only `to` gives `[to - range, to]`. An unparseable value, or a window that ends before it starts, returns 400. |
| includeConversions | boolean | Set to `false` or `0` to skip the conversion aggregation (much faster). Any other value, and omitting it, counts as true. |
Example request
curl -H "Authorization: Bearer $ANALYTICS_API_KEY" \ "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/goals/6512aa01bcf86cd7994390a1"
Example response
{
"goalId": "6512aa01bcf86cd7994390a1",
"siteId": "6507f1f77bcf86cd799439011",
"name": "Signup completed",
"type": "pageview",
"target": "/welcome",
"conversions": 213,
"createdAt": "2025-02-01T08:00:00.000Z",
"updatedAt": "2025-02-01T08:00:00.000Z",
"range": "30d",
"since": "2025-02-19T00:00:00.000Z",
"until": "2025-03-21T00:00:00.000Z"
}Update a goal
/api/v2/sites/{siteId}/goals/{goalId}scope: writeAt least one field is required. Mongoose validators run on update; the updated goal is returned.
- Viewers receive 403.
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. |
| goalId | string | Identifier of the goal. |
Request body
Every field is optional, but at least one must be supplied.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | No | New display name. |
| type | 'pageview' | 'event' | No | New goal type. |
| target | string | No | New pathname or event name. |
Example request
curl -X PATCH "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/goals/6512aa01bcf86cd7994390a1" \
-H "Authorization: Bearer $ANALYTICS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "target": "/thank-you" }'Example response
{
"goalId": "6512aa01bcf86cd7994390a1",
"siteId": "6507f1f77bcf86cd799439011",
"name": "Signup completed",
"type": "pageview",
"target": "/thank-you",
"createdAt": "2025-02-01T08:00:00.000Z",
"updatedAt": "2025-03-21T09:20:00.000Z"
}Delete a goal
/api/v2/sites/{siteId}/goals/{goalId}scope: adminDestructiveDeleting a goal destroys the definition and every conversion report built on it. Pass `confirm=<goalId>` as a query parameter or as a `confirm` field in the JSON body — it must equal the 24-character goal id from the URL, not the goal name.
Irreversible. Requires the admin scope and confirm={goalId} — see Destructive operations.
- Viewers receive 403; an unknown id returns 404.
- The confirmation is checked AFTER the scope and site-access checks but BEFORE the id is validated, so a wrong `confirm` with a non-existent id returns 400, never a 404 — a missing confirmation never reveals whether the goal exists.
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. |
| goalId | string | Identifier of the goal. |
Query parameters
| Name | Type | Description |
|---|---|---|
| confirm | string | Destructive-operation confirmation. Must exactly equal the `goalId` in the URL. May be sent as a `confirm` field in the JSON body instead. |
Request body
Optional — send `{ "confirm": "<goalId>" }` instead of the query parameter if you prefer.
| Field | Type | Required | Description |
|---|---|---|---|
| confirm | string | No | Must equal the goalId in the URL. |
Example request
curl -X DELETE "https://analytics.appfor.you/api/v2/sites/6507f1f77bcf86cd799439011/goals/6512aa01bcf86cd7994390a1?confirm=6512aa01bcf86cd7994390a1" \ -H "Authorization: Bearer $ANALYTICS_API_KEY"
Example response
{
"deleted": true,
"goalId": "6512aa01bcf86cd7994390a1"
}