> ## Documentation Index
> Fetch the complete documentation index at: https://docs.traceten.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sites API

> Create, read, update and delete the properties you track.

## Base URL and authentication

```
https://api.traceten.com/v1/sites
```

Every endpoint takes `Authorization: Bearer <YOUR_API_KEY>`. Create a key under **Settings → API keys** ([details](/api/authentication)). Reads need `stats:read`; changes need `config:write`, and creating and deleting need more than that. Each section below says which.

A site your key does not cover returns `404`, not `403`, so a key cannot be used to discover which sites exist.

## How sites are identified

A site has two identifiers, and which one you see depends on the credential you used.

On this page, and on [events](/api/events), [sources](/api/sources), [sessions](/api/sessions), [revenue](/api/revenue), [goals](/api/goals) and [funnels](/api/funnels), an API key only ever sees the **snippet key**: `ttid_` followed by 22 characters, the value your install snippet carries as `data-site`. It is what `id` holds in every response below, and it is what you pass back in any later call to those endpoints.

<Note>
  A few endpoints take the site's internal UUID instead of the snippet key: the [data deletion and
  access endpoints](/privacy/data-deletion), plus `POST /v1/ingest/conversions` and `POST
      /v1/consortium/fingerprints`. No API endpoint returns that UUID, so read it from the dashboard URL
  once and keep it. See [which site identifier those endpoints
  take](/privacy/data-deletion#which-site-identifier-these-endpoints-take).
</Note>

The dashboard uses these same endpoints with its own session and sees an internal UUID instead. If you are reading this page, you want the snippet key.

`snippet_key` is also returned as its own field, and it always holds that same `ttid_` value.

## `GET /v1/sites`

List the properties on your account, newest first.

```bash theme={null}
curl https://api.traceten.com/v1/sites \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

```json theme={null}
{
  "sites": [
    {
      "id": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
      "name": "Storefront",
      "domain": "example.com",
      "snippet_key": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
      "region": "us",
      "attribution_model": "last_touch",
      "cookie_apex_domain": "example.com",
      "suggested_cookie_apex_domain": "example.com",
      "currency": "USD",
      "created_at": "2026-08-01T09:12:44.120Z",
      "verified_at": "2026-08-01T09:18:02.004Z",
      "deleted_at": null,
      "deletion_effective_at": null
    }
  ]
}
```

If your key is scoped to a single site, this returns that one site rather than the whole account.

### Query parameters

| Parameter         | Type   | Required | Notes                                                              |
| ----------------- | ------ | -------- | ------------------------------------------------------------------ |
| `include_deleted` | string | no       | `true` or `false`. Adds sites inside their 30 day deletion window. |

Sites you have scheduled for deletion are excluded by default. Pass `include_deleted=true` to see them; each one carries a non-null `deleted_at` and `deletion_effective_at`.

### Response fields

| Field                          | Type               | Notes                                                                                                                                                    |
| ------------------------------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                           | string             | The snippet key. Pass this as `site_id` to the other endpoints.                                                                                          |
| `name`                         | string \| `null`   | Display label. `null` means the dashboard shows the domain.                                                                                              |
| `domain`                       | string             | The hostname you registered.                                                                                                                             |
| `snippet_key`                  | string             | The `ttid_` value your install snippet carries as `data-site`.                                                                                           |
| `region`                       | string             | `us` or `eu`. A recorded preference, not yet where data is stored. See below.                                                                            |
| `attribution_model`            | string             | `first_touch`, `last_touch`, `linear` or `time_decay`.                                                                                                   |
| `cookie_apex_domain`           | string \| `null`   | The domain identity cookies are scoped to, rendered as `data-cookie-domain`. `null` until you confirm one via `PATCH`; nothing is applied automatically. |
| `suggested_cookie_apex_domain` | string \| `null`   | The registrable domain `domain` derives to, recomputed on every read. Not applied: `PATCH` `cookie_apex_domain` with this value to confirm it.           |
| `currency`                     | string             | ISO 4217 code the dashboard reports revenue in. `USD` unless you changed it.                                                                             |
| `created_at`                   | ISO-8601           |                                                                                                                                                          |
| `verified_at`                  | ISO-8601 \| `null` | When we first received an event. `null` means the snippet has never reported in.                                                                         |
| `deleted_at`                   | ISO-8601 \| `null` | Non-null means deletion is scheduled. Always present.                                                                                                    |
| `deletion_effective_at`        | ISO-8601 \| `null` | When the erasure runs.                                                                                                                                   |

## `GET /v1/sites/{siteId}`

Fetch one site. `siteId` is the snippet key.

```bash theme={null}
curl https://api.traceten.com/v1/sites/ttid_7Rb4TrC1dTbnD8w3s1TS12 \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

Returns a single site object with the same fields as the list rows above (not wrapped in a `sites` array).

A site inside its deletion window returns `404` here. Use `GET /v1/sites?include_deleted=true` if you need to see it.

Note that a site in its deletion window keeps collecting nothing new but keeps its data: ingestion is suspended when you schedule the deletion, while the read endpoints keep serving what is already there so you can export it during the 30 days. That is why this endpoint 404s a site the [sources](/api/sources) and [events](/api/events) endpoints still answer for.

### About `region`

`region` is the region you chose for the site, and we return it so you can read back what is on file. It does not yet control where data is stored: all event data currently lands in one region whichever value you pick. If you have GDPR obligations that require EU storage, read [Data residency](/privacy/gdpr#data-residency) before relying on this field.

| Status | Cause                                                                     |
| ------ | ------------------------------------------------------------------------- |
| `401`  | Missing, malformed, revoked or expired key.                               |
| `403`  | The key is valid but lacks `stats:read`. The body names the permission.   |
| `404`  | No such site, the key does not cover it, or it is scheduled for deletion. |
| `422`  | `siteId` is neither a snippet key nor a UUID.                             |
| `429`  | Rate limited. Back off and retry.                                         |

## `POST /v1/sites`

Create a property. The response includes an API key for it, shown once.

```bash theme={null}
curl -X POST https://api.traceten.com/v1/sites \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "shop.example.com", "name": "Storefront" }'
```

```json theme={null}
{
  "site_id": "ttid_9kM2pXqW4vNbT7yH1cLd83",
  "snippet_key": "ttid_9kM2pXqW4vNbT7yH1cLd83",
  "domain": "shop.example.com",
  "name": "Storefront",
  "region": "us",
  "cookie_apex_domain": null,
  "suggested_cookie_apex_domain": "example.com",
  "api_key": "tk_live_example2_not_a_real_key_do_not_use",
  "created_at": "2026-08-29T12:14:09.220Z"
}
```

| Field    | Type             | Required | Notes                                                       |
| -------- | ---------------- | -------- | ----------------------------------------------------------- |
| `domain` | string           | yes      | Hostname only. No protocol, no trailing slash.              |
| `name`   | string \| null   | no       | Display label, up to 60 characters. Omit to use the domain. |
| `region` | `"us"` \| `"eu"` | no       | Defaults to your account's region, or `us`.                 |

<Warning>
  `api_key` is the plaintext key for the new site, shown once and never recoverable. It carries
  `stats:read` and `ingest:write`, which is what the install instructions point the server SDKs at.
</Warning>

`cookie_apex_domain` comes back `null` here, because nothing is applied automatically. `suggested_cookie_apex_domain` carries what `domain` derives to; render it as a pre-filled, unconfirmed suggestion, and `PATCH /v1/sites/{siteId}` with it to actually scope the snippet's identity cookies across subdomains as `data-cookie-domain`. Broadening cookies to an apex is a real security tradeoff, not a free upgrade: two sites that happen to resolve to the same registrable domain (including two different accounts, on a platform domain not on the Public Suffix List) can otherwise collide on one visitor identity, which is why this is confirm-not-derive. `suggested_cookie_apex_domain` is `null` when the domain has no registrable apex (`localhost`, an IP address). See [Subdomains and cross-domain](/install/subdomains).

**This endpoint needs three permissions: `credentials:write`, `stats:read` and `ingest:write`.** It hands you a credential carrying the last two, and a key can never mint a key more capable than itself. If yours is missing one, you get `403` and `required_scope` names it. See [minting credentials with a key](/api/authentication#minting-credentials-with-a-key).

It also requires that the person who created your key is still an account-wide admin, and it refuses a key scoped to a single site. See [who is behind a key](/api/authentication#who-is-behind-a-key).

| Status | `code`                     | Cause                                                           |
| ------ | -------------------------- | --------------------------------------------------------------- |
| `403`  | `site_limit_reached`       | Your plan's site cap. The body carries `plan` and `site_limit`. |
| `409`  | `domain_taken`             | You have already added this domain. Open that site instead.     |
| `409`  | `account_pending_deletion` | The account is scheduled for deletion.                          |

A domain another account tracks is not a conflict. Two accounts pointing at the same hostname collect entirely separate data.

## `PATCH /v1/sites/{siteId}`

Change a site's settings. Send only the fields you are changing. Needs `config:write`.

```bash theme={null}
curl -X PATCH https://api.traceten.com/v1/sites/ttid_7Rb4TrC1dTbnD8w3s1TS12 \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "attribution_model": "linear", "currency": "EUR" }'
```

Returns the full updated site, in the same shape as `GET /v1/sites/{siteId}`.

| Field                | Type           | Notes                                                                                                |
| -------------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
| `name`               | string \| null | `null` or `""` clears it back to the domain.                                                         |
| `attribution_model`  | string         | `first_touch`, `last_touch`, `linear` or `time_decay`.                                               |
| `cookie_apex_domain` | string \| null | Must be the site's own domain or a parent of it. `null` or `""` clears it back to host-only cookies. |
| `currency`           | string         | ISO 4217. Must be one we hold a rate for. See [currencies](/integrations/currencies).                |

At least one field is required.

Setting `currency` explicitly also stops a later Stripe or Shopify connection from overriding it, including when you set it to `USD`.

Unlike creating and deleting, this is **not** admin-only. Any member can change settings.

A site inside its deletion window returns `404`. Restore it first.

## `DELETE /v1/sites/{siteId}`

Schedule a site for deletion, 30 days out. Needs `config:write` and an account-wide admin behind the key.

```bash theme={null}
curl -X DELETE https://api.traceten.com/v1/sites/ttid_7Rb4TrC1dTbnD8w3s1TS12 \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "confirm_domain": "example.com" }'
```

```json theme={null}
{
  "status": "deletion_scheduled",
  "site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
  "domain": "example.com",
  "requested_at": "2026-08-29T12:20:00.000Z",
  "effective_at": "2026-09-28T12:20:00.000Z",
  "window_days": 30
}
```

**Nothing is destroyed yet.** For 30 days the site, its data and its snippet key all survive, and `POST /v1/sites/{siteId}/restore` cancels the whole thing. After `effective_at` the erasure runs and is not reversible.

What happens straight away:

* Collection stops. The snippet is still on your pages, but we no longer accept its events.
* The site disappears from `GET /v1/sites` unless you pass `include_deleted=true`.
* It stops counting against your plan's site cap.

`confirm_domain` must match the site's own domain, case-insensitively. It is the domain rather than a fixed phrase because on a multi-site account the mistake that actually happens is deleting the wrong property, and a constant phrase cannot catch that.

Re-issuing on a site that is already scheduled reports the existing `effective_at` and does not push the deadline out.

| Status | `error`             | Cause                           |
| ------ | ------------------- | ------------------------------- |
| `403`  | `forbidden`         | Not an account-wide admin.      |
| `409`  | `domain_mismatch`   | `confirm_domain` did not match. |
| `422`  | `validation_failed` | `confirm_domain` missing.       |

## `POST /v1/sites/{siteId}/restore`

Cancel a scheduled deletion and resume collection. Needs `config:write` and an account-wide admin.

```bash theme={null}
curl -X POST https://api.traceten.com/v1/sites/ttid_7Rb4TrC1dTbnD8w3s1TS12/restore \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

```json theme={null}
{
  "status": "active",
  "site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
  "domain": "example.com"
}
```

Calling this on a site that is not scheduled is a `200` no-op.

| Status | `code`                     | Cause                                                                                                    |
| ------ | -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `409`  | `deletion_in_progress`     | The 30 days elapsed and erasure has begun. Not recoverable through the API. Contact support immediately. |
| `409`  | `site_limit_reached`       | Restoring would put you back over your plan's cap. Delete another site or upgrade first.                 |
| `409`  | `domain_taken`             | You added this domain again as a new site while this one sat in its window.                              |
| `409`  | `account_pending_deletion` | The account itself is scheduled for deletion.                                                            |

## Next

* [Recent events](/api/events) to confirm a site is receiving traffic
* [Traffic sources](/api/sources) for the numbers behind the dashboard
