Annotations

The Annotation object, and the endpoints to create, list, retrieve, update and delete annotations.

Open in Claude

An annotation records a deploy, a change or a comment next to your telemetry. To mark deploys from CI, start from the Annotations guide.

Anyone in the organization may read annotations. Those on a restricted dataset are visible only to people who can read that dataset. Each endpoint says who may write.

The Annotation object

Badges say what a create request needs: required, optional, or server when Telemetry Machine sets it.

Attributesapplication/json
public_idstringserver

ann_…

typeenumrequired

Why the data might look different (refer to Types).

One of deploy, change, comment

messagestringrequired

What happened, such as checkout 1.42.0.

Rules: up to 4,096 characters.

started_attimestampoptional

When it happened, or when it started, such as 2026-10-23T14:02:00Z. Defaults to now. time is its older name, still accepted.

Rules: RFC 3339 with a zone.

ended_attimestamp, nullableoptional

When it ended, for an annotation that covers a window, such as a load test. Null for a moment (refer to Moments and windows).

Rules: RFC 3339 with a zone, not before started_at.

keystring, nullableoptional

Your own id for it, unique in the organization. It makes a create safe to retry (refer to Keys).

Rules: up to 255 characters, not blank.

datasetstring, nullableoptional

The dataset it concerns, by name or public id. Null for the whole organization.

Rules: up to 255 characters; a dataset that you can read.

attributesmapoptional

What it concerns, with OpenTelemetry names, such as service.name. A deploy's are checked (refer to Deploy attributes).

Rules: up to 64 names of 1 to 255 characters. Each value is a string of up to 1,024 characters, a number or a boolean.

created_byobject, nullableserver

The person who posted it.

created_by_api_keyobject, nullableserver

The ingest key that posted it.

created_by_ciobject, nullableserver

The CI job that posted it with its OIDC token (refer to CI trust rules).

Show 7 child attributesHide child attributes
created_by_ci.providerenum

github or gitlab.

created_by_ci.repository, repository_id string

The repository, such as acme/checkout, and the provider's id for it.

created_by_ci.refstring

The job's ref, such as refs/heads/main.

created_by_ci.environmentstring, nullable

The job's deployment environment, such as production.

created_by_ci.workflowstring, nullable

The workflow file, such as acme/checkout/.github/workflows/publish.yml@refs/heads/main.

created_by_ci.run_id, run_url string

The run, and a link to its log.

created_by_ci.rulestring

The trust rule that let the job in, ctr_….

created_attimestampserver

When it was posted.

updated_attimestampserver

When it last changed, such as when it ended.

200The Annotation object
{
  "public_id": "ann_01m5x9q2r7t4k8w3e6y1p0z5ab",
  "type": "deploy",
  "message": "checkout 1.42.0",
  "started_at": "2026-10-26T13:50:04.000Z",
  "ended_at": null,
  "key": "[email protected]@production",
  "dataset": null,
  "attributes": {
    "service.name": "checkout",
    "service.version": "1.42.0",
    "deployment.environment.name": "production",
    "vcs.ref.head.revision": "0b7c0a8e5d1f4c2b9a6e3d7f1c8b4a2e9d5f6c31",
    "url.full": "https://github.com/acme/checkout/actions/runs/123"
  },
  "created_by": null,
  "created_by_api_key": { "public_id": "apk_01m5…", "name": "github actions" },
  "created_by_ci": null,
  "created_at": "2026-10-26T13:50:05.000Z",
  "updated_at": "2026-10-26T13:50:05.000Z"
}

Responses carry it in the API's envelope, as data (refer to Responses).

Create an annotation

POST/api/organizations/{org}/annotations
Auth
A token with annotations:write, an ingest key with annotate on, or a CI job's OIDC token that a trust rule lets in

Takes the fields of the Annotation object that are marked required or optional. With a key that an annotation already has, it returns that annotation instead of adding one (refer to Keys).

POST /annotations
curl -X POST https://telemetrymachine.com/api/organizations/<your-org>/annotations \
  -H "X-API-Key: $TM_INGEST_KEY" -H 'Content-Type: application/json' \
  -d '{
    "type": "deploy",
    "message": "checkout 1.42.0",
    "key": "[email protected]@production",
    "attributes": {
      "service.name": "checkout",
      "service.version": "1.42.0",
      "deployment.environment.name": "production",
      "vcs.ref.head.revision": "0b7c0a8e5d1f4c2b9a6e3d7f1c8b4a2e9d5f6c31"
    }
  }'
200Response
{
  "status": 200,
  "data": {
    "public_id": "ann_01m5x9q2r7t4k8w3e6y1p0z5ab",
    "type": "deploy",
    "message": "checkout 1.42.0",
    "started_at": "2026-10-26T13:50:04.000Z",
    "ended_at": null,
    "key": "[email protected]@production"
  }
}

The response is the whole annotation; this one is cut short.

List annotations

GET/api/organizations/{org}/annotations
Auth
Any member, with a token or from the console
Returns
annotations, newest first, and total, limit and offset for paging
Query parameters
fromtimestampoptional

