Skip to content

Token Events API ​

A token event records the AI token usage of one agent session, for one model, on one issue. kendo adds every event to the issue's token_total, and the sprint and epic totals add up their issues. Send the numbers your agent's harness measured. kendo stores counts only, never prompts or responses.

For Claude Code, the Token Usage guide has a ready hook that sends every session through the CLI. AI assistants on the kendo MCP server can use the record-token-usage tool instead.

Permissions ​

Ingestion takes a token with the write scope:

AuthenticationRequiredScope
Personal access token or CLI loginThe write scopeProjects the token's user can access
Project tokenRead & writeThe single project the token is linked to

The token's user (for a project token, its creator) must also hold the Token Events: Create permission in the project. The Member role and every role that existed on 2026-10-08 hold it. A role created later starts with every Create permission off, this one included, and an admin turns it on at the Roles page (/roles). Admins and the project owner always pass.

Endpoints ​

MethodEndpointDescription
POST/api/projects/{projectId}/token-eventsRecord one event or a batch of up to 100

Record Token Usage ​

POST /api/projects/{projectId}/token-events

The endpoint returns 202 Accepted with an empty body.

bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/token-events \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "issue_key": "KD-0042",
    "model": "claude-opus-5-5",
    "tokens_in": 1200,
    "tokens_out": 350,
    "tokens_cache_read": 40000,
    "tokens_cache_creation": 6000,
    "source": "api",
    "occurred_at": "2026-10-08T14:00:00Z",
    "external_event_id": "6f1c2b9e-0d4a-4c1e-9a7b-2f3e8d5c1a90:claude-opus-5-5"
  }'
text
(empty body)

To send several events in one request, wrap them in an events array: {"events": [{...}, {...}]}. A batch is all or nothing: if one event fails, kendo stores none of them.

Request Fields ​

FieldTypeRequiredDescription
issue_idintegerOne ofThe numeric ID of the issue (send issue_id or issue_key, not both)
issue_keystringOne ofThe issue key exactly as kendo shows it, e.g. "KD-0042", max 20 characters
modelstringYesThe model the tokens were spent on, max 255 characters (e.g. claude-opus-5-5)
tokens_inintegerYesInput tokens, not counting cache reads or cache writes, at least 0
tokens_outintegerYesOutput tokens, at least 0
tokens_cache_readinteger | nullYesTokens read from the prompt cache. The key must be present: send null when your harness does not report it, which kendo stores as "not reported" rather than 0
tokens_cache_creationinteger | nullYesTokens written to the prompt cache. Same rule as tokens_cache_read
sourcestringYesHow the event was captured: api for your own integration, cli for the kendo CLI. ally_hook and backfill are also accepted. mcp is set only by the MCP tool, and hand_to_claude is retired
occurred_atstringYesWhen the usage happened, ISO 8601 (e.g. 2026-10-08T14:00:00Z), max 255 characters
external_event_idstringNoYour idempotency id, max 255 characters. See Resending a session

kendo ignores any other field.

Resending a Session ​

Send one event per session and model, with an external_event_id that names both, such as <session id>:<model>. When kendo already holds an event with that id:

  • Same issue, same or later occurred_at: kendo replaces the stored totals with the new ones. A retry stores nothing twice, and a resumed session's larger total replaces its earlier one.
  • Same issue, earlier occurred_at: kendo keeps the stored totals, so a late retry never lowers them.
  • Another issue: kendo ignores the event. An id stays on the issue it was first sent for.

The id is unique across the whole workspace, not per project or per user. Use an id nobody else can choose by accident, such as a session UUID. An event without an external_event_id is always stored as a new row, so a retry counts it twice.

How the Total Is Counted ​

An issue's token_total is the sum of tokens_in, tokens_out, tokens_cache_read and tokens_cache_creation over its events, with a null counted as 0. The Issues API returns it on each issue.

Common Errors ​

StatusCause
401Token is missing, invalid, revoked, or expired
403Token lacks the write scope, a project token is not linked to the target project, or the token's user lacks Token Events: Create
404An issue_id or issue_key names no issue in this project. Nothing in the batch is stored
422Request validation failed, including an event that sends both issue_id and issue_key, or neither
429Rate limit exceeded: 60 requests per minute per token

See Also ​