# Mark an issue

Marks an issue as a false positive or an accepted risk, or opens it again. The reason shows in the app with your key's name.

```
PATCH https://api.rustine.io/v1/issues/{issue_id}
```

OAuth permission: `issues:write` · an API key has them all

> A false positive leaves the score; an accepted risk stays in it. For a fixed issue, do not mark it: [recheck it](https://docs.rustine.io/en/api/issues/recheck) and Rustine closes it.

## Path parameters

- `issue_id` (string, required) Issue id.

## Request body

- `status` (string, required) `false_positive` (leaves the score), `accepted_risk` (stays in the score) or `open` to undo. (`open`, `false_positive`, `accepted_risk`)
- `reason` (string) Why, with the evidence checked (10 to 500 characters). Required for `false_positive` and `accepted_risk`; shown in the app with the key's name.

## Response (200 OK)

- `id` (string, required) Issue id; it keeps its identity from one scan to the next.
- `object` (string, required)
- `project_id` (string, required) Project of the issue.
- `target_id` (string, required) Target where it was found.
- `address` (string, required) Hostname of the site or `owner/repo`.
- `control_id` (string, required) The check that found it (stable identifier).
- `family` (string, required) Family of the check. (`database`, `secrets`, `exposedFiles`, `personalData`, `vulnerableVersions`, `configuration`, `compliance`, `server`, `code`)
- `severity` (string, required) (`critical`, `high`, `medium`, `low`, `info`)
- `confidence` (string, required) How sure the finding is. (`confirmed`, `likely`, `estimated`)
- `status` (string, required) `open`; `fixed` (a later scan or recheck no longer finds it); `false_positive` (left out of the score); `accepted_risk` (kept in the score). (`open`, `fixed`, `false_positive`, `accepted_risk`)
- `title` (string, required) One line, in the language of the key's owner.
- `explanation` (string, required) The risk in plain words, in the key owner's language.
- `detail` (string, required) Technical detail, in the key owner's language.
- `location` (string, required) Where: a path, a table, or `file:line@commit` in a repository.
- `file` (string | null, required) Repository issue: the file.
- `line` (integer | null, required) Repository issue: the line.
- `commit` (string | null, required) Repository issue: the commit analysed.
- `evidence` (string, required) What the check observed, secrets masked.
- `fix_unlocked` (boolean, required) Whether its fix is unlocked (readable for free).
- `status_reason` (string | null, required) Why it was marked a false positive or an accepted risk.
- `marked_by` (string | null, required) Who marked it: an agent, an API key's name, or `app`.
- `first_seen_at` (string, required) First scan that found it.
- `last_seen_at` (string, required) Last scan that found it.
- `fixed_at` (string | null, required) When it was found fixed.

## Examples

### curl

```bash
# The key stays in a variable, never in the address
curl https://api.rustine.io/v1/issues/2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f \
  -X PATCH \
  -H "Authorization: Bearer $RUSTINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"false_positive","reason":"README.md:42 shows a placeholder key (sk_test_xxxx) that opens nothing."}'
```

### Node

```js
const res = await fetch(
  "https://api.rustine.io/v1/issues/2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
  {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${process.env.RUSTINE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      status: "false_positive",
      reason: "README.md:42 shows a placeholder key (sk_test_xxxx) that opens nothing."
    }),
  },
)
const issue = await res.json()
```

### Python

```python
import os

import requests

issue = requests.patch(
    "https://api.rustine.io/v1/issues/2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
    headers={
        "Authorization": f"Bearer {os.environ['RUSTINE_API_KEY']}",
    },
    json={
        "status": "false_positive",
        "reason": "README.md:42 shows a placeholder key (sk_test_xxxx) that opens nothing.",
    },
).json()
```

### Response: 200 OK

```json
{
  "id": "2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
  "object": "issue",
  "project_id": "4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b",
  "target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
  "address": "app.example.com",
  "control_id": "exposed-env-file",
  "family": "exposedFiles",
  "severity": "high",
  "confidence": "confirmed",
  "status": "false_positive",
  "title": "Your .env file is readable online.",
  "explanation": "Your secrets are served to everyone.",
  "detail": "GET /.env → 200",
  "location": "/.env",
  "file": null,
  "line": null,
  "commit": null,
  "evidence": "GET /.env → 200 (STRIPE_SECRET_KEY=sk_live_••••)",
  "fix_unlocked": false,
  "status_reason": "README.md:42 shows a placeholder key (sk_test_xxxx) that opens nothing.",
  "marked_by": "CI script",
  "first_seen_at": "2026-10-13T08:42:10.000Z",
  "last_seen_at": "2026-10-13T08:42:10.000Z",
  "fixed_at": null
}
```

## Errors

| Status | error.code | What happens |
| --- | --- | --- |
| 400 | `body_invalid` | The request body is not valid JSON. Check the `Content-Type: application/json` header. |
| 400 | `parameter_invalid` | A parameter is missing or invalid; `param` names it. |
| 400 | `reason_required` | Marking a false positive or an accepted risk needs a reason, 10 to 500 characters. |
| 401 | `authentication_required` | No key in the request. Pass it in the `Authorization: Bearer rst_…` header, never in the address. |
| 401 | `invalid_api_key` | The key or OAuth token is wrong, revoked or expired. Check it is copied in full, or create a new key. |
| 402 | `api_plan_required` | The workspace is on the Free plan. The API comes with Pro, or on a 14-day trial with Solo. |
| 402 | `api_trial_ended` | Solo's 14-day trial is over. Move to Pro: your keys work again at once. |
| 403 | `scope_missing` | The OAuth token lacks the permission this route needs (shown on its page). Reconnect the agent to grant it. An API key has every permission. |
| 404 | `resource_not_found` | Nothing with this id in the key's workspace. An object of another workspace answers the same: its existence is never revealed. |
| 409 | `issue_already_fixed` | The issue is fixed: there is nothing to mark. |
| 409 | `proof_required` | The site must first be proved yours (full audit, fix, marking). Prove it in the app: DNS, file or meta tag. |
| 429 | `rate_limited` | Too many requests for this key (120 per minute). Wait `Retry-After` seconds. |
| 500 | `internal_error` | An error on our side, reported automatically. Retry later; give the `Request-Id` to support if it comes back. |
| 503 | `service_unavailable` | Rustine is unavailable for a moment. Retry with the same `Idempotency-Key`. |

https://docs.rustine.io/en/api/errors