Only annotations at or after it. A window that began earlier and ends after it, such as a load test, is included.

totimestampoptional

Only annotations that start at or before it.

typeenumoptional

Only annotations of this type.

datasetstringoptional

Only annotations about this dataset, and those for the whole organization.

limitintegeroptional

How many to return.

Rules: 1 to 1,000; defaults to 100.

offsetintegeroptional

How many to skip, for paging; defaults to 0.

Retrieve an annotation

GET/api/organizations/{org}/annotations/{public_id}
Auth
Any member who can read its dataset

Update an annotation

PATCH/api/organizations/{org}/annotations/{public_id}
Auth
A token with annotations:write, or the ingest key that created it. A CI job's token can't update.

Updates the fields that you send, and leaves the rest. Setting ended_at turns a moment into a window, such as a load test that you mark when it finishes.

Body parameters
ended_attimestamp, nullableoptional

When it ended. Null makes it a moment again.

messagestringoptional

What happened.

started_attimestampoptional

When it happened, or when it started.

datasetstring, nullableoptional

A dataset, or null to make it cover the whole organization.

type, attributesoptional

As on the Annotation object. attributes replaces them all.

PATCH /annotations/{public_id}
curl -X PATCH https://telemetrymachine.com/api/organizations/<your-org>/annotations/ann_01m5y2k8r4t6w9e3q7p1z0x5cd \
  -H "Authorization: Bearer $TM_TOKEN" -H 'Content-Type: application/json' \
  -d '{ "ended_at": "2026-10-26T15:00:00Z" }'
200Response
{
  "status": 200,
  "data": {
    "public_id": "ann_01m5y2k8r4t6w9e3q7p1z0x5cd",
    "type": "change",
    "message": "load test of checkout",
    "started_at": "2026-10-26T14:00:00.000Z",
    "ended_at": "2026-10-26T15:00:00.000Z"
  }
}

Delete an annotation

DELETE/api/organizations/{org}/annotations/{public_id}
Auth
A token with annotations:write. Ingest keys and CI jobs can't delete.
Returns
{ "deleted": true }

Moments and windows

An annotation is a moment at started_at. With ended_at, it covers a window, such as a load test or a comment on a stretch of a chart. Send ended_at with the annotation, or set it later with Update. Nothing ends an annotation by itself.

Keys

A key is your own id for an annotation, unique in the organization. It makes a create safe to retry, so CI can send the same call again after a timeout.

  • Posting a key again returns the existing annotation when every field that you send matches it: a retry. Fields that you leave out aren't compared, so a retry needn't send its time again.
  • Posting a key again with a field that differs is refused with 409 Conflict, so two different things never share a key silently.
  • A retry is answered even when the organization is at its daily limit.
  • A key that another poster holds is a conflict for a CI job: a job retries only the keys that its own repository posted.

A key names everything that makes it unique. Something from another system is <source>:<id>, such as launchdarkly:new-checkout:42 for a flag's 42nd version. A deploy is its service, version and environment, such as [email protected]@production, because the same version often goes to staging and then to production.

Deploy attributes

A deploy must name a commit or a service. These checks run when it is created, and when an update changes its type or attributes. Other names aren't checked, so you can send your own.

Deploy attributes
service.namestring

The service that it deploys.

Rules: required unless the deploy names a commit; not blank.

service.versionstring

The version that it ships, as the service reports it.

deployment.environment.namestring

The environment, such as production.

Rules: not blank.

vcs.ref.head.revisionstring

The commit that it ships.

Rules: 7 to 40 hex characters, such as $GITHUB_SHA; required unless the deploy names a service.name.

url.fullstring

A link to the CI run.

Limits

An organization can post 1,000 annotations in 24 hours. More get 429: send events at that volume to an events dataset instead. A retry of a key is still answered.

Errors

StatusWhenWhat to do
400type is not one of the typesUse one of the types that the error names.
400attributes breaks a limit: more than 64 names, a name over 255 characters, or a value that isn't a string, a number or a booleanFix the attribute that the error names.
400A deploy names neither vcs.ref.head.revision nor service.name, or its commit isn't 7 to 40 hex charactersSend the commit as CI has it, such as $GITHUB_SHA.
400ended_at is before started_at, or started_at and time differSend one start, and an end after it.
400dataset names no dataset that you can readUse the dataset's name or its public id.
401A CI job's token is invalid: its signature, issuer or audience is wrong, or it expiredAsk the provider for a new token, addressed to telemetrymachine.com.
403The token lacks annotations:write, or the ingest key doesn't have annotate onUse a token with the scope, or an ingest key with annotate on.
403No trust rule lets the CI job in, or a CI job's token tried to updateAdd a trust rule for the repository, or check the ref and environment that it names.
404The organization has no annotation with this public_id, or you can't read its datasetCheck the id.
409The key exists with a different bodyThe key names something else. Add what makes it unique, such as the environment.
429The organization posted 1,000 annotations in the last 24 hoursSend events at this volume to an events dataset instead.