# Crawls

Crawl runs and their results, issues and pages.

## List the crawls of a project

`GET /v1/projects/{project}/crawls`

Crawl runs of a project, newest first.


- Required scopes: `crawls:read`
- Rate-limit cost: 1 unit(s)

### Parameters

- `project` (path, string (uuid), required)
- `cursor` (query, string, optional): Opaque cursor from `meta.next_cursor`. It is bound to the request that issued it; reusing it with other filters returns `400 pagination.invalid_cursor`.
- `limit` (query, integer, optional): Items per page, 1 to 100 (default 25).

### Responses

- `200`: OK.
- `400`: Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`).
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/projects/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/crawls" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": [
        {
            "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
            "project_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
            "campaign_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
            "status": "queued",
            "stage": "string",
            "active": true,
            "stage_plan": {
                "current_step": 42,
                "step_count": 42,
                "settled": true,
                "steps": [
                    {
                        "stage": "string",
                        "state": "pending",
                        "skip_reason": "string"
                    }
                ]
            },
            "pages": {
                "crawled": 42,
                "discovered": 42,
                "budget": 42
            },
            "issue_counts": {
                "error": 42,
                "warning": 42,
                "info": 42
            },
            "truncated": true,
            "blocked": true,
            "blocked_reason": "string",
            "comparable": true,
            "error": "string",
            "trigger": "manual",
            "started_at": "2026-10-01T09:30:00Z",
            "finished_at": "2026-10-01T09:30:00Z",
            "created_at": "2026-10-01T09:30:00Z"
        }
    ],
    "meta": {
        "limit": 100,
        "has_more": true,
        "next_cursor": "string"
    }
}
```

## Get a crawl

`GET /v1/crawls/{crawl}`

Status of a crawl run. Poll this resource while `active` is `true`.
`status: completed` is set before post-processing ends: wait for
`stage_plan.settled` to be `true` before reading reports.


- Required scopes: `crawls:read`
- Rate-limit cost: 1 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)

### Responses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "project_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "campaign_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "status": "queued",
        "stage": "string",
        "active": true,
        "stage_plan": {
            "current_step": 42,
            "step_count": 42,
            "settled": true,
            "steps": [
                {
                    "stage": "string",
                    "state": "pending",
                    "skip_reason": "string"
                }
            ]
        },
        "pages": {
            "crawled": 42,
            "discovered": 42,
            "budget": 42
        },
        "issue_counts": {
            "error": 42,
            "warning": 42,
            "info": 42
        },
        "truncated": true,
        "blocked": true,
        "blocked_reason": "string",
        "comparable": true,
        "error": "string",
        "trigger": "manual",
        "started_at": "2026-10-01T09:30:00Z",
        "finished_at": "2026-10-01T09:30:00Z",
        "created_at": "2026-10-01T09:30:00Z"
    }
}
```

## List issue occurrences

`GET /v1/crawls/{crawl}/issues`

One item per occurrence (an issue on a URL), in a stable order. Findings
that describe a firewall or interstitial page rather than the site are
excluded; the summary reports their number separately.


- Required scopes: `crawls:read`
- Rate-limit cost: 1 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)
- `cursor` (query, string, optional): Opaque cursor from `meta.next_cursor`. It is bound to the request that issued it; reusing it with other filters returns `400 pagination.invalid_cursor`.
- `limit` (query, integer, optional): Items per page, 1 to 100 (default 25).
- `severity` (query, string, optional)
- `category` (query, string, optional)
- `name` (query, string, optional): Exact issue name, as returned by the summary.

### Responses

- `200`: OK.
- `400`: Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`).
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/issues" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": [
        {
            "name": "Exemple",
            "severity": "error",
            "category": "string",
            "url": "https://exemple.fr/",
            "details": "string"
        }
    ],
    "meta": {
        "limit": 100,
        "has_more": true,
        "next_cursor": "string"
    }
}
```

## Summarise issues

`GET /v1/crawls/{crawl}/issues/summary`

Issue families with their number of occurrences, and totals by severity.


- Required scopes: `crawls:read`
- Rate-limit cost: 5 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)

### Responses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/issues/summary" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "crawl_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "totals": {
            "error": 42,
            "warning": 42,
            "info": 42
        },
        "protection_occurrences": 42,
        "groups": [
            {
                "name": "Exemple",
                "severity": "error",
                "category": "string",
                "occurrences": 42
            }
        ]
    }
}
```

