---
title: "Next alarm: claim the oldest eligible unheld alarm in one call"
method: POST
path: "/events/claims/next"
tags: ["events"]
---

# Next alarm: claim the oldest eligible unheld alarm in one call

`POST /events/claims/next`

## 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/events/claims/next`
- Auth: HTTP bearer

One call hands the caller an unheld, unreserved alarm, ALREADY CLAIMED, in one transaction, chosen best-effort among the alarms the statement can see and lock. Order: any eligible alarm queued at least 45 s first (oldest of those first); below that the caller's own Focus alarms; then the oldest in the queue (`queued_at`, then id). Severity does not sort the hand-out. Never a live-held alarm (including the caller's own), an alarm validly reserved for another operator's Focus, or (with `CLAIM_ESCALATION_ENABLED` on) an alarm with a live call or announcement in progress (a snapshot read, not a commit-time guarantee). Two operators pressing at once get distinct alarms. The claim is an ordinary claim (120 s TTL, renewed by the review page, a fresh ownership generation). Manual clicks and Take over are unchanged.

`request_id` identifies ONE press: retrying the same id (a lost response, a timeout) returns the RECORDED result with the alarm's current state (`replay: true`; `current` is `yours`, `taken_over`, `closed`, `lapsed`, `reserved`, `out_of_scope` or `unavailable`) and never selects a second alarm; the record is kept 24 hours. `status: none` carries a `reason` and grouped `counts` inside the caller's scope: `empty`, `all_held`, `all_reserved`, `all_in_session`, `contended` (others are picking right now), `scan_budget` (the 200-row scan stopped early; pick from the list) or `not_a_claimant` (owners never take claims). 503 `outcome: not_applied` (this call established that the `request_id` has no record AND its allocation was rolled back: nothing claimed, the id may be abandoned) or `unknown` (anything else: a failure before the record was looked up, while replaying it, or after the COMMIT may have been sent; keep the id and retry the SAME `request_id`, the server's record decides) with `Retry-After`. Requires `review_label_alarms` and central staff (403 otherwise). 404 as if the route did not exist while `CLAIM_NEXT_ENABLED` is off (read `claim_next_enabled` on `GET /auth/me`).

## Request body

- ClaimNextRequest
  - `request_id` string, uuid, required: One per key press, generated by the console. Reuse it ONLY to retry that same press (a timeout, a lost response, a 503 `unknown`): the server answers a recorded press from its record and never selects a second alarm. A deliberate new press uses a new id. The record is kept for 24 hours.
  - `source` 'queue_key' | 'queue_button' | 'review_key' | 'review_button', required: Where the press came from. Logged and recorded; it never changes the selection.

## Response `200`

Successful Response

- ClaimNextResponse
  - `status` 'claimed' | 'none', required: `claimed`: an alarm (see `current`). `none`: nothing could be claimed (see `reason`).
  - `replay` boolean, required: True when this answers a RECORDED press (a retry of the same `request_id`); a replay never selects, stamps or extends anything.
  - `current` 'yours' | 'taken_over' | 'closed' | 'lapsed' | 'reserved' | 'out_of_scope' | 'unavailable', nullable: `claimed` only. The alarm's state NOW (read under the event row lock), never a promise of ownership the caller no longer has. A fresh handout is always `yours`. A replay may be any value; navigate automatically only for `yours`.
  - `event_id` string, nullable: The alarm. Omitted for `out_of_scope` and `unavailable` replays.
  - `claim` AlarmClaim
    - `operator_id` string, required: The operator holding the alarm
    - `operator_name` string, nullable: Their display name; null if the operator no longer exists
    - `claimed_at` string, required: When the claim was taken (ISO 8601, UTC)
    - `expires_at` string, required: When the claim lapses (ISO 8601, UTC)
    - `expires_in_seconds` integer, required: Seconds left on the claim, computed server-side. Time the claim from this against the local clock at receipt, not from `expires_at`, so a skewed workstation clock does not move it.
    - `generation` string, nullable: Ownership generation (a UUID): it changes whenever the claim's HOLDER changes (acquire, take-over) and never on a renewal. Echo it on `POST /events/{id}/claim/acting` and its cancel; a write carrying an old one is rejected. Null while CLAIM_ESCALATION_ENABLED is off, or on a claim stamped before generations existed (the holder's next renew fills it in).
  - `alarm` object, nullable: The queue row (the `GET /operator/events/all` row shape) for `yours`; null if hydration did not finish in its budget (the claim stands and `event_id` identifies the alarm: fetch it).
  - `tier` string, nullable: A display / log LABEL (`critical`, `high`, `medium`) of the handed-out alarm. Never an ordering input.
  - `queued_at` string, nullable: When the handed-out alarm entered the queue (ISO 8601, UTC): the clock the card shows.
  - `reason` string, nullable: `none` only: `empty`, `all_held`, `all_reserved`, `all_in_session`, `contended`, `scan_budget` or `not_a_claimant`.
  - `counts` ClaimNextCounts
    - `waiting` integer, required: Open alarms nobody holds or has reserved for another operator (inside the caller's scope).
    - `held_by_others` integer, required: Open alarms live-held by another operator.
    - `held_by_you` integer, required: Open alarms live-held by the caller.
    - `reserved_for_others` integer, required: Not live-held alarms under another operator's valid Focus.
    - `in_session_seen` integer, required: Alarms this scan skipped for a live call / announcement / intent. A LOWER BOUND (seen in this scan), not a total.
  - `recorded_at` string, nullable: Replay only: when the press was recorded.
  - `as_of` string, nullable: Replay only: the server time `current` describes.

## Other responses

- `422`: Validation Error
- `503`: Deadline or database failure; see `outcome`.

---

[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)
