---
title: "Who is focusing (the active focus records in your scope)"
method: GET
path: "/focus"
tags: ["focus"]
---

# Who is focusing (the active focus records in your scope)

`GET /focus`

## Request

- Base URL: `https://api.verdun.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.verdun.ai/focus`
- Auth: HTTP bearer

The active focus records in the caller's tenant scope: what makes "everyone sees who is focusing". The claims reads (`GET /events/claims`, `GET /events/{id}/claim`) carry the same records beside `reservations`. Central staff only. 404 while `FOCUS_ENABLED` is off.

## Response `200`

Successful Response

- FocusListResponse
  - `focus` union[], required: The active focus records in the caller's tenant scope (small): who is focusing.
    - union
      - FocusRecord: One Focus generation (`id` IS the generation: a restart is a new id).
        - `id` string, required: The focus generation id; a restart is a new id.
        - `site_id` string, required: The site the focus is on.
        - `scope` FocusScopeOut, required
          - `kind` 'site' | 'camera', required: Whether the focus covers the whole site or one camera.
          - `camera_id` string, nullable: Set iff `kind` is `camera`.
        - `operator_id` string, required: The focuser.
        - `operator_name` string, nullable: Their display name; null if the operator no longer exists.
        - `started_at` string, required: ISO 8601, UTC.
        - `last_input_at` string, required: Last accepted meaningful input or Keep (ISO 8601, UTC).
        - `check_due_at` string, required: `last_input_at` + FOCUS_CHECK_IDLE_SECONDS: when the console asks "Still focusing?".
        - `check_deadline` string, required: `check_due_at` + FOCUS_CHECK_ANSWER_SECONDS: with no explicit Keep by then the server releases the focus (and treats it as released at once, before any sweep).
        - `check_pending` boolean, required: True while the check is due and unanswered (active records only).
        - `check_due_in_seconds` integer, required: Seconds until `check_due_at`, computed server-side: time it from this against the local clock at receipt.
        - `check_deadline_in_seconds` integer, required: Seconds until `check_deadline`, computed server-side.
        - `state` 'active' | 'released', required: `active` while reserving alarms; `released` once it has ended.
        - `released_at` string, nullable: When the focus was released (ISO 8601, UTC); null while active.
        - `release_reason` 'manual' | 'check_timeout' | 'disabled' | 'scope_lost', nullable: Why it was released; null while active.
      - StoryHoldRecord: One site hold (`origin = "story"`, Site Story step 3): one operator owns the site while its story is open. `id` is the hold; `lineage_id` is shared by a Take over's successor, which is how the held alarms stay held for the taker with no gap.
        - `origin` 'story', required: Always `story`.
        - `id` string, required: The hold id (a site Take over creates a successor with a new id in the same lineage).
        - `lineage_id` string, required: Shared by a hold and its Take over successors.
        - `site_id` string, required: The site the hold is on.
        - `scope` FocusScopeOut, required
          - `kind` 'site' | 'camera', required: Whether the focus covers the whole site or one camera.
          - `camera_id` string, nullable: Set iff `kind` is `camera`.
        - `operator_id` string, required: The holder.
        - `operator_name` string, nullable: Their display name; null if the operator no longer exists.
        - `started_at` string, required: ISO 8601, UTC.
        - `last_seen_at` string, nullable: The holder's last heartbeat (ISO 8601, UTC).
        - `left_at` string, nullable: Set by the holder's best-effort leave signal, cleared by a heartbeat inside the grace. After a clean leave the hold returns its alarms about 30 s after this instant (a fixed server constant), or sooner on its idle or new-alert deadline.
        - `open` boolean, required: True while the holder's page is open: no clean leave and a heartbeat within `open_s`. While true there is no deadline of any kind.
        - `return_at` string, nullable: When the unattended hold returns its alarms to the queue (ISO 8601, UTC); null while `open`. The earliest of the idle deadline, the new-alert deadline and (after a clean leave) 30 s after `left_at`, pushed later by a live call. A display, never a decision.
        - `now` string, required: The server clock this record was built against: compute `return_at - now` once and tick it locally.
        - `serious_unopened` integer: Serious alarms the current holder has not opened (visibility only).
        - `oldest_serious_queued_at` string, nullable: When the oldest of those entered the queue (ISO 8601, UTC).
        - `open_seconds_total` integer: Seconds the holder's page was open during the hold (each interval since the last heartbeat, capped at `open_s`, settled at the next heartbeat, a leave or the release).
        - `idle_s` integer, required: Frozen at start: seconds since the last heartbeat before an unattended hold returns. The backstop for a page that vanished without a clean leave; after a clean leave the hold returns about 30 s later instead (a fixed server constant, not frozen).
        - `new_alert_s` integer, required: Frozen at start: seconds after a new alert before an unattended hold returns.
        - `open_s` integer, required: Frozen at start: seconds since the last heartbeat within which the page still counts as open.
        - `state` 'active' | 'released', required: `active` while it holds the site.
        - `released_at` string, nullable: ISO 8601, UTC; null while active.
        - `release_reason` 'idle_2min' | 'new_alert_2min' | 'holder_gone' | 'submit' | 'taken_over' | 'flag_off' | 'scope_lost' | 'disabled', nullable: Why it ended; null while active. `idle_2min` is the unattended return after a clean leave (about 30 s after it, or the idle deadline if that came first; the name is historical, not a measured 2 minutes); `holder_gone` is the idle deadline with no clean leave.

---

[API](https://skmtc.dev/verdun/apis/verdun-api.md) - [All operations](https://skmtc.dev/verdun/apis/verdun-api/llms.txt) - [OpenAPI document](https://skmtc.dev/verdun/apis/verdun-api/revisions/35a2a6ebedda?raw)