## List crawled pages

`GET /v1/crawls/{crawl}/pages`

Pages discovered by the crawl, in a stable order.


- Required scopes: `crawls:read`
- Rate-limit cost: 1 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)
- `cursor` (query, string, optional): Opaque cursor from `meta.next_cursor`. It is bound to the request that issued it; reusing it with other filters returns `400 pagination.invalid_cursor`.
- `limit` (query, integer, optional): Items per page, 1 to 100 (default 25).
- `status` (query, string, optional): HTTP status class; `unreachable` means no HTTP response at all.
- `internal` (query, string, optional)

### Responses

- `200`: OK.
- `400`: Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`).
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pages" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": [
        {
            "url": "https://exemple.fr/",
            "status_code": 200,
            "error_type": "string",
            "content_type": "string",
            "is_internal": true,
            "depth": 42,
            "title": "string",
            "meta_description": "string",
            "h1": "string",
            "word_count": 42,
            "canonical_url": "https://exemple.fr/",
            "response_time_ms": 0.5,
            "size_bytes": 42,
            "internal_links": 42,
            "external_links": 42,
            "images": 42,
            "images_without_alt": 42
        }
    ],
    "meta": {
        "limit": 100,
        "has_more": true,
        "next_cursor": "string"
    }
}
```

## Get a page by URL

`GET /v1/crawls/{crawl}/pages/by-url`

A crawled page and its issues, errors first.


- Required scopes: `crawls:read`
- Rate-limit cost: 1 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)
- `url` (query, string, required): Page URL; matched on its normalised form.

### Responses

- `200`: OK.
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pages/by-url?url=https%3A%2F%2Fexemple.fr%2F" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "url": "https://exemple.fr/",
        "status_code": 200,
        "error_type": "string",
        "content_type": "string",
        "is_internal": true,
        "depth": 42,
        "title": "string",
        "meta_description": "string",
        "h1": "string",
        "word_count": 42,
        "canonical_url": "https://exemple.fr/",
        "response_time_ms": 0.5,
        "size_bytes": 42,
        "internal_links": 42,
        "external_links": 42,
        "images": 42,
        "images_without_alt": 42,
        "issues": [
            {
                "name": "Exemple",
                "severity": "error",
                "category": "string",
                "url": "https://exemple.fr/",
                "details": "string"
            }
        ]
    }
}
```

## Read PageSpeed results

`GET /v1/crawls/{crawl}/pagespeed`

Lighthouse performance of the PageSpeed sample (home page and category pages), on
mobile and desktop. A strategy that succeeded on no page is `null`, never `0`; each
average travels with the number of pages it was measured on. `combined` is the mean
of the measured strategies, the one the health score uses. `failed` means every
measurement was refused.


- Required scopes: `crawls:read`
- Rate-limit cost: 1 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)

### Responses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pagespeed" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "crawl_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "status": "ready",
        "average": {
            "mobile": 42,
            "desktop": 42,
            "combined": 42,
            "pages_measured_mobile": 42,
            "pages_measured_desktop": 42,
            "pages_sampled": 42
        },
        "results": [
            {
                "url": "https://exemple.fr/",
                "mobile": {
                    "score": 42,
                    "metrics": {
                        "fcp": 0.5,
                        "lcp": 0.5,
                        "cls": 0.5,
                        "fid": 0.5,
                        "speed_index": 0.5,
                        "tti": 0.5
                    }
                },
                "desktop": {
                    "score": 42,
                    "metrics": {
                        "fcp": 0.5,
                        "lcp": 0.5,
                        "cls": 0.5,
                        "fid": 0.5,
                        "speed_index": 0.5,
                        "tti": 0.5
                    }
                },
                "analysis_date": "string",
                "error": "string"
            }
        ]
    }
}
```

