Base URL and authentication
Authorization: Bearer <YOUR_API_KEY>. Create a key under Settings → API keys (details).
site_id is required on every request. With an API key it is the site’s snippet key, the ttid_… value your install snippet carries as data-site. (The dashboard uses the same endpoints with its own session and passes the site’s internal UUID instead; if you are reading this page, you want the snippet key.)
A site your key does not cover returns 404, not 403, so a key cannot be used to discover which sites exist.
The goal registry
You do not have to create a goal before firing it. The first time an event with a new name is attributed, Traceten registers it automatically, up to 200 distinct names per site. These endpoints exist to pre-declare a goal, to give it a display name and emoji, to order the list, and to archive names you no longer use.name is immutable. It is the event name on the wire, so changing it would orphan every event already recorded under it. Change display_name instead.
GET /v1/goals
List every goal on the site, ordered by sort_order then name. Archived goals are omitted.
POST /v1/goals
Pre-declare a goal, so it appears in the dashboard before it has ever fired.
201 returns { "goal": { … } } with the same shape as the list rows.
PATCH /v1/goals/:id
Update the presentation fields. Omitted fields are left alone.
display_name, emoji, and sort_order. 200 returns { "goal": { … } }; an id belonging to another site is 404.
PATCH /v1/goals/reorder
Rewrite the display order in one call. The goals are given sort_order 0, 1, 2… in the order you list them.
goal_ids takes 1 to 500 ids. Ids belonging to another site are ignored rather than applied. 200 returns the full reordered list as { "goals": [ … ] }.
This endpoint has no CLI command and no MCP tool. It is deliberately absent from the OpenAPI spec,
which both of those are generated from, because it rewrites the dashboard’s display order, a
presentation detail with no meaning to a script or an agent. It stays callable over REST with an
API key exactly as shown above. The data deletion and access endpoints
are outside the spec too, for a different reason.
DELETE /v1/goals/:id
Archive a goal. Returns 204 with no body, or 404 if the id is not on this site.
This is an archive, not a delete. Events with that name keep arriving and keep being recorded, and a hard delete would simply be recreated the next time one did. Archiving takes it out of the dashboard’s lists.
POST /v1/goals/:id/unarchive
Bring an archived goal back. It reappears in the dashboard’s lists and in GET /v1/goals without include_archived.
Note where site_id goes: this endpoint takes it in the JSON body, while DELETE /v1/goals/:id takes it in the query string.
200 returns { "goal": { … } }, the same shape POST /v1/goals returns.
Two failures are specific to this endpoint:
The
409 has no field: it is a per-site limit, not a bad value you can correct in the request.
GET /v1/goals/timeseries
Completions per goal per day, for every goal that fired in the range.
from and to are YYYY-MM-DD. to defaults to today, from to six days before to.
points rather than present as zeros.
GET /v1/goals/:name/properties
The property breakdown for one goal: which keys the site sends with it, and the most common values of each. At most 10 values are returned per key, most common first.
How the budget works, because it can surprise you. The scan reads at most 200 key/value pairs in one pass, ordered by key name and then by count. That budget is shared across all keys, so a single key with 200 or more distinct values (an id, a timestamp, a full URL) consumes the whole thing and no other key appears in the response. When the budget runs out, the keys that survive are the ones earliest in alphabetical order, not the ones sent most often. Keep property values to a small set of repeated labels and this never comes up.
count counts distinct events, so an event re-sent by a retry is counted once.
Conversion records, including these properties, are retained for 365 days, and each goal also writes a visitor-grain step row retained for 730 days. See data collected.
This endpoint returns property keys and values as they were stored. Neither is scanned for personal names or addresses, and keys are not scanned at all. Read what happens to property keys and values before you put anything into one.
GET /v1/goals/:name/visitors
The people behind one goal’s completion count. GET /v1/goals/timeseries tells you how many times a goal was completed; this tells you who completed it.
Rows are the same shape
GET /v1/visitors returns, including the opaque handle you can pass to GET /v1/visitors/{handle} to open one person.
total_count counts people; the timeseries counts completions. Someone who completed the goal three times is one row here and three completions there, so this number is normally smaller than the completion count for the same window. Both are right. Compare it against another count only if you know which of the two that count is.
Identifiers that belong to one signed-in person are merged before the page is cut, so someone who signed in between two completions is one row and one count. Only people with a visit in the window are listed and counted, because a row is built from their visits: a completion whose visitor had no visit in the window (for example a payment recorded server-side) is left out of both the rows and total_count. Every page but the last returns page_size rows, and has_more is derived from total_count.
identity_merge_complete is false when some people on this page were summarised from part of their history, because the links between someone’s signed-in and anonymous visits are capped per page. When it is false, read the per-row visit, pageview and revenue figures as lower bounds. It is true on most pages, and always true on an empty one.
A goal nobody completed in the window returns an empty list, not a 404. Goals register themselves the first time their event fires, so “never seen” and “no completions in this window” are the same observation.
from and to may span at most 365 days, because a goal completion is a conversion record and those are retained for that long. A wider window would report the expired part as nobody having completed it.
Errors
Rate limits are per credential: 120 requests per minute for reads, 60 for writes. A second ceiling of 600 requests per minute applies per IP address across all of these endpoints.
Next
- Funnels API
- Goals: how goals get fired in the first place.

