---
title: "Report worker telemetry for a run lane (heartbeat + events)"
method: POST
path: "/agents/runs/{id}/events"
tags: ["Agents"]
---

# Report worker telemetry for a run lane (heartbeat + events)

`POST /agents/runs/{id}/events`

## Request

- Base URL: `https://api.apphandoff.com/api`
- URL: `https://api.apphandoff.com/api/agents/runs/{id}/events`
- Auth: API key in cookie `sb-access-token`

Worker telemetry sidecar. Renews the lane's claim lease, applies the latest
phase/log line, and appends batched lifecycle events. Guarded by `worker_id`
— only the worker holding the claim may report (else 409). Rate limited (600/min/IP).

## Path parameters

- `id` string, uuid, required

## Request body

- object
  - `worker_id` string, required
  - `lease_seconds` integer
  - `phase` string
  - `log_line` string
  - `events` object[]
    - `type` 'heartbeat' | 'log_line' | 'phase_change' | 'action' | 'error' | 'ship_complete' | 'transcript_chunk', required
    - `payload` object
  - `usage` object: Optional token report for this batch; cost is priced server-side.
    - `model` string
    - `input_tokens` integer, required
    - `output_tokens` integer, required
    - `cache_read_tokens` integer
    - `cache_write_tokens` integer

## Response `200`

Telemetry recorded; updated run returned

- object
  - `ok` boolean, required
  - `run` AgentRun, required
    - `id` string, uuid, required
    - `plan_id` string, uuid, required
    - `project_id` string, uuid, required
    - `billing_account_id` string, uuid, required
    - `milestone_id` string, uuid, required
    - `lane_key` string, required
    - `title` string, required
    - `role` 'frontend' | 'backend' | 'test' | 'design' | 'devops' | 'qa' | 'other', required
    - `ticket_ids` string[], required
    - `state` 'queued' | 'claimed' | 'running' | 'paused' | 'shipping' | 'shipped' | 'failed' | 'released', required
    - `phase` string, nullable
    - `worker_id` string, nullable: Identity of the worker holding the claim (null until claimed).
    - `claimed_at` string, date-time, nullable
    - `lease_expires_at` string, date-time, nullable: Claim lease deadline; reclaimed if a heartbeat does not renew it.
    - `last_heartbeat` string, date-time, nullable
    - `started_at` string, date-time, nullable
    - `finished_at` string, date-time, nullable
    - `worktree_path` string, nullable
    - `branch` string, nullable
    - `pr_url` string, nullable
    - `last_log_line` string, nullable
    - `external_ref` object, nullable
    - `stall_flagged_at` string, nullable
    - `self_report_state` string, nullable
    - `shipping_started_at` string, nullable
    - `attention_reason` 'stalled' | 'budget_paused' | 'shipping_timeout' | 'null', nullable
    - `attestation` object, nullable
      - `tests_added` boolean
      - `verify_passed` boolean, nullable
      - `verify_command` string, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required

## Other responses

- `400`: Invalid request
- `401`: Not authenticated
- `403`: Forbidden
- `404`: Not found
- `409`: Resource state conflict
- `429`: Rate limited
- `500`: Server error

---

[API](https://skmtc.dev/apphandoff/apis/apphandoff-api.md) · [All operations](https://skmtc.dev/apphandoff/apis/apphandoff-api/llms.txt) · [OpenAPI document](https://skmtc.dev/apphandoff/apis/apphandoff-api/revisions/9f138fe46d56?raw)