## Read the GEO readiness report

`GET /v1/crawls/{crawl}/geo`

AI search readiness, frozen at the end of the crawl: AI bot access (robots.txt),
`llms.txt`, schema.org inventory, editorial signals and the GEO score with its axes.
Detail sections follow the scoring version that produced them (`score_version`).


- Required scopes: `crawls:read`
- Rate-limit cost: 5 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)

### Responses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/geo" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "crawl_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "status": "ready",
        "score": 42,
        "score_version": 42,
        "score_axes": [
            {
                "key": "string",
                "points": 0.5,
                "max": 42,
                "applicable": true
            }
        ],
        "origin": "string",
        "access": {},
        "llms_txt": {},
        "structured_data": {},
        "editorial": {},
        "hints": [
            {
                "key": "string",
                "priority": "high"
            }
        ]
    }
}
```

## Read the accessibility report

`GET /v1/crawls/{crawl}/accessibility`

Automated accessibility audit of the crawl (WCAG, RGAA rate), frozen at the end of
the crawl, with the pages it was measured on. Requires the campaign accessibility
option; otherwise `unavailable`. Manual checks list what no automated tool can
verify.


- Required scopes: `crawls:read`
- Rate-limit cost: 5 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)

### Responses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/accessibility" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "crawl_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "status": "ready",
        "score": 42,
        "score_version": 42,
        "standard": "string",
        "origin": "string",
        "rgaa_compliance_rate": 42,
        "pages_analyzed": 42,
        "pages_total": 42,
        "rendered_pages_analyzed": 42,
        "rendered_pages_total": 42,
        "render_mode": "string",
        "is_protected": true,
        "contrast_status": "string",
        "contrast_incomplete_count": 42,
        "axes": [
            {}
        ],
        "conformance": {},
        "rules": [
            {}
        ],
        "by_principle": [],
        "contrast_pairs": [
            {}
        ],
        "hints": [
            {
                "key": "string",
                "priority": "high"
            }
        ],
        "manual_checks": [
            "string"
        ]
    }
}
```

## Read the trust foundation report

`GET /v1/crawls/{crawl}/eeat`

E-E-A-T trust foundation: the score, the axes expected for the declared business
model and sector, and the attribution signals. An axis that does not apply is
`non_applicable` and leaves the denominator; it never counts as a failure.
`previous_score` is `null` when the previous record used another scoring version.


- Required scopes: `crawls:read`
- Rate-limit cost: 5 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)

### Responses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/eeat" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "crawl_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "status": "ready",
        "score": 42,
        "bareme_version": 42,
        "business_model": "string",
        "business_model_source": "string",
        "declared_sector": "string",
        "declared_fulfilment": [
            "string"
        ],
        "merchant_measured": true,
        "ships_refuted": true,
        "contradiction": "string",
        "pages_analyzed": 42,
        "truncated": true,
        "axes": [
            {
                "key": "string",
                "state": "string",
                "points": 0.5,
                "max": 42
            }
        ],
        "attribution": {},
        "trust_pages": {},
        "predicate_ranks": {},
        "citations": {},
        "previous_score": 42,
        "comparable_to_previous": true
    }
}
```

## Read the internal linking report

`GET /v1/crawls/{crawl}/structure`

Internal linking of the crawl: depth, inlinks, link scores (ranks, not grades),
orphan pages, anchors and leaks. Outside the health score. `coverage` says whether
JavaScript was rendered and whether edges were truncated: without it a low link
count reads as a linking defect when it is a measurement limit.


- Required scopes: `crawls:read`
- Rate-limit cost: 10 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)

### Responses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/structure" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "crawl_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "status": "measured",
        "error_reason": "string",
        "pages_measured": 42,
        "coverage": {},
        "totals": {},
        "leaks": {},
        "anchors": {},
        "boilerplate": {},
        "pagerank": {},
        "classification": {},
        "depth": {},
        "inlinks": {},
        "score_depth": {},
        "score_spread": {},
        "top_authority": {},
        "top_content": {},
        "orphans": {},
        "deepest": {},
        "no_content_inlinks": {}
    }
}
```

