Skip to content
POST /registry/heartbeat

Extends the session's lease by expiresInMs, replaces its headroom and draining state, re-probes the advertise URL, and returns pending control messages. A session with no heartbeat for one lease is dead: its placements re-place on the next request.

Authentication

pinboard:worker scope, same account (sub) that registered the session. The principal's claim must still cover every namespace, hostname and env of the registration.

Request body

application/json, at most 1 MiB.

FieldTypeRequiredDescription
sessionIdstringYesFrom register.
headroomnumberYesCurrent free capacity. Replaces the stored value; each new placement decrements it until the next heartbeat.
drainingbooleanYesThe worker's own drain flag.
challengestringYesToken for this heartbeat's probe. Rotate per heartbeat.
challengeValuestringYesExpected answer.
acksstring[]NomsgIds of control messages the worker has applied.

Probe

Each heartbeat calls GET {advertiseUrl}/_pinned/challenge?challenge={challenge}, as in register.

Probe resultEffect
Answers challengeValueLease renewed. Clears degraded.
Unreachable, non-2xx, or body not receivedLease renewed, record marked degraded (degradedSince). Existing placements keep routing; no new placements. Ack carries degraded: true.
Answers a different valueRegistration voided: records dropped, 409 with reregister. The URL reaches another worker, typically a shared load balancer.

Control messages

Pinboard queues messages per session and re-delivers them on every heartbeat until the worker lists their msgId in acks. The queue expires two leases after its last write or read.

FieldTypeDescription
msgIdstring16 hex characters. Ack it once applied.
type"wind_down"The only type.
nsstringNamespace.
idstringInstance id.
deadlinenumber | nullSeconds from now until the placement is freed, computed at delivery; 0 once passed. null revokes an earlier wind-down.

Example

curl -X POST https://pinboard.example.com/registry/heartbeat \
  -H "Authorization: Bearer $PINBOARD_WORKER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "3f9c0d6e2b1a4c7d8e9f0a1b2c3d4e5f",
    "headroom": 96,
    "draining": false,
    "challenge": "e2a71c0d9f3b4a58",
    "challengeValue": "4c8d2b6f1e0a7c39",
    "acks": ["9a1b2c3d4e5f6071"]
  }'

Response

200

FieldTypeDescription
expiresInMsintegerLease length from now.
drainingbooleanOwn draining or an operator drain on the clientId.
drainDeadlinenumberOnly while draining under a timed operator drain: seconds until forced release, 0 once passed.
degradedtrueOnly when this heartbeat's probe failed.
messagesobject[]Only when non-empty. Control messages above.
{
  "expiresInMs": 15000,
  "draining": false,
  "messages": [
    {
      "msgId": "0f1e2d3c4b5a6978",
      "type": "wind_down",
      "ns": "/threads",
      "id": "t_8f2a",
      "deadline": 29.4
    }
  ]
}

Under a timed operator drain the lease stops advancing past the drain deadline plus one lease.

Errors

StatusBodyWhenWorker should
401{"error":"unauthorized"}No accepted credentialFix the token
403{"error":"forbidden"}Missing scope, other account, or the claim no longer covers the registrationRetry; check the token
503{"error":"not the serving fleet"}Node not servingRetry after Retry-After
413 / 400body too large / invalid jsonFix the request
400{"error":"expected { sessionId, headroom, draining, challenge, challengeValue, acks? }"}Schema failureFix the request
409{"error":"unknown session","reregister":true}Session unknown or lease expiredRegister again with prevSessionId and instances
409{"error":"registration voided: challenge value mismatch on the advertise URL","reregister":true}Probe reached another workerFix advertiseUrl, then register
409{"error":"registration superseded: {hostname}{ns} now bound to env {env} at epoch {n}","superseded":true}Hostname-aware: a higher-epoch registration took the binding. Records dropped.Stop registering