> ## 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.

# Chart notes API

> Read, add, change and delete the team notes pinned to days on a site's traffic chart.

Chart notes are short notes your team pins to a day on the Overview traffic chart, such as "Launched pricing page" or "Newsletter went out". This API reads and writes the same notes the dashboard shows.

## What to know first

* **One set of notes per site, shared with your whole team.** Everyone in your organization who can see a site sees its notes, and admins and members can add, change and delete them. There are no private notes.
* **Dates are UTC calendar days, inside the analytics retention window.** A note belongs to a day, `YYYY-MM-DD`, from 729 days before today up to today: 730 days, today included. There is no per-site timezone and no future date. Once a note's day falls out of that window, our hourly deletion job deletes it within a day. On weekly or hourly charts, a note appears on the week or hour that contains its day.
* **Every note records how it was written.** `created_via` and `updated_via` are `dashboard`, `api_key` or `oauth` (an AI assistant you connected). A note written with an API key is attributed to the team member who created that key.
* **API keys and AI assistants never see member ids.** `created_by` and `updated_by` (user ids from our authentication provider) are returned only to the dashboard, which turns them into names. Traceten stores no names or email addresses with a note.
* **Note text is typed by people.** If you feed notes to an AI model, treat the text as data, not instructions.
* **A site holds at most 2,000 notes.**
* **Notes are deleted with their site.** Deleting a site or closing your account deletes its notes when the erasure runs, 30 days later.
* **Do not put personal data in a note.** Everyone on your team, every API key that can read the site, and any AI assistant you connect can read it.

## Base URL and authentication

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

Every endpoint takes `Authorization: Bearer <YOUR_API_KEY>` ([details](/api/authentication)) and a `site_id`. With an API key, `site_id` is the site's **snippet key**, the `ttid_…` value your install snippet carries as `data-site`.

| Endpoint                     | Permission     |
| ---------------------------- | -------------- |
| `GET /v1/annotations`        | `stats:read`   |
| `GET /v1/annotations/:id`    | `stats:read`   |
| `POST /v1/annotations`       | `config:write` |
| `PATCH /v1/annotations/:id`  | `config:write` |
| `DELETE /v1/annotations/:id` | `config:write` |

Responses use the standard envelope: `{ "status": "success", "data": … }` on success and `{ "status": "error", "error", "message", "field", "statusCode" }` on failure. See [Errors](/api/errors).

## The note object

What an API key receives:

```json theme={null}
{
  "id": "3f1c8a2e-5b7d-4e19-9a61-2c8f0d4b7e53",
  "site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
  "date": "2026-08-10",
  "note": "Launched the new pricing page",
  "created_via": "dashboard",
  "updated_via": "api_key",
  "created_at": "2026-08-10T09:12:44.120Z",
  "updated_at": "2026-08-12T16:03:10.502Z"
}
```

| Field         | Type   | Notes                                                                                                                   |
| ------------- | ------ | ----------------------------------------------------------------------------------------------------------------------- |
| `id`          | string | UUID.                                                                                                                   |
| `site_id`     | string | The site, in the same form you sent it: the snippet key with an API key.                                                |
| `date`        | string | UTC calendar day, `YYYY-MM-DD`.                                                                                         |
| `note`        | string | 1 to 500 characters, with at least one visible character. Leading and trailing whitespace is removed. Line breaks kept. |
| `created_via` | string | `dashboard`, `api_key` or `oauth`.                                                                                      |
| `updated_via` | string | How the last change was made. Equal to `created_via` until the note is changed.                                         |
| `created_at`  | string | ISO 8601 timestamp.                                                                                                     |
| `updated_at`  | string | ISO 8601 timestamp.                                                                                                     |
| `created_by`  | string | Dashboard sessions only. User id of the member who added it, or `deleted-user` once their user is deleted.              |
| `updated_by`  | string | Dashboard sessions only. User id of the member who last changed it.                                                     |

Characters are counted as Unicode characters, so an emoji counts as one. Notes containing control characters (other than tab and line breaks) or text-direction overrides are refused with `422`.

## `GET /v1/annotations`

List a site's notes for a date window, oldest day first. Several notes can share a day; those come back in the order they were added.

| Parameter | Type    | Required | Notes                                                                    |
| --------- | ------- | -------- | ------------------------------------------------------------------------ |
| `site_id` | string  | yes      |                                                                          |
| `from`    | string  | yes      | First UTC day, inclusive.                                                |
| `to`      | string  | yes      | Last UTC day, inclusive. The window may cover at most 730 days.          |
| `limit`   | integer | no       | 1 to 500. Defaults to 500.                                               |
| `offset`  | integer | no       | Notes to skip. Defaults to 0. Use it with `pagination.has_more` to page. |