## Compare with a baseline crawl

`GET /v1/crawls/{crawl}/compare`

Differences with a baseline crawl of the same project: health, pages, issues by
severity, status codes, appeared and resolved issues and pages. Without `baseline`,
the previous comparable crawl is used; a truncated crawl is never a baseline
(`crawl.no_baseline` when none exists).


- Required scopes: `crawls:read`
- Rate-limit cost: 10 unit(s)

### Parameters

- `crawl` (path, string (uuid), required)
- `baseline` (query, string (uuid), optional): The baseline crawl identifier.

### Responses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/compare" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Response example

```json
{
    "data": {
        "crawl_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "baseline_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "summary": {},
        "status_codes": {},
        "issues": {},
        "pages": {}
    }
}
```

## Launch a crawl

`POST /v1/campaigns/{campaign}/crawls`

Queues a crawl of the campaign and answers `202` with the crawl to poll
(`Location`). Consumes one crawl of the plan monthly quota, so an
`Idempotency-Key` is required: a retry after a timeout returns the first
response instead of launching a second crawl. One crawl runs at a time per
team (`409 crawl.already_running`, with the running crawl id).


- Required scopes: `crawls:write`
- Rate-limit cost: 1 unit(s)
- Idempotency-Key required
- Consumes a plan quota

### Parameters

- `campaign` (path, string (uuid), required)

### Responses

- `202`: OK.
- `400`: Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`).
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `409`: The request conflicts with the current state (a crawl already running, an idempotency key in use, a project not ready).
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl -X POST "https://api.nessflow.com/v1/campaigns/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/crawls" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

### Response example

```json
{
    "data": {
        "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "project_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "campaign_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "status": "queued",
        "stage": "string",
        "active": true,
        "stage_plan": {
            "current_step": 42,
            "step_count": 42,
            "settled": true,
            "steps": [
                {
                    "stage": "string",
                    "state": "pending",
                    "skip_reason": "string"
                }
            ]
        },
        "pages": {
            "crawled": 42,
            "discovered": 42,
            "budget": 42
        },
        "issue_counts": {
            "error": 42,
            "warning": 42,
            "info": 42
        },
        "truncated": true,
        "blocked": true,
        "blocked_reason": "string",
        "comparable": true,
        "error": "string",
        "trigger": "manual",
        "started_at": "2026-10-01T09:30:00Z",
        "finished_at": "2026-10-01T09:30:00Z",
        "created_at": "2026-10-01T09:30:00Z"
    }
}
```

## Stop a crawl

`POST /v1/crawls/{crawl}/stop`

Stops a running crawl synchronously. Never refused for billing reasons.


- Required scopes: `crawls:write`
- Rate-limit cost: 1 unit(s)
- Idempotency-Key accepted

### Parameters

- `crawl` (path, string (uuid), required)

### Responses

- `200`: OK.
- `400`: Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`).
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `409`: The request conflicts with the current state (a crawl already running, an idempotency key in use, a project not ready).
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `429`: The plan rate limit is exhausted for one window.

### Request example (cURL)

```bash
curl -X POST "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/stop" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

### Response example

```json
{
    "data": {
        "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "project_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "campaign_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "status": "queued",
        "stage": "string",
        "active": true,
        "stage_plan": {
            "current_step": 42,
            "step_count": 42,
            "settled": true,
            "steps": [
                {
                    "stage": "string",
                    "state": "pending",
                    "skip_reason": "string"
                }
            ]
        },
        "pages": {
            "crawled": 42,
            "discovered": 42,
            "budget": 42
        },
        "issue_counts": {
            "error": 42,
            "warning": 42,
            "info": 42
        },
        "truncated": true,
        "blocked": true,
        "blocked_reason": "string",
        "comparable": true,
        "error": "string",
        "trigger": "manual",
        "started_at": "2026-10-01T09:30:00Z",
        "finished_at": "2026-10-01T09:30:00Z",
        "created_at": "2026-10-01T09:30:00Z"
    }
}
```
