# Marquer un problème

Marque un problème comme faux positif ou risque accepté, ou le rouvre. La raison s'affiche dans l'app avec le nom de ta clé.

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

Permission OAuth : `issues:write` · une clé d'API les a toutes

> Un faux positif sort du score ; un risque accepté y reste. Pour un problème corrigé, ne le marque pas : [revérifie-le](https://docs.rustine.io/api/issues/recheck), Rustine le fermera.

## Paramètres de chemin

- `issue_id` (string, obligatoire) Le problème.

## Corps de la requête

- `status` (string, obligatoire) `false_positive` (sort du score), `accepted_risk` (reste dans le score) ou `open` pour annuler. (`open`, `false_positive`, `accepted_risk`)
- `reason` (string) Pourquoi, avec ce que tu as vérifié (fichier, ligne, raison), de 10 à 500 caractères. Obligatoire pour `false_positive` et `accepted_risk` ; affichée dans l'app avec le nom de ta clé.

## Réponse (200 OK)

- `id` (string, obligatoire) L'identifiant du problème ; il le garde d'un scan à l'autre.
- `object` (string, obligatoire) Le type d'objet, toujours le même pour une route donnée.
- `project_id` (string, obligatoire) Le projet du problème.
- `target_id` (string, obligatoire) Le site ou le dépôt où il a été trouvé.
- `address` (string, obligatoire) Le nom d'hôte du site ou `propriétaire/dépôt`.
- `control_id` (string, obligatoire) Le contrôle qui l'a trouvé (identifiant stable).
- `family` (string, obligatoire) La famille du contrôle : base de données, secrets, fichiers exposés… (`database`, `secrets`, `exposedFiles`, `personalData`, `vulnerableVersions`, `configuration`, `compliance`, `server`, `code`)
- `severity` (string, obligatoire) La gravité, de `critical` à `info`. (`critical`, `high`, `medium`, `low`, `info`)
- `confidence` (string, obligatoire) À quel point Rustine est sûr : `confirmed` (vu), `likely` (très probable), `estimated` (déduit, par exemple d'une version). (`confirmed`, `likely`, `estimated`)
- `status` (string, obligatoire) `open` ; `fixed` (un scan ou une revérification ne le trouve plus) ; `false_positive` (sorti du score) ; `accepted_risk` (reste dans le score). (`open`, `fixed`, `false_positive`, `accepted_risk`)
- `title` (string, obligatoire) Le problème en une ligne, dans la langue du propriétaire de la clé.
- `explanation` (string, obligatoire) Le risque en mots simples, dans la langue du propriétaire de la clé.
- `detail` (string, obligatoire) Le détail technique, dans la langue du propriétaire de la clé.
- `location` (string, obligatoire) Où : un chemin, une table, ou `fichier:ligne@commit` dans un dépôt.
- `file` (string | null, obligatoire) Problème de dépôt : le fichier.
- `line` (integer | null, obligatoire) Problème de dépôt : la ligne.
- `commit` (string | null, obligatoire) Problème de dépôt : le commit analysé.
- `evidence` (string, obligatoire) Ce que le contrôle a observé, secrets masqués.
- `fix_unlocked` (boolean, obligatoire) Vrai si son correctif est débloqué : le relire ne coûte rien.
- `status_reason` (string | null, obligatoire) Pourquoi il a été marqué faux positif ou risque accepté.
- `marked_by` (string | null, obligatoire) Qui l'a marqué : un agent, le nom d'une clé d'API, ou `app`.
- `first_seen_at` (string, obligatoire) Le premier scan qui l'a trouvé.
- `last_seen_at` (string, obligatoire) Le dernier scan qui l'a trouvé.
- `fixed_at` (string | null, obligatoire) Quand il a été trouvé corrigé.

## Exemples

### curl

```bash
# La clé reste dans une variable, jamais dans l'adresse
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()
```

### Réponse : 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
}
```

## Erreurs

| Code | error.code | Ce qui se passe |
| --- | --- | --- |
| 400 | `body_invalid` | Le corps de la requête n'est pas du JSON valable. Vérifie l'en-tête `Content-Type: application/json`. |
| 400 | `parameter_invalid` | Un paramètre manque ou n'est pas valable ; `param` le nomme. |
| 400 | `reason_required` | Marquer un faux positif ou un risque accepté demande une raison, de 10 à 500 caractères. |
| 401 | `authentication_required` | Aucune clé dans la requête. Passe-la dans l'en-tête `Authorization: Bearer rst_…`, jamais dans l'adresse. |
| 401 | `invalid_api_key` | La clé ou le jeton OAuth est faux, révoqué ou expiré. Vérifie qu'il est copié en entier, ou crée une nouvelle clé. |
| 402 | `api_plan_required` | L'espace est sur l'offre Gratuite. L'API vient avec Pro, ou à l'essai 14 jours sur Solo. |
| 402 | `api_trial_ended` | L'essai de 14 jours de Solo est terminé. Passe à Pro : tes clés remarchent aussitôt. |
| 403 | `scope_missing` | Le jeton OAuth n'a pas la permission que demande cette route (indiquée sur sa page). Reconnecte l'agent pour l'accorder. Une clé d'API a toutes les permissions. |
| 404 | `resource_not_found` | Rien avec cet identifiant dans l'espace de la clé. Un objet d'un autre espace répond pareil : son existence n'est jamais révélée. |
| 409 | `issue_already_fixed` | Le problème est corrigé : il n'y a rien à marquer. |
| 409 | `proof_required` | Le site doit d'abord être prouvé comme le tien (audit complet, correctif, marquage). Prouve-le dans l'app : DNS, fichier ou balise meta. |
| 429 | `rate_limited` | Trop de requêtes pour cette clé (120 par minute). Attends `Retry-After` secondes. |
| 500 | `internal_error` | Une erreur de notre côté, signalée automatiquement. Réessaie plus tard ; donne le `Request-Id` au support si elle revient. |
| 503 | `service_unavailable` | Rustine est indisponible un instant. Réessaie avec la même `Idempotency-Key`. |

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