POST /v1/heartbeat
Integration liveness over the authenticated channel. Uninstalling or silencing an integration is the cheapest bypass there is, and without a beat the runtime cannot distinguish "agent idle" (fine) from "integration went dark" (a coverage loss). The heartbeat keeps those two facts apart.
A heartbeat is transport-level: it is not a GuardEvent and carries no
guarded action. This is also where the integration build id's liveness copy lives —
fleet coverage reads it from here. (The event carries its own optional
integration copy for per-event triage: the heartbeat goes quiet exactly
when a bad rollout is what you are naming.)
POST {base_url}/v1/heartbeat
Authorization: Bearer ogr_<key>
Content-Type: application/json
Request
At least one of integration / agent_id must be present.
{
"integration": "ogr-higress/3.14.0",
"instance_id": "gw-7c4f1a",
"agent_id": "invoice-bot",
"interval_s": 30,
"counters": {"events_sent": 120, "evaluate_errors": 0, "unresolved_spans": 0},
"ruleset": "rs_8f21c0"
}
| Field | Type | Required | Description |
|---|---|---|---|
integration | string | one-of | The integration identifying itself — name and build, e.g. my-harness/1.2.0 |
agent_id | string | one-of | The agent whose liveness rides this beat |
instance_id | string | optional | WHICH PROCESS is beating — one value per running replica, stable for that process's life |
interval_s | number | optional | Declared cadence; lets the runtime compute "missed beats" |
counters | object | optional | Free-form counters — events_sent, evaluate_errors, unresolved_spans, … |
ruleset | string | optional | The id of the secret ruleset this integration currently holds, when it does local redaction |
⚠️ Send instance_id if you run more than one replica. A runtime records the
build and the counters per (integration, instance_id). Without it, every replica
of one integration collapses into a single row: the version shown is whichever
beat last, and a lab instance on a newer build can name itself while the replicas
actually serving traffic are invisible. The counters have the same problem — they
are properties of a PROCESS, not of a name.
evaluate_errors is how the runtime learns an integration entered
degraded mode: events observed
while the runtime was unreachable are lost observations (the protocol has no replay
channel), and the counters are what make the gap visible instead of silent.
unresolved_spans counts
modification spans the
integration could not apply — "no spans resolved" is otherwise
indistinguishable from "no redaction policy".
Response — 200
{
"ok": true,
"limits": { "max_request_bytes": 67108864,
"media": { "image": 8388608, "audio": 8388608, "document": 16777216,
"file": 8388608, "video": 0 },
"media_parts_max": 16 },
"rules": { "id": "rs_8f21c0" }
}
The two echoes let a long-running integration learn about a change within one interval, without polling:
limitsis the same objectGET /v1/limitsserves — what this tenant may send. An integration that elides oversized media reads its budget from here.rules.idis the id of the tenant's current secret ruleset. When it differs from the one the integration holds, refetchGET /v1/rules.
Both are advisory: an integration that ignores them behaves exactly as before.
A heartbeat registers a live-but-idle agent: fleet coverage reflects integrations that have not yet emitted a single event. Deploy the integration, start the beat, and the runtime knows the surface is covered before the first guarded action arrives.
Example
curl -s $OGR_RUNTIME/v1/heartbeat \
-H "Authorization: Bearer $OGR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"integration": "my-harness/1.0.0", "agent_id": "invoice-bot", "interval_s": 30}'
Operational semantics
- A runtime alerts when an integration misses beats beyond a tolerance, and treats the gap as a coverage loss — never as "no risk".
countersreconciled against delivered events is what makes selective event suppression detectable: an integration reporting N sent while N−k arrived is a finding, not noise.