```bash theme={null}
curl "https://api.traceten.com/v1/annotations?site_id=ttid_7Rb4TrC1dTbnD8w3s1TS12&from=2026-08-01&to=2026-08-31" \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

```json theme={null}
{
  "status": "success",
  "data": {
    "site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
    "from": "2026-08-01",
    "to": "2026-08-31",
    "annotations": [
      {
        "id": "3f1c8a2e-5b7d-4e19-9a61-2c8f0d4b7e53",
        "site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
        "date": "2026-08-10",
        "note": "Launched the new pricing page",
        "created_via": "dashboard",
        "updated_via": "dashboard",
        "created_at": "2026-08-10T09:12:44.120Z",
        "updated_at": "2026-08-10T09:12:44.120Z"
      }
    ]
  },
  "pagination": { "limit": 500, "offset": 0, "has_more": false }
}
```

```ts theme={null}
const res = await fetch(
  "https://api.traceten.com/v1/annotations?site_id=ttid_7Rb4TrC1dTbnD8w3s1TS12&from=2026-08-01&to=2026-08-31",
  { headers: { Authorization: `Bearer ${process.env.TRACETEN_API_KEY}` } },
);
const { data } = await res.json();
console.log(data.annotations.length);
```

## `GET /v1/annotations/:id`

One note. `site_id` goes in the query string. A note on another site is `404`.

```bash theme={null}
curl "https://api.traceten.com/v1/annotations/3f1c8a2e-5b7d-4e19-9a61-2c8f0d4b7e53?site_id=ttid_7Rb4TrC1dTbnD8w3s1TS12" \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

`200` returns `{ "status": "success", "data": { "annotation": { … } } }`.

## `POST /v1/annotations`

Add a note.

| Field     | Type   | Required | Notes                                                                           |
| --------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `site_id` | string | yes      |                                                                                 |
| `date`    | string | yes      | UTC calendar day, from 729 days before today up to today (730 days, today too). |
| `note`    | string | yes      | 1 to 500 characters, with at least one visible character.                       |

```bash theme={null}
curl -X POST https://api.traceten.com/v1/annotations \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12", "date": "2026-08-10", "note": "Launched the new pricing page"}'
```

```python theme={null}
import os, requests

res = requests.post(
    "https://api.traceten.com/v1/annotations",
    headers={"Authorization": f"Bearer {os.environ['TRACETEN_API_KEY']}"},
    json={"site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12", "date": "2026-08-10", "note": "Launched the new pricing page"},
)
print(res.json()["data"]["annotation"]["id"])
```

`201` returns `{ "status": "success", "data": { "annotation": { … } } }`.

## `PATCH /v1/annotations/:id`

Change a note's text, its day, or both. Send `site_id` and at least one of `date` or `note`; each follows the same rules as creating a note. `updated_via` records how the change was made. The note's creator never changes.

```bash theme={null}
curl -X PATCH https://api.traceten.com/v1/annotations/3f1c8a2e-5b7d-4e19-9a61-2c8f0d4b7e53 \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12", "note": "Launched pricing page v2"}'
```

`200` returns `{ "status": "success", "data": { "annotation": { … } } }`.

## `DELETE /v1/annotations/:id`

Delete a note permanently. There is no undo. `site_id` goes in the query string.

```bash theme={null}
curl -X DELETE "https://api.traceten.com/v1/annotations/3f1c8a2e-5b7d-4e19-9a61-2c8f0d4b7e53?site_id=ttid_7Rb4TrC1dTbnD8w3s1TS12" \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

`204` with no body.

## Errors

| Status | `error`                    | Cause and fix                                                                                                                                                                                                                                        |
| ------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `unauthorized`             | Missing, invalid or revoked credential. A key is revoked when the member who created it leaves your organization.                                                                                                                                    |
| `403`  | `insufficient_scope`       | The key lacks `config:write` for a write, or `stats:read` for a read. Mint a key with the permission.                                                                                                                                                |
| `403`  | `forbidden`                | Dashboard session only: the site belongs to another account, is outside your site access, or is being deleted.                                                                                                                                       |
| `404`  | `site_not_found`           | With a key: the site does not exist, is not yours, is outside the key's site, or is being deleted.                                                                                                                                                   |
| `404`  | `annotation_not_found`     | No note with that id on this site. A note id from another site is also a `404`.                                                                                                                                                                      |
| `409`  | `annotation_limit_reached` | The site already has 2,000 notes. Delete some first.                                                                                                                                                                                                 |
| `422`  | `validation_failed`        | A bad field, named by `field`: an unreal date, a date in the future or more than 729 days ago, `from` after `to`, a window over 730 days, a note over 500 characters, an empty note, a control or text-direction character, or an unknown parameter. |
| `429`  | `rate_limited`             | Slow down. Writes allow 60 requests a minute per credential.                                                                                                                                                                                         |

## Next

* [Dashboard overview](/dashboard/overview#chart-notes) for adding notes from the chart
* [MCP tools](/mcp/tools) to manage notes from an AI assistant
