Annotations are event markers that record when something notable happened -- deployments, incidents, maintenance windows, config changes. They appear as vertical dashed lines on SVG charts and can be queried independently.

What annotations store

Each annotation has:

FieldTypeDescription
idintegerAuto-generated unique ID
timestampintegerUnix timestamp (seconds) when the event occurred
titlestringShort description (e.g., "Deploy v2.3.1")
descriptionstringOptional longer description
tagslistOptional tags for filtering (e.g., ["deploy", "backend"])

Elixir API

Create an annotation

{:ok, id} = TimelessMetrics.annotate(:metrics, System.os_time(:second), "Deploy v2.3.1",
  description: "Rolled out new caching layer",
  tags: ["deploy", "backend"])
# => {:ok, 1}

Query annotations

now = System.os_time(:second)

# All annotations in the last 24 hours
{:ok, annotations} = TimelessMetrics.annotations(:metrics, now - 86_400, now)

# Filter by tags
{:ok, deploys} = TimelessMetrics.annotations(:metrics, now - 86_400, now,
  tags: ["deploy"])

Returns:

{:ok, [
  %{id: 1, timestamp: 1700000000, title: "Deploy v2.3.1",
    description: "Rolled out new caching layer", tags: ["deploy", "backend"]},
  %{id: 2, timestamp: 1700043200, title: "Deploy v2.3.2",
    description: nil, tags: ["deploy"]}
]}

Delete an annotation

:ok = TimelessMetrics.delete_annotation(:metrics, 1)

HTTP API

Create an annotation

curl -X POST http://localhost:8428/api/v1/annotations \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Deploy v2.3.1",
    "description": "Rolled out new caching layer",
    "tags": ["deploy", "backend"],
    "timestamp": 1700000000
  }'
# => {"id": 1, "status": "created"}

If timestamp is omitted, the current time is used.

Query annotations

# All annotations in the last 24 hours
curl 'http://localhost:8428/api/v1/annotations?from=-24h&to=now'

# Filter by tags (comma-separated)
curl 'http://localhost:8428/api/v1/annotations?from=-7d&tags=deploy,incident'

Response:

{
  "data": [
    {
      "id": 1,
      "timestamp": 1700000000,
      "title": "Deploy v2.3.1",
      "description": "Rolled out new caching layer",
      "tags": ["deploy", "backend"]
    }
  ]
}

Delete an annotation

curl -X DELETE http://localhost:8428/api/v1/annotations/1
# => {"status": "deleted"}

Tag-based filtering

Tags let you categorize annotations and query specific types:

# Mark different event types
TimelessMetrics.annotate(:metrics, now, "Deploy v2.3.1", tags: ["deploy"])
TimelessMetrics.annotate(:metrics, now, "High error rate", tags: ["incident", "p1"])
TimelessMetrics.annotate(:metrics, now, "DB maintenance", tags: ["maintenance"])

# Query only incidents
{:ok, incidents} = TimelessMetrics.annotations(:metrics, from, to, tags: ["incident"])

# Query deploys and maintenance
{:ok, events} = TimelessMetrics.annotations(:metrics, from, to, tags: ["deploy", "maintenance"])

When filtering by multiple tags, annotations matching any of the specified tags are returned (OR logic).

Chart overlay

Annotations automatically appear on SVG charts rendered via the /chart endpoint. They display as vertical amber dashed lines with the title text:

<img src="http://localhost:8428/chart?metric=cpu_usage&from=-24h" />

Any annotations within the chart's time range are included automatically. This makes it easy to correlate metric changes with events like deployments.

See Charts & Embedding for more chart options.

Common patterns

Marking deployments from CI

# In your CI/CD pipeline
curl -X POST http://localhost:8428/api/v1/annotations \
  -H "Authorization: Bearer $METRICS_TOKEN" \
  -d "{
    \"title\": \"Deploy ${CI_COMMIT_SHORT_SHA}\",
    \"description\": \"Branch: ${CI_COMMIT_BRANCH}, Pipeline: ${CI_PIPELINE_ID}\",
    \"tags\": [\"deploy\", \"${CI_ENVIRONMENT_NAME}\"]
  }"

Marking incidents

TimelessMetrics.annotate(:metrics, System.os_time(:second), "P1: Payment failures",
  description: "Error rate spike in payment service, investigating",
  tags: ["incident", "p1", "payments"])

Marking maintenance windows

# Start of maintenance
{:ok, start_id} = TimelessMetrics.annotate(:metrics, System.os_time(:second),
  "Maintenance start: DB migration",
  tags: ["maintenance"])

# ... perform maintenance ...

# End of maintenance
TimelessMetrics.annotate(:metrics, System.os_time(:second),
  "Maintenance end: DB migration complete",
  tags: ["maintenance"])