# List a project's scans

A project's scans, newest first. How far back it goes depends on your plan.

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

OAuth permission: `scans:read` · an API key has them all

## Path parameters

- `project_id` (string, required) Project id.

## Query parameters

- `limit` (integer) How many objects to return, 1 to 100 (default 20).
- `starting_after` (string) Cursor: the id of the last object of the previous page.

## Response (200 OK)

- `object` (string, required)
- `data` (array of object, required)
  - `id` (string, required) Scan id.
  - `object` (string, required)
  - `target_id` (string, required) Target scanned.
  - `kind` (string, required) `quick` (what any visitor could see), `audit` (adds deeper read-only checks, verified sites only) or `repository` (analysis of a GitHub repository). (`quick`, `audit`, `repository`)
  - `status` (string, required) Scans run asynchronously: poll until `done` or `failed`. (`queued`, `running`, `done`, `failed`)
  - `score` (number | null, required) Score out of 100, once done.
  - `grade` (string | null, required) Grade, once done. (`A`, `B`, `C`, `D`, `F`)
  - `coverage` (number | null, required) Share of the checks that could run (0 to 1), once done.
  - `audit_fallback` (boolean, required) An audit was asked but the site's proof no longer held: a quick scan ran instead and no audit was counted.
  - `reused` (boolean, required) True when the request got an existing scan instead of a new one (one already running, or one of the same kind in the last hour); nothing was counted.
  - `next_real_scan_at` (string | null, required) When `reused` because of a recent scan: when a new scan of this kind may start.
  - `created_at` (string, required) When the scan was requested.
  - `finished_at` (string | null, required) When the scan finished.
- `has_more` (boolean, required) Whether more objects follow: pass the last id as `starting_after`.

## Examples

### curl

```bash
# The key stays in a variable, never in the address
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()
```

### Response: 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
}
```

## Errors

| Status | error.code | What happens |
| --- | --- | --- |
| 400 | `parameter_invalid` | A parameter is missing or invalid; `param` names it. |
| 400 | `cursor_invalid` | `starting_after` is not the id of an item of this list. |
| 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. |
| 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
