---
title: "Resolve or reopen a signal"
method: PATCH
path: "/api/v1/public/signals/{signal_id}/status"
tags: ["Signals"]
---

# Resolve or reopen a signal

`PATCH /api/v1/public/signals/{signal_id}/status`

## Request

- Base URL: `https://app.traceroot.ai`
- URL: `https://app.traceroot.ai/api/v1/public/signals/{signal_id}/status`
- Auth: HTTP bearer

Resolve or reopen a signal. Dismissing is not possible here.

Requires MEMBER role or higher in the project's workspace (the write
service decides). Resolving needs a ``reason``; the reason ``other`` also
needs a ``note``. A resolved signal opens again by itself when its
detector finds the problem in a trace that started after the resolve. Send
``expected_status`` to refuse the change when someone else changed the
status first. Setting the status the signal already has changes nothing.

Args:
    request (Request): Incoming request (rate-limit plumbing).
    response (Response): Outgoing response (rate-limit plumbing).
    auth (AccountStampedAuth): Account-scope user auth (session token or
        CLI access JWT); its resolved ``user_id`` becomes the actor.
    signal_id (str): The signal to change.
    payload (SignalStatusRequest): The project, the target status, and
        the reason, note and expected status.
    _live (None): Write-path liveness gate (blocks a revoked JWT session).

Returns:
    SignalStatusResponse: The signal with its new status.

Raises:
    HTTPException: The write service's 400/403/404/409 verbatim, or a
        503 when its response is ambiguous.

## Path parameters

- `signal_id` string, required

## Request body

- SignalStatusRequest: Body for resolving or reopening a signal. Only ``resolved`` and ``open`` are settable here. A dismissed signal never reopens on a new finding, so dismissing stays a decision a person makes in the web app. Resolving needs a ``reason``; the reason ``other`` also needs a ``note``. The write service is the single validator of those rules.
  - `expected_status` 'open' | 'resolved' | 'dismissed': The status the caller last read. When the signal's current status differs, nothing is written and the answer is a 409.
  - `note` string: A free-text note; required when reason is "other"
  - `project_id` string, required
  - `reason` 'fixed_elsewhere' | 'fixed_by_pr' | 'already_fixed_before' | 'other': Why the signal is resolved; required when status is "resolved"
  - `status` 'resolved' | 'open', required

## Response `200`

Successful Response

- SignalStatusResponse: The signal after the write. ``changed`` is empty when it already had that status.
  - `changed` string[], required
  - `signal` SignalStatusRow, required: A signal as the status write returns it.
    - `detector_id` string, required
    - `project_id` string, required
    - `reopen_count` integer, required
    - `resolved_at` string, date-time, nullable, required
    - `signal_id` string, required
    - `status` 'open' | 'resolved' | 'dismissed', required
    - `title` string, required
  - `updated` boolean, required

## Other responses

- `400`: Invalid request
- `401`: Authentication failed or session revoked
- `403`: Insufficient role or wrong credential kind
- `404`: Not found
- `409`: The status changed since the caller read it, or the signal was merged
- `422`: Validation Error
- `503`: Write service unavailable

---

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