# Crawls

Les crawls exécutés et leurs résultats : anomalies, pages, rapports.

## Lister les crawls d’un projet

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

Les crawls exécutés d’un projet, du plus récent au plus ancien.

- Scopes requis: `crawls:read`
- Coût en débit: 1 unité(s)

### Paramètres

- `project` (path, string (uuid), requis)
- `cursor` (query, string, facultatif): 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, facultatif): Items per page, 1 to 100 (default 25).

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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"
    }
}
```

## Lire un crawl

`GET /v1/crawls/{crawl}`

L’état d’un crawl. Sondez cette ressource tant que `active` vaut `true`. `status: completed` est posé avant la fin du post-traitement : attendez que `stage_plan.settled` vaille `true` avant de lire les rapports.

- Scopes requis: `crawls:read`
- Coût en débit: 1 unité(s)

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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"
    }
}
```

## Lister les occurrences d’anomalies

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

Une ligne par occurrence (une anomalie sur une URL), dans un ordre stable. Les constats qui décrivent une page de pare-feu ou d’interstitiel plutôt que le site sont exclus ; le résumé en donne le nombre à part.

- Scopes requis: `crawls:read`
- Coût en débit: 1 unité(s)

### Paramètres

- `crawl` (path, string (uuid), requis)
- `cursor` (query, string, facultatif): 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, facultatif): Items per page, 1 to 100 (default 25).
- `severity` (query, string, facultatif)
- `category` (query, string, facultatif)
- `name` (query, string, facultatif): Exact issue name, as returned by the summary.

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

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

## Résumer les anomalies

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

Les familles d’anomalies avec leur nombre d’occurrences, et les totaux par gravité.

- Scopes requis: `crawls:read`
- Coût en débit: 5 unité(s)

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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
            }
        ]
    }
}
```

## Lister les pages crawlées

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

Les pages découvertes par le crawl, dans un ordre stable.

- Scopes requis: `crawls:read`
- Coût en débit: 1 unité(s)

### Paramètres

- `crawl` (path, string (uuid), requis)
- `cursor` (query, string, facultatif): 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, facultatif): Items per page, 1 to 100 (default 25).
- `status` (query, string, facultatif): HTTP status class; `unreachable` means no HTTP response at all.
- `internal` (query, string, facultatif)

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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"
    }
}
```

## Lire une page par son URL

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

Une page crawlée et ses anomalies, les erreurs d’abord.

- Scopes requis: `crawls:read`
- Coût en débit: 1 unité(s)

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (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"
```

### Exemple de réponse

```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"
            }
        ]
    }
}
```

## Lire les résultats PageSpeed

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

La performance Lighthouse de l’échantillon PageSpeed (page d’accueil et pages catégorie), sur mobile et sur ordinateur. Une stratégie qui n’a abouti sur aucune page vaut `null`, jamais `0` ; chaque moyenne voyage avec le nombre de pages mesurées. `combined` est la moyenne des stratégies mesurées, celle du score de santé. `failed` signifie que toutes les mesures ont été refusées.

- Scopes requis: `crawls:read`
- Coût en débit: 1 unité(s)

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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"
            }
        ]
    }
}
```

## Lire le rapport de préparation GEO

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

La préparation à la recherche IA, figée à la fin du crawl : accès des robots IA (robots.txt), `llms.txt`, inventaire schema.org, signaux éditoriaux et score GEO avec ses axes. Les sections de détail suivent la version du barème qui les a produites (`score_version`).

- Scopes requis: `crawls:read`
- Coût en débit: 5 unité(s)

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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"
            }
        ]
    }
}
```

## Lire le rapport d’accessibilité

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

L’audit d’accessibilité automatisé du crawl (WCAG, taux RGAA), figé à la fin du crawl, avec les pages sur lesquelles il a été mesuré. Exige l’option accessibilité de la campagne, sinon `unavailable`. Les vérifications manuelles listent ce qu’aucun outil automatique ne peut vérifier.

- Scopes requis: `crawls:read`
- Coût en débit: 5 unité(s)

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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"
        ]
    }
}
```

## Lire le socle de confiance

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

Le socle de confiance E-E-A-T : le score, les axes attendus pour le modèle d’affaires et le secteur déclarés, et les signaux d’attribution. Un axe qui ne s’applique pas vaut `non_applicable` et sort du dénominateur ; il ne compte jamais comme un échec. `previous_score` vaut `null` quand le relevé précédent a été produit par un autre barème.

- Scopes requis: `crawls:read`
- Coût en débit: 5 unité(s)

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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
    }
}
```

## Lire le rapport de maillage interne

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

Le maillage interne du crawl : profondeur, liens entrants, Link Scores (des rangs, pas des notes), pages orphelines, ancres et fuites. Hors score de santé. `coverage` dit si le JavaScript a été rendu et si des arêtes ont été tronquées : sans cela, un faible nombre de liens se lirait comme un défaut de maillage là où il n’y a qu’une limite de mesure.

- Scopes requis: `crawls:read`
- Coût en débit: 10 unité(s)

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

```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": {}
    }
}
```

## Comparer à un crawl de référence

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

Les écarts avec un crawl de référence du même projet : santé, pages, anomalies par gravité, codes HTTP, anomalies et pages apparues ou résolues. Sans `baseline`, le crawl comparable précédent sert de référence ; un crawl tronqué n’en est jamais une (`crawl.no_baseline` quand il n’y en a aucune).

- Scopes requis: `crawls:read`
- Coût en débit: 10 unité(s)

### Paramètres

- `crawl` (path, string (uuid), requis)
- `baseline` (query, string (uuid), facultatif): The baseline crawl identifier.

### Réponses

- `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.

### Exemple de requête (cURL)

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

### Exemple de réponse

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

## Lancer un crawl

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

Met en file un crawl de la campagne et répond `202` avec le crawl à sonder (`Location`). Consomme un crawl du quota mensuel de l’offre, d’où l’`Idempotency-Key` exigée : une relance après un délai dépassé rend la première réponse au lieu de lancer un second crawl. Un seul crawl tourne à la fois par équipe (`409 crawl.already_running`, avec l’identifiant du crawl en cours).

- Scopes requis: `crawls:write`
- Coût en débit: 1 unité(s)
- Idempotency-Key exigée
- Consomme un quota de l’offre

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (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)"
```

### Exemple de réponse

```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"
    }
}
```

## Arrêter un crawl

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

Arrête un crawl en cours, de façon synchrone. Jamais refusé pour une raison de facturation.

- Scopes requis: `crawls:write`
- Coût en débit: 1 unité(s)
- Idempotency-Key acceptée

### Paramètres

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

### Réponses

- `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.

### Exemple de requête (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)"
```

### Exemple de réponse

```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"
    }
}
```
