# Historique des scans

Les scans d'un projet, du plus récent au plus ancien. La profondeur de l'historique dépend de ton offre.

```
GET https://api.rustine.io/v1/projects/{project_id}/scans
```

Permission OAuth : `scans:read` · une clé d'API les a toutes

## Paramètres de chemin

- `project_id` (string, obligatoire) Le projet.

## Paramètres de requête

- `limit` (integer) Combien d'éléments par page, de 1 à 100 (20 par défaut).
- `starting_after` (string) Le curseur : l'identifiant du dernier élément de la page précédente.

## Réponse (200 OK)

- `object` (string, obligatoire) Le type d'objet, toujours le même pour une route donnée.
- `data` (array of object, obligatoire) Les éléments de la page.
  - `id` (string, obligatoire) L'identifiant du scan.
  - `object` (string, obligatoire) Le type d'objet, toujours le même pour une route donnée.
  - `target_id` (string, obligatoire) Le site ou le dépôt scanné.
  - `kind` (string, obligatoire) `quick` (ce que tout visiteur voit), `audit` (ajoute des contrôles en lecture seule, sites prouvés seulement) ou `repository` (analyse d'un dépôt GitHub). (`quick`, `audit`, `repository`)
  - `status` (string, obligatoire) Les scans tournent en arrière-plan : interroge jusqu'à `done` ou `failed`. (`queued`, `running`, `done`, `failed`)
  - `score` (number | null, obligatoire) Le score sur 100, une fois terminé.
  - `grade` (string | null, obligatoire) La note, une fois terminé. (`A`, `B`, `C`, `D`, `F`)
  - `coverage` (number | null, obligatoire) La part des contrôles qui ont pu tourner (de 0 à 1), une fois terminé.
  - `audit_fallback` (boolean, obligatoire) Un audit était demandé mais la preuve du site ne tenait plus : un scan rapide a tourné à la place, et aucun audit n'a été décompté.
  - `reused` (boolean, obligatoire) Vrai quand la requête a reçu un scan existant au lieu d'un nouveau (un en cours, ou un du même type dans l'heure) ; rien n'a été décompté.
  - `next_real_scan_at` (string | null, obligatoire) Avec `reused` pour un scan récent : quand un nouveau scan de ce type pourra partir.
  - `created_at` (string, obligatoire) Quand il a été demandé (ISO 8601, UTC).
  - `finished_at` (string | null, obligatoire) Quand il s'est terminé, `null` tant qu'il tourne.
- `has_more` (boolean, obligatoire) Vrai s'il reste des éléments après cette page : rappelle la route avec `starting_after`.

## Exemples

### curl

```bash
# La clé reste dans une variable, jamais dans l'adresse
curl https://api.rustine.io/v1/projects/4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b/scans \
  -H "Authorization: Bearer $RUSTINE_API_KEY"
```

### Node

```js
const res = await fetch(
  "https://api.rustine.io/v1/projects/4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b/scans",
  {
    headers: {
      Authorization: `Bearer ${process.env.RUSTINE_API_KEY}`,
    },
  },
)
const page = await res.json()
```

### Python

```python
import os

import requests

page = requests.get(
    "https://api.rustine.io/v1/projects/4f9c2a7e-1b3d-4e5f-8a6b-7c8d9e0f1a2b/scans",
    headers={
        "Authorization": f"Bearer {os.environ['RUSTINE_API_KEY']}",
    },
).json()
```

### Réponse : 200 OK

```json
{
  "object": "list",
  "data": [
    {
      "id": "e7f9a1b3-5c7d-4e9f-b1a3-c5d7e9f1a3b5",
      "object": "scan",
      "target_id": "8d2e4f6a-9b1c-4d3e-a5f7-0b2c4d6e8f1a",
      "kind": "audit",
      "status": "done",
      "score": 62,
      "grade": "C",
      "coverage": 0.94,
      "audit_fallback": false,
      "reused": false,
      "next_real_scan_at": null,
      "created_at": "2026-10-13T08:41:02.000Z",
      "finished_at": "2026-10-13T08:42:10.000Z"
    }
  ],
  "has_more": true
}
```

## Erreurs

| Code | error.code | Ce qui se passe |
| --- | --- | --- |
| 400 | `parameter_invalid` | Un paramètre manque ou n'est pas valable ; `param` le nomme. |
| 400 | `cursor_invalid` | `starting_after` n'est l'identifiant d'aucun élément de cette liste. |
| 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. |
| 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
