Skip to main content

Goals

Goal properties

The custom properties sent with one goal, and how often each value came.

GET/sites/{siteId}/goals/{goal}/properties
Goal properties
curl --request GET \
  --url 'https://truestat.io/api/v1/sites/{siteId}/goals/{goal}/properties' \
  --header 'Authorization: Bearer <token>'
{
  "data": [
    {
      "goals": 4,
      "value": "usd",
      "property": "currency",
      "visitors": 1
    },
    {
      "goals": 2,
      "value": "2000",
      "property": "mrr_delta_cents",
      "visitors": 1
    }
  ],
  "meta": {
    "page": {
      "limit": 100,
      "offset": 0,
      "has_more": false
    },
    "range": {
      "to": "2026-09-16T00:00:00.000Z",
      "tz": "UTC",
      "from": "2026-08-17T00:00:00.000Z",
      "clamped": false
    },
    "site_id": "6fed8980-a119-4a9c-a4d4-53e448ef9f9e"
  }
}

What was inside your conversions, not just how many there were.

The question this answers

Breakdowns by goal tells you signup fired 412 times. This tells you what was in those 412 — which plan, which form, which variant.

That is usually the number you actually wanted. "412 signups" is a figure; "310 on Starter, 102 on Pro" is a decision.

One row per value

Rows are per property value, not per property. A plan property with three values is three rows, each naming the property it belongs to:

[
  { "property": "plan",     "value": "starter", "visitors": 288, "goals": 310 },
  { "property": "plan",     "value": "pro",     "visitors": 97,  "goals": 102 },
  { "property": "currency", "value": "usd",     "visitors": 385, "goals": 412 }
]

Group by property to get the breakdown per property back. The flat shape means a caller iterating properties writes one loop rather than walking a nested object.

visitors counts distinct people and goals counts times the goal fired, so goals is larger wherever somebody converted more than once.

An unknown goal returns an empty list

Not a 404. Goal names are whatever your site sends — there is no registry to check a name against, so a name nothing has ever sent and a real goal that fired nothing in this window are indistinguishable, and both lead a caller to the same empty state.

Names containing / must be url-encoded, as any path segment must.

Goals without properties return nothing

A goal sent with no properties has no rows here, and that is the common case: truestat("signup") sends a name and nothing else.

Properties come from the second argument — truestat("signup", { plan: "pro" }) — and from server-sent goals. See custom events for how to attach them.

This needs goals:read

Not analytics:read, unlike most read endpoints.

The distinction is deliberate: this reads what your site attached to its conversions, which is more specific than traffic figures. A key issued to a reporting dashboard so it can read visitor counts should not also be able to read the contents of every signup event.

A key without the scope gets 403 insufficient_scope — the same key can still call /overview and the breakdowns.

Authorizations

Authorizationstringheaderrequired

An API key from Settings → API keys. Scopes available: analytics:read, sites:read, sites:write, settings:read, settings:write, goals:read, goals:write, funnels:read, funnels:write, notes:read, notes:write, alerts:read, alerts:write, team:read, team:write, revenue:read, revenue:write.

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Path parameters

siteIdstring (uuid)pathrequired
The site's id, from `GET /sites`. A site that is not yours and a site that does not exist both answer 403, so this cannot be used to discover other people's ids.
goalstringpathrequired
The goal's name, url-encoded.

Query parameters

periodenum<string>query
A named window, resolved in `tz`. Cannot be combined with `from`/`to` — a request carrying both is refused rather than guessed at.
options:todayyesterdaylast24hlast7dlast30dlast12mmonthyearall
fromstring (date-time)query
Start of the window, ISO 8601 with an offset. Must be given together with `to`.
tostring (date-time)query
End of the window. Must be given together with `from`.
tzstringquerydefault: UTC
IANA timezone for day boundaries. `period=today&tz=Europe/Istanbul` means the reader's today, not the server's. Defaults to UTC.
botsenum<string>querydefault: excluded
Whether crawler traffic is counted. Excluded by default — the `/ai/*` endpoints report it separately and always include it.
options:excludedincluded
limitintegerquerydefault: 100
Rows per page, 1 to 1000.
offsetintegerquerydefault: 0
Rows to skip.

Response

application/json

The answer.

dataunknown
See the example.
metaobject