> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rafter.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Scan Site

> Trigger a re-scan of a site you already own

## POST /api/static/sites/scan

Trigger a new scan run for a [Site](/guides/sites) you already own. Use this to re-check a site after remediation or on a schedule.

Requires an API key with the `read-and-scan` scope.

### Request

**Headers:**

* `x-api-key` (required): Your Rafter security API key, scope `read-and-scan`
* `Content-Type: application/json`

**Body:**

Provide exactly one of `projectId` or `url` to identify the site. Optionally restrict the scan to specific sections.

```json theme={null}
{
  "projectId": "b1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

```json theme={null}
{
  "url": "https://example.com"
}
```

**Fields:**

| Field       | Type             | Required                 | Description                                                                                              |
| ----------- | ---------------- | ------------------------ | -------------------------------------------------------------------------------------------------------- |
| `projectId` | string           | One of `projectId`/`url` | The site's id                                                                                            |
| `url`       | string           | One of `projectId`/`url` | The site's URL (used to look up the site)                                                                |
| `sections`  | array of strings | No                       | Restrict the scan to a subset of sections: `"flight"`, `"security"`, `"dns"`. Omit to scan all sections. |

### Example Request

```bash theme={null}
curl -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: RFabc-your-api-key-here" \
  -d '{
    "projectId": "b1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "sections": ["security", "dns"]
  }' \
  https://rafter.so/api/static/sites/scan
```

### Response

**Success (200 OK):**

```json theme={null}
{
  "run": {
    "id": "r_1a2b3c4d",
    "project_id": "b1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "user_id": "u_abc123",
    "status": "running",
    "started_at": "2026-07-24T00:00:00.000+00:00",
    "finished_at": null,
    "progress_total": 0,
    "progress_done": 0,
    "created_at": "2026-07-24T00:00:00.000+00:00",
    "updated_at": "2026-07-24T00:00:00.000+00:00"
  }
}
```

**Error (400 Bad Request — missing or ambiguous identifier):**

```json theme={null}
{
  "error": "Provide exactly one of projectId or url."
}
```

**Error (401 Unauthorized):**

```json theme={null}
{
  "error": "Invalid or inactive API key."
}
```

**Error (403 Forbidden — wrong scope):**

```json theme={null}
{
  "error": "API key does not have read-and-scan scope."
}
```

**Error (403 Forbidden — run limit reached):**

```json theme={null}
{
  "error": "You have reached your scan run limit for this billing period."
}
```

**Error (404 Not Found — not owned or doesn't exist):**

```json theme={null}
{
  "error": "Site not found."
}
```

<Note>
  The 404 response is deliberately identical whether the site doesn't exist or simply isn't owned by your account — this avoids leaking which domains other accounts monitor.
</Note>

**Error (429 Too Many Requests):**

```json theme={null}
{
  "error": "Rate limit exceeded",
  "message": "Too many requests. Please try again later.",
  "retryAfter": 30
}
```

**Error (500 Internal Server Error):**

```json theme={null}
{
  "error": "An unexpected error occurred."
}
```

### Response Fields

| Field                | Type           | Description                                      |
| -------------------- | -------------- | ------------------------------------------------ |
| `run.id`             | string         | Unique identifier for the scan run               |
| `run.project_id`     | string         | The site's id                                    |
| `run.user_id`        | string         | Owning user's id                                 |
| `run.status`         | string         | Run status                                       |
| `run.started_at`     | string \| null | ISO 8601 timestamp when the run started          |
| `run.finished_at`    | string \| null | ISO 8601 timestamp when the run finished         |
| `run.progress_total` | integer        | Total number of steps in the run                 |
| `run.progress_done`  | integer        | Number of steps completed so far                 |
| `run.created_at`     | string         | ISO 8601 timestamp when the run was created      |
| `run.updated_at`     | string         | ISO 8601 timestamp when the run was last updated |

## Rate Limiting

The API implements rate limiting to ensure fair usage:

* **Rate Limit**: 100 requests per minute per IP address
* **Quota**: Based on your subscription plan

### Examples

#### JavaScript

```javascript theme={null}
const response = await fetch('https://rafter.so/api/static/sites/scan', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'RFabc-your-api-key-here'
  },
  body: JSON.stringify({ projectId: 'b1b2c3d4-e5f6-7890-abcd-ef1234567890' })
});

const data = await response.json();
console.log(`Run ID: ${data.run.id}`);
```

#### Python

```python theme={null}
import requests

response = requests.post(
    'https://rafter.so/api/static/sites/scan',
    headers={
        'Content-Type': 'application/json',
        'x-api-key': 'RFabc-your-api-key-here'
    },
    json={'projectId': 'b1b2c3d4-e5f6-7890-abcd-ef1234567890'}
)

data = response.json()
print(f"Run ID: {data['run']['id']}")
```

### Next Steps

Poll the [Get Site endpoint](/api-reference/endpoint/static/sites/get) with the site's id to watch the run's status and see the resulting findings summary once it completes.
