---
title: "Update an annotation"
method: POST
path: "/api/annotations.update"
tags: ["Web Analytics"]
---

# Update an annotation

`POST /api/annotations.update`

Edits an annotation's moment, timezone and presentation. Requires the web_analytics write permission.

Automatic annotations are editable — an operator may want to reword a broadcast's title — but their origin is not: `source` and `source_id` are reloaded from storage and carried forward, so an edit can neither promote a manual row to a system one nor take over another broadcast's slot.

## Request body

- UpdateAnnotationRequest
  - `workspace_id` string, required
  - `id` string, required
  - `annotated_at` string, date-time, required
  - `timezone` string — Falls back to the annotation's stored timezone when omitted.
  - `title` string, required
  - `description` string
  - `color` string — Falls back to the annotation's stored colour when omitted.

## Response `200`

Annotation updated

- AnnotationResponse
  - `annotation` Annotation
    - `id` string, required — Server-generated identifier (a UUID with its dashes removed).
    - `annotated_at` string, date-time, required — The moment the annotation marks, as a real instant.
    - `timezone` string, required — IANA timezone the moment was entered in. This is display intent only — annotated_at already fixes the instant, and filtering never uses this field. It is what lets "9am in Tokyo" render back as 9am instead of the reader's local equivalent.
    - `title` string, required — Counted in characters, not bytes.
    - `description` string — Optional longer note. Counted in characters, not bytes.
    - `color` string, required — Six-digit hex colour used for the marker on the charts.
    - `source` 'manual' | 'broadcast', required — Who wrote the row. `manual` is anything typed by an operator or posted to this API; `broadcast` rows are written automatically when a broadcast starts sending. There is deliberately no API source — see the note on annotations.create.
    - `source_id` string — The entity that caused an automatic annotation (a broadcast id today). Always absent on manual rows, and never settable through the API. An annotation outlives the entity it points at: deleting a broadcast leaves its annotation, because the send did happen.
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required

## Other responses

- `400` — Bad request - unparseable body, validation failed, or the instance runs in demo mode.
- `401` — Unauthorized - invalid or missing authentication token
- `403` — Forbidden - missing required permission
- `404` — No annotation with that ID in this workspace
- `500` — Internal server error

## Changes

- **2026-08-17** `d26605f2fc47` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/pierre-b/apis/notifuse-api/changes/api/annotations.update/post.md)

---

[API](https://skmtc.dev/pierre-b/apis/notifuse-api.md) · [All operations](https://skmtc.dev/pierre-b/apis/notifuse-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/pierre-b/notifuse-api/revisions/d26605f2fc47/schema)
