Annotations
The Annotation object, and the endpoints to create, list, retrieve, update and delete annotations.
An annotation records a deploy, a change or a comment next to your telemetry. To mark deploys from CI, start from the Annotations guide.
/api/organizations/{org}/annotations/CreateGET/ListGET/{public_id}RetrievePATCH/{public_id}UpdateDELETE/{public_id}DeleteAnyone 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.
public_idstringserverann_…
messagestringrequiredWhat happened, such as checkout 1.42.0.
Rules: up to 4,096 characters.
started_attimestampoptionalWhen 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, nullableoptionalWhen 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, nullableoptionalYour 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, nullableoptionalThe dataset it concerns, by name or public id. Null for the whole organization.
Rules: up to 255 characters; a dataset that you can read.
attributesmapoptionalWhat 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, nullableserverThe person who posted it.
created_by_api_keyobject, nullableserverThe ingest key that posted it.
created_by_ciobject, nullableserverThe CI job that posted it with its OIDC token (refer to CI trust rules).
Show 7 child attributesHide child attributes
created_by_ci.providerenumgithub or gitlab.
created_by_ci.repository, repository_id stringThe repository, such as acme/checkout, and the provider's id for it.
created_by_ci.refstringThe job's ref, such as refs/heads/main.
created_by_ci.environmentstring, nullableThe job's deployment environment, such as production.
created_by_ci.workflowstring, nullableThe workflow file, such as acme/checkout/.github/workflows/publish.yml@refs/heads/main.
created_by_ci.run_id, run_url stringThe run, and a link to its log.
created_by_ci.rulestringThe trust rule that let the job in, ctr_….
created_attimestampserverWhen it was posted.
updated_attimestampserverWhen it last changed, such as when it ended.
{
"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
/api/organizations/{org}/annotationsTakes 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).
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"
}
}'POST /api/organizations/<your-org>/annotations
X-API-Key: KEY
Content-Type: application/json
{
"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"
}
}{
"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
/api/organizations/{org}/annotationsfromtimestampoptionalOnly annotations at or after it. A window that began earlier and ends after it, such as a load test, is included.
totimestampoptionalOnly annotations that start at or before it.
typeenumoptionalOnly annotations of this type.
datasetstringoptionalOnly annotations about this dataset, and those for the whole organization.
limitintegeroptionalHow many to return.
Rules: 1 to 1,000; defaults to 100.
offsetintegeroptionalHow many to skip, for paging; defaults to 0.
Retrieve an annotation
/api/organizations/{org}/annotations/{public_id}Update an annotation
/api/organizations/{org}/annotations/{public_id}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.
ended_attimestamp, nullableoptionalWhen it ended. Null makes it a moment again.
messagestringoptionalWhat happened.
started_attimestampoptionalWhen it happened, or when it started.
datasetstring, nullableoptionalA dataset, or null to make it cover the whole organization.
type, attributesoptionalAs on the Annotation object. attributes replaces them all.
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" }'PATCH /api/organizations/<your-org>/annotations/ann_01m5y2k8r4t6w9e3q7p1z0x5cd
Authorization: Bearer TOKEN
Content-Type: application/json
{ "ended_at": "2026-10-26T15:00:00Z" }{
"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
/api/organizations/{org}/annotations/{public_id}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.
service.namestringThe service that it deploys.
Rules: required unless the deploy names a commit; not blank.
service.versionstringThe version that it ships, as the service reports it.
deployment.environment.namestringThe environment, such as production.
Rules: not blank.
vcs.ref.head.revisionstringThe commit that it ships.
Rules: 7 to 40 hex characters, such as $GITHUB_SHA; required unless the deploy names a service.name.
url.fullstringA 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
| Status | When | What to do |
|---|---|---|
| 400 | type is not one of the types | Use one of the types that the error names. |
| 400 | attributes breaks a limit: more than 64 names, a name over 255 characters, or a value that isn't a string, a number or a boolean | Fix the attribute that the error names. |
| 400 | A deploy names neither vcs.ref.head.revision nor service.name, or its commit isn't 7 to 40 hex characters | Send the commit as CI has it, such as $GITHUB_SHA. |
| 400 | ended_at is before started_at, or started_at and time differ | Send one start, and an end after it. |
| 400 | dataset names no dataset that you can read | Use the dataset's name or its public id. |
| 401 | A CI job's token is invalid: its signature, issuer or audience is wrong, or it expired | Ask the provider for a new token, addressed to telemetrymachine.com. |
| 403 | The token lacks annotations:write, or the ingest key doesn't have annotate on | Use a token with the scope, or an ingest key with annotate on. |
| 403 | No trust rule lets the CI job in, or a CI job's token tried to update | Add a trust rule for the repository, or check the ref and environment that it names. |
| 404 | The organization has no annotation with this public_id, or you can't read its dataset | Check the id. |
| 409 | The key exists with a different body | The key names something else. Add what makes it unique, such as the environment. |
| 429 | The organization posted 1,000 annotations in the last 24 hours | Send events at this volume to an events dataset instead. |