Skip to content
NessFlow
Menu
    Documentation contents

    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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'projects/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/crawls', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/projects/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/crawls', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/projects/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/crawls",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/issues', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/issues', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/issues",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/issues/summary', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/issues/summary', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/issues/summary",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pages', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pages', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pages",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

    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"

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pages/by-url', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
        'query' => [
            'url' => 'https://exemple.fr/',
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pages/by-url?url=https%3A%2F%2Fexemple.fr%2F', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pages/by-url?url=https%3A%2F%2Fexemple.fr%2F",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pagespeed', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pagespeed', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/pagespeed",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/geo', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/geo', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/geo",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/accessibility', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/accessibility', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/accessibility",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/eeat', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/eeat', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/eeat",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/structure', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/structure', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/structure",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/compare', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/compare', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/compare",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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
    Idempotency-Key required
    Rate-limit cost
    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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('POST', 'campaigns/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/crawls', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
            'Idempotency-Key' => bin2hex(random_bytes(16)),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/campaigns/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/crawls', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
        'Idempotency-Key': crypto.randomUUID(),
      },
    });
    
    const data = await response.json();

    Python

    import os
    import uuid
    
    import requests
    
    response = requests.post(
        "https://api.nessflow.com/v1/campaigns/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/crawls",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

    {
        "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
    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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('POST', 'crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/stop', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
            'Idempotency-Key' => bin2hex(random_bytes(16)),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/stop', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
        'Idempotency-Key': crypto.randomUUID(),
      },
    });
    
    const data = await response.json();

    Python

    import os
    import uuid
    
    import requests
    
    response = requests.post(
        "https://api.nessflow.com/v1/crawls/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/stop",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Response example

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

    API reference Error codes API documentation

    This page in Markdown