Skip to content
Pandus
Contents

API documentation

Reference of the Pandus REST API v1: authentication, limits, errors, pagination, every endpoint with parameters and examples in curl, JavaScript, PHP and Python, and signed webhooks.

Version:
1.1
Base URL:
https://pandus.online/api/v1
Updated:

OpenAPI specification (YAML) What the API is for

The API returns the results of an automated check, not a full audit. It is not legal advice.

Getting started

The Pandus API gives you the automated accessibility checks of the cabinet from your own tools: a deployment script, an agency dashboard, a ticket system. Every answer is an automated check, not a full audit, and not legal advice.

  1. The API is included in the Pro and Agency plans, see pricing.
  2. In the cabinet, open API and webhooks and create a token. Copy it right away: it is shown only once.
  3. Keep the token on your server, for example in the environment variable PANDUS_TOKEN, and send your first request:
curl https://pandus.online/api/v1/account \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

The answer shows your plan, its limits and how much of them is used. From there, a typical integration adds a site (POST /sites), waits for the check (GET /scans/{scan} or the scan.completed webhook) and reads the report as JSON or PDF.

Every file of a check is in the API too: the PDF report, the issue exports (CSV, JSON, GitHub issues, Jira import) and, on Agency plans, the ACR draft as JSON, HTML or PDF. GET /sites/{site}/export returns the file of the site's latest completed check without looking the check up first. PDFs are made synchronously, up to a minute the first time: keep the read timeout of your client at 120 seconds.

All addresses below are relative to the base URL https://pandus.online/api/v1. Requests and answers are JSON in UTF-8 (the PDF, the screenshots and the exports are files); dates are ISO 8601 with a time zone. The machine-readable description of everything on this page is the OpenAPI specification.

Authentication

Send the token in the Authorization header of every request:

Authorization: Bearer 12|pNd4sY…
Accept: application/json
  • A token belongs to the team, not to a person, so it keeps working when someone leaves the team.
  • Abilities. read covers every GET request. write covers adding and deleting sites and starting checks. A read-only token is enough for a dashboard.
  • Revoking. Delete a token in the cabinet and it stops working at once. A session cookie of the cabinet is never accepted by the API.
  • Plan. If the team moves to a plan without the API, its tokens answer 403 until the plan includes the API again; they are not deleted.
  • Keep it secret. Do not put the token in code that runs in a browser or in a public repository. Use one token per integration, so you can revoke one without touching the others.

Limits

  • Requests: 60 a minute per token. The PDF report, the issue exports and the ACR files: 20 a minute.
  • Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining. Above the limit you get 429 with Retry-After (seconds to wait) and X-RateLimit-Reset (Unix time).
  • Plan limits apply as in the cabinet: the number of sites, pages per check, how long checks are kept (history) and how often a check may be started by hand: once an hour per site, never while a check of the site is running. A request over a limit returns 422 with a message.
  • GET /account returns the limits and the usage, including manual_checks_available: the number of sites on which a check by hand can start right now.

Errors

Errors are JSON with a message in English. Validation errors and plan limits (422) add errors with the problems per field:

{
    "message": "The url field is required. (and 1 more error)",
    "errors": {
        "url": ["The url field is required."],
        "report_locale": ["The report locale field is required."]
    }
}
Status Meaning
401 No token, or the token is unknown or deleted.
403 The token lacks the ability, the plan does not include the API or the tool, or the team is blocked.
404 Not found in your team, or older than the plan's history. The body is always {"message": "Not found."}.
409 The check is not completed yet.
422 The request is not valid, or a plan limit stops it.
429 Too many requests: wait Retry-After seconds.
503 The PDF could not be prepared right now: try again in a minute.

Decide by the status code, not by the wording of message: the wording may improve.

Pagination

Lists (GET /sites and GET /sites/{site}/scans) come in pages. per_page sets the size, from 1 to 100 (50 sites or 20 checks by default), page the page number. Every list has three keys:

  • data: the items of this page;
  • links: first, last, prev and next, full URLs or null;
  • meta: current_page, last_page, per_page, total, from, to and path.

To read everything, follow links.next until it is null:

let url = 'https://pandus.online/api/v1/sites?per_page=100';
const sites = [];
while (url) {
    const response = await fetch(url, {
        headers: {
            Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
            Accept: 'application/json',
        },
    });
    const page = await response.json();
    sites.push(...page.data);
    url = page.links.next;
}

Webhooks

Webhooks tell your server when something happens, so you do not have to poll. Add an endpoint in the cabinet under API and webhooks: a public https address and the events it listens to. A team can have up to 5 endpoints. The secret (whsec_…) is shown once, when the endpoint is created.

  • Delivery. A POST with a JSON body and the headers Pandus-Event, Pandus-Delivery and Pandus-Signature. Redirects are not followed; private and local addresses are refused.
  • Answer with any 2xx within 10 seconds. Do the slow work after answering, for example in a queue.
  • Retries. Other answers and timeouts are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. Pandus-Delivery stays the same on every retry: use it to ignore repeats. After 20 deliveries that failed every attempt, the endpoint is switched off; switch it on again in the cabinet.
  • Signature. Pandus-Signature: t=<unix time>,v1=<hex>. v1 is HMAC-SHA256 of the string "<t>.<raw body>" with the endpoint's secret. Compute it over the raw body, before any JSON parsing, compare in constant time and refuse timestamps older than 5 minutes.

The events, their payloads and the signature check in four languages follow.

scan.completed · A check is completed

Sent for every completed check of a site of the team, scheduled or started by hand. A failed check sends no event. data.scan.links has ready-to-use API links to the report, the PDF, every export and the ACR draft.

Headers

NameDescription
Pandus-EventThe event type, the same as type in the body.
Pandus-DeliveryID of this delivery; the same on every retry, so you can ignore repeats.
Pandus-Signaturet=<unix time>,v1=<hex>. v1 is HMAC-SHA256 of "<t>.<raw body>" with the endpoint's secret (whsec_…). Compare it in constant time and refuse old timestamps (for example older than 5 minutes) against replays.

Payload

{
    "id": "evt_01j9z4c3x5v7b9n1m3k5j7h9g2",
    "type": "scan.completed",
    "created_at": "2026-10-14T06:03:42+00:00",
    "data": {
        "scan": {
            "id": "01j9z4a7d2f4g6h8j0k2l4z6x8",
            "site_id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
            "kind": "scheduled",
            "status": "completed",
            "pages_requested": 2000,
            "pages_scanned": 37,
            "issues": 12,
            "previous_issues": 15,
            "by_impact": {
                "critical": 2,
                "serious": 7,
                "moderate": 3
            },
            "error": null,
            "queued_at": "2026-10-14T06:00:02+00:00",
            "started_at": "2026-10-14T06:00:05+00:00",
            "finished_at": "2026-10-14T06:03:41+00:00",
            "links": {
                "self": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8",
                "report": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/report",
                "pdf": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/pdf",
                "exports": {
                    "json": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=json",
                    "csv": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=csv",
                    "github": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=github",
                    "jira": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=jira"
                },
                "acr": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr"
            }
        },
        "site": {
            "id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
            "url": "https://shop.example/",
            "domain": "shop.example",
            "name": "Example Shop",
            "platform": "shopify",
            "stack": [
                "shopify"
            ],
            "report_locale": "de",
            "status": "active",
            "tags": [
                "client-a"
            ],
            "next_scan_at": "2026-10-21T06:00:00+00:00",
            "created_at": "2026-09-02T12:31:08+00:00"
        },
        "new_issues": 2
    }
}

Any 2xx answer within 10 seconds marks the delivery as received. Other answers and timeouts are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours (6 attempts). After 20 deliveries that failed every attempt the endpoint is switched off.

issues.new · A check found new problems

Sent after scan.completed when the check found problems that the previous check did not have (new_issues > 0). Same payload.

Headers

NameDescription
Pandus-EventThe event type, the same as type in the body.
Pandus-DeliveryID of this delivery; the same on every retry, so you can ignore repeats.
Pandus-Signaturet=<unix time>,v1=<hex>. v1 is HMAC-SHA256 of "<t>.<raw body>" with the endpoint's secret (whsec_…). Compare it in constant time and refuse old timestamps (for example older than 5 minutes) against replays.

Payload

{
    "id": "evt_01j9z4c3x5v7b9n1m3k5j7h9g3",
    "type": "issues.new",
    "created_at": "2026-10-14T06:03:42+00:00",
    "data": {
        "scan": {
            "id": "01j9z4a7d2f4g6h8j0k2l4z6x8",
            "site_id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
            "kind": "scheduled",
            "status": "completed",
            "pages_requested": 2000,
            "pages_scanned": 37,
            "issues": 12,
            "previous_issues": 15,
            "by_impact": {
                "critical": 2,
                "serious": 7,
                "moderate": 3
            },
            "error": null,
            "queued_at": "2026-10-14T06:00:02+00:00",
            "started_at": "2026-10-14T06:00:05+00:00",
            "finished_at": "2026-10-14T06:03:41+00:00",
            "links": {
                "self": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8",
                "report": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/report",
                "pdf": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/pdf",
                "exports": {
                    "json": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=json",
                    "csv": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=csv",
                    "github": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=github",
                    "jira": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=jira"
                },
                "acr": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr"
            }
        },
        "site": {
            "id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
            "url": "https://shop.example/",
            "domain": "shop.example",
            "name": "Example Shop",
            "platform": "shopify",
            "stack": [
                "shopify"
            ],
            "report_locale": "de",
            "status": "active",
            "tags": [
                "client-a"
            ],
            "next_scan_at": "2026-10-21T06:00:00+00:00",
            "created_at": "2026-09-02T12:31:08+00:00"
        },
        "new_issues": 2
    }
}

Any 2xx answer within 10 seconds marks the delivery as received. Other answers and timeouts are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours (6 attempts). After 20 deliveries that failed every attempt the endpoint is switched off.

ping · A test event

Sent when you press "Send a test event" next to an endpoint in the cabinet. Every endpoint receives it, whatever events it listens to.

Headers

NameDescription
Pandus-EventThe event type, the same as type in the body.
Pandus-DeliveryID of this delivery; the same on every retry, so you can ignore repeats.
Pandus-Signaturet=<unix time>,v1=<hex>. v1 is HMAC-SHA256 of "<t>.<raw body>" with the endpoint's secret (whsec_…). Compare it in constant time and refuse old timestamps (for example older than 5 minutes) against replays.

Payload

{
    "id": "evt_01j9z5d9k1l3m5n7b9v1c3x5z7",
    "type": "ping",
    "created_at": "2026-10-14T09:20:11+00:00",
    "data": {
        "message": "Test event from Pandus."
    }
}

Any 2xx answer within 10 seconds marks the delivery as received. Other answers and timeouts are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours (6 attempts). After 20 deliveries that failed every attempt the endpoint is switched off.

Verify the signature

Compute HMAC-SHA256 of the timestamp, a dot and the raw body with the endpoint's secret, compare it with v1 in constant time and refuse timestamps older than five minutes.

Shell

# SIGNATURE: the Pandus-Signature header; body.json: the raw request body.
t=$(printf '%s' "$SIGNATURE" | sed -E 's/.*t=([0-9]+).*/\1/')
v1=$(printf '%s' "$SIGNATURE" | sed -E 's/.*v1=([0-9a-f]+).*/\1/')
expected=$({ printf '%s.' "$t"; cat body.json; } \
  | openssl dgst -sha256 -hmac "$PANDUS_WEBHOOK_SECRET" -r | cut -d' ' -f1)
[ "$expected" = "$v1" ] && echo "valid" || echo "invalid"

JavaScript

import crypto from 'node:crypto';
import express from 'express';

const app = express();

// The signature covers the raw body: read it before any JSON parser does.
app.post('/pandus/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const parts = Object.fromEntries(
        (req.get('Pandus-Signature') ?? '').split(',').map((part) => part.split('=')),
    );
    const expected = crypto
        .createHmac('sha256', process.env.PANDUS_WEBHOOK_SECRET)
        .update(`${parts.t}.${req.body}`)
        .digest('hex');
    const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
    const valid =
        fresh &&
        typeof parts.v1 === 'string' &&
        parts.v1.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
    if (!valid) return res.sendStatus(400);

    const event = JSON.parse(req.body);
    // event.type: "scan.completed", "issues.new" or "ping"
    res.sendStatus(204);
});

PHP

// routes/api.php: no CSRF token on this route.
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post('pandus/webhook', function (Request $request) {
    parse_str(str_replace(',', '&', (string) $request->header('Pandus-Signature')), $parts);
    $expected = hash_hmac('sha256', ($parts['t'] ?? '').'.'.$request->getContent(), config('services.pandus.webhook_secret'));

    abort_unless(
        isset($parts['t'], $parts['v1'])
        && abs(time() - (int) $parts['t']) < 300
        && hash_equals($expected, (string) $parts['v1']),
        400,
    );

    $event = $request->json()->all();
    // $event['type']: scan.completed, issues.new or ping

    return response()->noContent();
});

Python

import hashlib
import hmac
import os
import time

from flask import Flask, abort, request

app = Flask(__name__)


@app.post("/pandus/webhook")
def pandus_webhook():
    header = request.headers.get("Pandus-Signature", "")
    parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
    body = request.get_data()  # the raw body, before any parsing
    secret = os.environ["PANDUS_WEBHOOK_SECRET"].encode()
    signed = parts.get("t", "").encode() + b"." + body
    expected = hmac.new(secret, signed, hashlib.sha256).hexdigest()
    fresh = parts.get("t", "").isdigit() and abs(time.time() - int(parts["t"])) < 300
    if not fresh or not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(400)

    event = request.get_json()
    # event["type"]: "scan.completed", "issues.new" or "ping"
    return "", 204

Account

The team the token belongs to, its plan, limits and usage.

Plan, limits and usage of the team

GET /account Token ability: read

The team the token belongs to with its plan, what the plan includes and how much of it is used. Check usage before you add a site or start a check.

Response

200 The team, plan, limits and usage.

application/json

{
    "data": {
        "team": {
            "id": "01j9z3k8m4q2w7e5r6t8y0u1i2",
            "name": "Nova Studio"
        },
        "plan": {
            "code": "agency_10",
            "name": "Agency 10",
            "status": "active",
            "ends_at": "2026-11-14T09:00:00+00:00",
            "trial_ends_at": null
        },
        "features": {
            "api": true,
            "exports": true,
            "white_label": true,
            "statement_drafts": true,
            "acr_drafts": true
        },
        "limits": {
            "sites": 10,
            "pages_per_check": 2000,
            "check_frequency": "weekly",
            "history_months": 24,
            "seats": 5,
            "manual_check_interval": "PT1H",
            "requests_per_minute": 60
        },
        "usage": {
            "sites": {
                "used": 4,
                "allowed": 10
            },
            "seats": {
                "used": 2,
                "allowed": 5
            },
            "manual_checks_available": 3
        }
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/account" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/account', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->get('https://pandus.online/api/v1/account')
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/account",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Sites

Sites of the team. Adding one starts its first check.

List the team's sites

GET /sites Token ability: read

Sites in alphabetical order of their domain, 50 a page by default.

Parameters

NameInTypeDescription
tag query string Only sites with this tag, e.g. the name of an agency's client.
per_page query integer (1–100) Items per page, 1 to 100. Default: 50
page query integer Page number, from 1. Default: 1

Response

200 One page of sites.

application/json

{
    "data": [
        {
            "id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
            "url": "https://shop.example/",
            "domain": "shop.example",
            "name": "Example Shop",
            "platform": "shopify",
            "stack": [
                "shopify"
            ],
            "report_locale": "de",
            "status": "active",
            "tags": [
                "client-a"
            ],
            "next_scan_at": "2026-10-21T06:00:00+00:00",
            "created_at": "2026-09-02T12:31:08+00:00"
        }
    ],
    "links": {
        "first": "https://pandus.online/api/v1/sites?page=1",
        "last": "https://pandus.online/api/v1/sites?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "page": null,
                "active": false
            },
            {
                "url": "https://pandus.online/api/v1/sites?page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": null,
                "label": "Next &raquo;",
                "page": null,
                "active": false
            }
        ],
        "path": "https://pandus.online/api/v1/sites",
        "per_page": 50,
        "to": 1,
        "total": 1
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/sites?tag=client-a" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/sites?tag=client-a', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->get('https://pandus.online/api/v1/sites', [
        'tag' => 'client-a',
    ])
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/sites",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    params={
        "tag": "client-a",
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Add a site

POST /sites Token ability: write

Adds a site to the team and queues its first check, unless scan is false. The address must be public: private networks, local addresses and blocked domains are refused with 422. The number of sites is limited by the plan.

Request body application/json

FieldTypeDescription
url(required) string (max. 500) The address; https:// is added when left out.
name string | null (max. 120)
report_locale(required) string Values: en, uk, de, pl, cs, ro, ru
platform string | null One platform key (older form of stack).
stack array of string (max. 8) Platform keys, e.g. shopify, woocommerce, wordpress, laravel, vue. Fixing tips follow the first shop platform.
tags array of string (max. 40) (max. 10)
scan boolean Start the first check right away. Default: true

Example

{
    "url": "shop.example",
    "name": "Example Shop",
    "report_locale": "de",
    "stack": [
        "shopify"
    ],
    "tags": [
        "client-a"
    ],
    "scan": true
}

Response

201 The new site.

application/json

{
    "data": {
        "id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
        "url": "https://shop.example",
        "domain": "shop.example",
        "name": "Example Shop",
        "platform": "shopify",
        "stack": [
            "shopify"
        ],
        "report_locale": "de",
        "status": "active",
        "tags": [
            "client-a"
        ],
        "next_scan_at": "2026-10-21T06:00:00+00:00",
        "created_at": "2026-10-14T09:12:44+00:00"
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
422 The request is not valid or a plan limit stops it. The url field is required. (and 1 more error)
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl -X POST "https://pandus.online/api/v1/sites" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "shop.example",
  "name": "Example Shop",
  "report_locale": "de",
  "stack": [
    "shopify"
  ],
  "tags": [
    "client-a"
  ],
  "scan": true
}'

JavaScript

const response = await fetch('https://pandus.online/api/v1/sites', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        url: 'shop.example',
        name: 'Example Shop',
        report_locale: 'de',
        stack: ['shopify'],
        tags: ['client-a'],
        scan: true,
    }),
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->post('https://pandus.online/api/v1/sites', [
        'url' => 'shop.example',
        'name' => 'Example Shop',
        'report_locale' => 'de',
        'stack' => ['shopify'],
        'tags' => ['client-a'],
        'scan' => true,
    ])
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.post(
    "https://pandus.online/api/v1/sites",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    json={
        "url": "shop.example",
        "name": "Example Shop",
        "report_locale": "de",
        "stack": ["shopify"],
        "tags": ["client-a"],
        "scan": True,
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

One site

GET /sites/{site} Token ability: read

Parameters

NameInTypeDescription
site(required) path string The site's id.

Response

200 The site.

application/json

{
    "data": {
        "id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
        "url": "https://shop.example/",
        "domain": "shop.example",
        "name": "Example Shop",
        "platform": "shopify",
        "stack": [
            "shopify"
        ],
        "report_locale": "de",
        "status": "active",
        "tags": [
            "client-a"
        ],
        "next_scan_at": "2026-10-21T06:00:00+00:00",
        "created_at": "2026-09-02T12:31:08+00:00"
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->get('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1')
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Delete a site

DELETE /sites/{site} Token ability: write

Deletes the site with all its checks, reports and screenshots. This cannot be undone.

Parameters

NameInTypeDescription
site(required) path string The site's id.

Response

204 Deleted; no body.

Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl -X DELETE "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1', {
    method: 'DELETE',
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
// 204 No Content

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->delete('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1')
    ->throw();

// 204 No Content

Python

import os

import requests

response = requests.delete(
    "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
# 204 No Content

Checks

Start a check and follow its status.

Checks of a site

GET /sites/{site}/scans Token ability: read

Checks within the plan's history, newest first, 20 a page by default. The latest completed check of a site stays available even when it is older than the history.

Parameters

NameInTypeDescription
site(required) path string The site's id.
per_page query integer (1–100) Items per page, 1 to 100. Default: 20
page query integer Page number, from 1. Default: 1

Response

200 One page of checks.

application/json

{
    "data": [
        {
            "id": "01j9z4a7d2f4g6h8j0k2l4z6x8",
            "site_id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
            "kind": "scheduled",
            "status": "completed",
            "pages_requested": 2000,
            "pages_scanned": 37,
            "issues": 12,
            "previous_issues": 15,
            "by_impact": {
                "critical": 2,
                "serious": 7,
                "moderate": 3
            },
            "error": null,
            "queued_at": "2026-10-14T06:00:02+00:00",
            "started_at": "2026-10-14T06:00:05+00:00",
            "finished_at": "2026-10-14T06:03:41+00:00",
            "links": {
                "self": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8",
                "report": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/report",
                "pdf": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/pdf",
                "exports": {
                    "json": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=json",
                    "csv": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=csv",
                    "github": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=github",
                    "jira": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=jira"
                },
                "acr": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr"
            }
        }
    ],
    "links": {
        "first": "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans?page=1",
        "last": "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans?page=3",
        "prev": null,
        "next": "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 3,
        "links": [],
        "path": "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans",
        "per_page": 1,
        "to": 1,
        "total": 3
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->get('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans')
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Start a check now

POST /sites/{site}/scans Token ability: write

Queues a check of the site with the plan's number of pages. One check by hand per site an hour (a month on Free), never while a check of the site is running: both are answered with 422. Follow the status with "Status of a check" or a webhook.

Parameters

NameInTypeDescription
site(required) path string The site's id.

Response

202 The check is queued.

application/json

{
    "data": {
        "id": "01j9z5c1v3b5n7m9q1w3e5r7t9",
        "site_id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
        "kind": "manual",
        "status": "queued",
        "pages_requested": 2000,
        "pages_scanned": 0,
        "issues": null,
        "previous_issues": null,
        "by_impact": null,
        "error": null,
        "queued_at": "2026-10-14T09:15:00+00:00",
        "started_at": null,
        "finished_at": null,
        "links": {
            "self": "https://pandus.online/api/v1/scans/01j9z5c1v3b5n7m9q1w3e5r7t9",
            "report": null,
            "pdf": null,
            "exports": null,
            "acr": null
        }
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
422 A check of the site is running, or one was started by hand too recently. You started a check of this site less than an hour ago.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl -X POST "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->post('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans')
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.post(
    "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/scans",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Status of a check

GET /scans/{scan} Token ability: read

The status (queued, running, completed, failed), the number of pages and problems.

Parameters

NameInTypeDescription
scan(required) path string The check's id.

Response

200 The check.

application/json

{
    "data": {
        "id": "01j9z4a7d2f4g6h8j0k2l4z6x8",
        "site_id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
        "kind": "scheduled",
        "status": "completed",
        "pages_requested": 2000,
        "pages_scanned": 37,
        "issues": 12,
        "previous_issues": 15,
        "by_impact": {
            "critical": 2,
            "serious": 7,
            "moderate": 3
        },
        "error": null,
        "queued_at": "2026-10-14T06:00:02+00:00",
        "started_at": "2026-10-14T06:00:05+00:00",
        "finished_at": "2026-10-14T06:03:41+00:00",
        "links": {
            "self": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8",
            "report": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/report",
            "pdf": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/pdf",
            "exports": {
                "json": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=json",
                "csv": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=csv",
                "github": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=github",
                "jira": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=jira"
            },
            "acr": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr"
        }
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->get('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8')
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Reports

Results of a completed check, as JSON, PDF, files for developers or a comparison.

Export the latest check of a site

GET /sites/{site}/export Token ability: read

The same files as "Export the issues for developers", for the latest completed check of the site (the one its report shows), so you do not have to look up the check first. The Pandus-Scan header names the check the file is from. format=pdf is the PDF report (every plan with the API); the other formats come with paid plans. The PDF is prepared synchronously on the first request for a language (up to a minute, then kept); keep the read timeout of your client at 120 seconds. 20 requests a minute.

Parameters

NameInTypeDescription
site(required) path string The site's id.
format query string json (default) a JSON document; csv one row per element (UTF-8 with BOM); github Markdown with one GitHub issue per rule; jira a CSV for Jira's importer with one issue per rule; pdf the PDF report for the site's owner. GitHub and Jira files list problems (WCAG A/AA) only; CSV and JSON add the recommendations. Values: json, csv, github, jira, pdf Default: json
lang query string Report language; the site's report language when left out. Values: en, uk, de, pl, cs, ro, ru

Response

200 The file as an attachment: Content-Disposition carries the file name, Pandus-Scan the check it is from. JSON, CSV (UTF-8 with BOM), Markdown or PDF by format.

application/json

{
    "format": "accessibility-issues/1",
    "disclaimer": "Automated check, not a full audit. Not legal advice.",
    "generated_by": "Nova Studio",
    "site": {
        "name": "Example Shop",
        "url": "https://shop.example/"
    },
    "checked_at": "2026-10-14T06:03:41+00:00",
    "pages_checked": 37,
    "standard": "WCAG 2.2 A/AA, EN 301 549",
    "engine": {
        "axe": "4.10.0"
    },
    "language": "en",
    "rules": [
        {
            "rule": "button-name",
            "type": "problem",
            "severity": "critical",
            "title": "Some buttons have no name",
            "summary": "Some buttons have no readable label, for example an icon-only \"add to cart\", search or menu button.",
            "how_to_fix": "Give every button a name: visible text, aria-label, or aria-labelledby pointing to visible text.",
            "platform_tip": "Usually icon buttons in the theme or buttons injected by apps.",
            "wcag": [
                "4.1.2 Name, Role, Value (A)"
            ],
            "en301549": [
                "9.4.1.2"
            ],
            "count": 1,
            "pages": 1,
            "issues": [
                {
                    "page": "https://shop.example/cart",
                    "selector": ".cart-icon",
                    "html": "<button class=\"cart-icon\"></button>",
                    "failure_summary": "Fix any of the following: Element does not have inner text that is visible to screen readers",
                    "is_new": false
                }
            ]
        }
    ]
}

text/csv

rule,type,severity,wcag,title,page_url,selector,html,failure_summary,new_since_previous_check
button-name,problem,critical,"4.1.2 Name, Role, Value (A)","Some buttons have no name",https://shop.example/cart,.cart-icon,"<button class=""cart-icon""></button>","Fix any of the following",no

text/markdown

# Accessibility issues of Example Shop

> Automated check, not a full audit. Not legal advice.

## [critical] Some buttons have no name (button-name)

- [ ] https://shop.example/cart — `.cart-icon`

Labels: `accessibility`, `a11y:critical`, `axe:button-name`

application/pdf

A file; no example shown.

Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 Without the ability read, without the API in the plan, or without exports in the plan (CSV, JSON, GitHub and Jira files; the PDF is in every plan with the API). Issue exports are not included in the current plan of this team. / Invalid ability provided.
404 The site is not in this team, or it has no completed check yet. The site has no completed check yet. / Not found.
422 The request is not valid or a plan limit stops it. The url field is required. (and 1 more error)
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.
503 The PDF could not be prepared right now; try again after Retry-After seconds. The PDF could not be prepared right now. Please try again in a minute.

Code samples

curl

curl "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/export?format=pdf&lang=de" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  --fail -o report.pdf

JavaScript

import { writeFile } from 'node:fs/promises';

const response = await fetch('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/export?format=pdf&lang=de', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
await writeFile('report.pdf', Buffer.from(await response.arrayBuffer()));

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->get('https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/export', [
        'format' => 'pdf',
        'lang' => 'de',
    ])
    ->throw();

file_put_contents('report.pdf', $response->body());

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/sites/01j9z3m2c5v7b9n1m3k5j7h9g1/export",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}"},
    params={
        "format": "pdf",
        "lang": "de",
    },
    timeout=30,
)
response.raise_for_status()
with open("report.pdf", "wb") as file:
    file.write(response.content)

The report as JSON

GET /scans/{scan}/report Token ability: read

The owner report of a completed check, the same content as in the cabinet: problem groups with plain-language explanations and fixing tips, examples, affected pages, totals by severity, the comparison with the previous check and the trend. Recommendations (AAA, best practice) are under advisory. Screenshot URLs need the same token.

Parameters

NameInTypeDescription
scan(required) path string The check's id.
lang query string Report language; the site's report language when left out. Values: en, uk, de, pl, cs, ro, ru

Response

200 The report.

application/json

{
    "data": {
        "scan_id": "01j9z4a7d2f4g6h8j0k2l4z6x8",
        "locale": "en",
        "url": "https://shop.example/",
        "title": "Example Shop",
        "platform": "shopify",
        "platform_name": "Shopify",
        "checked_at": "2026-10-14T06:03:41.000000Z",
        "screen": null,
        "totals": {
            "critical": 1,
            "serious": 0,
            "moderate": 0,
            "minor": 0
        },
        "total": 1,
        "groups": [
            {
                "rule": "button-name",
                "severity": "critical",
                "rank": 0,
                "count": 1,
                "new_count": 0,
                "title": "Some buttons have no name",
                "summary": "Some buttons have no readable label, for example an icon-only \"add to cart\", search or menu button.",
                "impact": "Screen reader and voice control users cannot tell which button adds to cart, opens the menu or submits the search.",
                "who": [
                    "blind customers",
                    "screen reader users",
                    "voice control users"
                ],
                "how_to_fix": "Give every button a name: visible text, aria-label, or aria-labelledby pointing to visible text.",
                "platform_tip": "Usually icon buttons in the theme or buttons injected by apps. Add labels in the theme's Liquid snippets.",
                "machine": false,
                "has_card": true,
                "help": "Buttons must have discernible text",
                "refs": [
                    "WCAG 2.2 4.1.2 (A) Name, Role, Value",
                    "EN 301 549 9.4.1.2"
                ],
                "pages": [
                    {
                        "url": "https://shop.example/cart",
                        "count": 1
                    }
                ],
                "examples": [
                    {
                        "id": 4182,
                        "page": "https://shop.example/cart",
                        "target": ".cart-icon",
                        "html": "<button class=\"cart-icon\"></button>",
                        "details": null,
                        "is_new": false,
                        "shot": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/evidence/issue/4182",
                        "shot_width": 1280,
                        "shot_height": 720
                    }
                ],
                "boxes": [],
                "n": 1
            }
        ],
        "advisory": [],
        "pages": [
            {
                "url": "https://shop.example/cart",
                "title": "Cart",
                "issues": 1,
                "error": null
            }
        ],
        "diff": {
            "previous_id": "01j9yq3r5t7y9u1i3o5p7a9s1d",
            "previous_date": "2026-10-07T06:02:57+00:00",
            "previous_total": 2,
            "new": 0,
            "fixed": 1,
            "remaining": 1,
            "fixed_rules": [
                {
                    "title": "Images have no text description",
                    "count": 1
                }
            ]
        },
        "trend": [
            {
                "id": "01j9yq3r5t7y9u1i3o5p7a9s1d",
                "date": "2026-10-07T06:02:57+00:00",
                "total": 2
            },
            {
                "id": "01j9z4a7d2f4g6h8j0k2l4z6x8",
                "date": "2026-10-14T06:03:41+00:00",
                "total": 1
            }
        ]
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
409 The check is still queued or running, or it failed. The scan is not completed yet.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/report" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/report', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->get('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/report')
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/report",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

The report as PDF

GET /scans/{scan}/pdf Token ability: read

The PDF report of a completed check, under the team's brand on plans with white-label. Synchronous: the first request for a language prepares the file (up to a minute; keep the read timeout of your client at 120 seconds) and the next ones get the stored file. If the renderer is busy or down the answer is 503 with Retry-After. The same file as GET /scans/{scan}/export?format=pdf. 20 requests a minute.

Parameters

NameInTypeDescription
scan(required) path string The check's id.
lang query string Report language; the site's report language when left out. Values: en, uk, de, pl, cs, ro, ru

Response

200 The PDF file (Content-Disposition attachment).

application/pdf

A file; no example shown.

Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
409 The check is still queued or running, or it failed. The scan is not completed yet.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.
503 The PDF could not be prepared right now; try again after Retry-After seconds. The PDF could not be prepared right now. Please try again in a minute.

Code samples

curl

curl "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/pdf?lang=de" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  --fail -o report.pdf

JavaScript

import { writeFile } from 'node:fs/promises';

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/pdf?lang=de', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
await writeFile('report.pdf', Buffer.from(await response.arrayBuffer()));

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->get('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/pdf', [
        'lang' => 'de',
    ])
    ->throw();

file_put_contents('report.pdf', $response->body());

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/pdf",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}"},
    params={
        "lang": "de",
    },
    timeout=30,
)
response.raise_for_status()
with open("report.pdf", "wb") as file:
    file.write(response.content)

Export the issues for developers

GET /scans/{scan}/export Token ability: read

The issues of a completed check as a file for developers and ticket systems, the same files as the export menus in the cabinet. Included in paid plans (and for the check an express report was bought for). format=pdf returns the PDF report instead, as "The report as PDF" (every plan with the API). 20 requests a minute.

Parameters

NameInTypeDescription
scan(required) path string The check's id.
format query string json (default) a JSON document; csv one row per element (UTF-8 with BOM); github Markdown with one GitHub issue per rule; jira a CSV for Jira's importer with one issue per rule; pdf the PDF report for the site's owner. GitHub and Jira files list problems (WCAG A/AA) only; CSV and JSON add the recommendations. Values: json, csv, github, jira, pdf Default: json
lang query string Report language; the site's report language when left out. Values: en, uk, de, pl, cs, ro, ru

Response

200 The file as an attachment: Content-Disposition carries the file name, Pandus-Scan the check it is from. JSON, CSV (UTF-8 with BOM), Markdown or PDF by format.

application/json

{
    "format": "accessibility-issues/1",
    "disclaimer": "Automated check, not a full audit. Not legal advice.",
    "generated_by": "Nova Studio",
    "site": {
        "name": "Example Shop",
        "url": "https://shop.example/"
    },
    "checked_at": "2026-10-14T06:03:41+00:00",
    "pages_checked": 37,
    "standard": "WCAG 2.2 A/AA, EN 301 549",
    "engine": {
        "axe": "4.10.0"
    },
    "language": "en",
    "rules": [
        {
            "rule": "button-name",
            "type": "problem",
            "severity": "critical",
            "title": "Some buttons have no name",
            "summary": "Some buttons have no readable label, for example an icon-only \"add to cart\", search or menu button.",
            "how_to_fix": "Give every button a name: visible text, aria-label, or aria-labelledby pointing to visible text.",
            "platform_tip": "Usually icon buttons in the theme or buttons injected by apps.",
            "wcag": [
                "4.1.2 Name, Role, Value (A)"
            ],
            "en301549": [
                "9.4.1.2"
            ],
            "count": 1,
            "pages": 1,
            "issues": [
                {
                    "page": "https://shop.example/cart",
                    "selector": ".cart-icon",
                    "html": "<button class=\"cart-icon\"></button>",
                    "failure_summary": "Fix any of the following: Element does not have inner text that is visible to screen readers",
                    "is_new": false
                }
            ]
        }
    ]
}

text/csv

rule,type,severity,wcag,title,page_url,selector,html,failure_summary,new_since_previous_check
button-name,problem,critical,"4.1.2 Name, Role, Value (A)","Some buttons have no name",https://shop.example/cart,.cart-icon,"<button class=""cart-icon""></button>","Fix any of the following",no

text/markdown

# Accessibility issues of Example Shop

> Automated check, not a full audit. Not legal advice.

## [critical] Some buttons have no name (button-name)

- [ ] https://shop.example/cart — `.cart-icon`

Labels: `accessibility`, `a11y:critical`, `axe:button-name`

application/pdf

A file; no example shown.

Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 Without the ability read, without the API in the plan, or without exports in the plan (CSV, JSON, GitHub and Jira files; the PDF is in every plan with the API). Issue exports are not included in the current plan of this team. / Invalid ability provided.
404 Not found in this team, or older than the plan's history. Not found.
409 The check is still queued or running, or it failed. The scan is not completed yet.
422 The request is not valid or a plan limit stops it. The url field is required. (and 1 more error)
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.
503 The PDF could not be prepared right now; try again after Retry-After seconds. The PDF could not be prepared right now. Please try again in a minute.

Code samples

curl

curl "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=csv" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  --fail -o issues.csv

JavaScript

import { writeFile } from 'node:fs/promises';

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export?format=csv', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
await writeFile('issues.csv', Buffer.from(await response.arrayBuffer()));

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->get('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export', [
        'format' => 'csv',
    ])
    ->throw();

file_put_contents('issues.csv', $response->body());

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/export",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}"},
    params={
        "format": "csv",
    },
    timeout=30,
)
response.raise_for_status()
with open("issues.csv", "wb") as file:
    file.write(response.content)

Compare two checks

GET /scans/{scan}/compare/{other} Token ability: read

Before and after between two completed checks of one site, per rule: which problems (WCAG A/AA) were fixed, which are new and which are still open. Problems are matched by rule, page and element. Up to 25 elements are listed per rule and state; count has the full number.

Parameters

NameInTypeDescription
scan(required) path string The check's id.
other(required) path string ID of another completed check of the same site. The older of the two is "before".
lang query string Report language; the site's report language when left out. Values: en, uk, de, pl, cs, ro, ru

Response

200 The comparison.

application/json

{
    "data": {
        "site_id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
        "locale": "en",
        "before": {
            "id": "01j9yq3r5t7y9u1i3o5p7a9s1d",
            "date": "2026-10-07T06:02:57+00:00",
            "issues": 3
        },
        "after": {
            "id": "01j9z4a7d2f4g6h8j0k2l4z6x8",
            "date": "2026-10-14T06:03:41+00:00",
            "issues": 2
        },
        "totals": {
            "fixed": 2,
            "new": 1,
            "open": 1,
            "before": 3,
            "after": 2
        },
        "rules": [
            {
                "rule": "button-name",
                "title": "Some buttons have no name",
                "severity": "critical",
                "fixed": {
                    "count": 1,
                    "items": [
                        {
                            "page": "https://shop.example/cart",
                            "selector": ".wishlist"
                        }
                    ]
                },
                "new": {
                    "count": 0,
                    "items": []
                },
                "open": {
                    "count": 1,
                    "items": [
                        {
                            "page": "https://shop.example/cart",
                            "selector": ".cart-icon"
                        }
                    ]
                }
            },
            {
                "rule": "color-contrast",
                "title": "Text is hard to read because of low contrast",
                "severity": "serious",
                "fixed": {
                    "count": 0,
                    "items": []
                },
                "new": {
                    "count": 1,
                    "items": [
                        {
                            "page": "https://shop.example/cart",
                            "selector": ".price"
                        }
                    ]
                },
                "open": {
                    "count": 0,
                    "items": []
                }
            }
        ]
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
409 The check is still queued or running, or it failed. The scan is not completed yet.
422 The checks belong to different sites, or it is the same check twice. Both checks must belong to the same site.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/compare/01j9yq3r5t7y9u1i3o5p7a9s1d" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/compare/01j9yq3r5t7y9u1i3o5p7a9s1d', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->get('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/compare/01j9yq3r5t7y9u1i3o5p7a9s1d')
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/compare/01j9yq3r5t7y9u1i3o5p7a9s1d",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

A screenshot from the report

GET /scans/{scan}/evidence/{kind}/{id} Token ability: read

The JPEG behind a shot or screen.url link of the JSON report.

Parameters

NameInTypeDescription
scan(required) path string The check's id.
kind(required) path string page for a page screenshot, issue for the screenshot of one element. Values: page, issue
id(required) path integer The number from the report (examples[].id for an element).

Response

200 The screenshot.

image/jpeg

A file; no example shown.

Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 The token lacks the ability for this request, the team's plan does not include the API, or the team is blocked. Invalid ability provided. / The API is not included in the current plan of this team.
404 Not found in this team, or older than the plan's history. Not found.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/evidence/issue/4182" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  --fail -o screenshot.jpg

JavaScript

import { writeFile } from 'node:fs/promises';

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/evidence/issue/4182', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
await writeFile('screenshot.jpg', Buffer.from(await response.arrayBuffer()));

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->get('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/evidence/issue/4182')
    ->throw();

file_put_contents('screenshot.jpg', $response->body());

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/evidence/issue/4182",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}"},
    timeout=30,
)
response.raise_for_status()
with open("screenshot.jpg", "wb") as file:
    file.write(response.content)

The ACR draft as JSON

GET /scans/{scan}/acr Token ability: read

Draft of an Accessibility Conformance Report (VPAT 2.x terms) from a completed check: one entry per WCAG 2.2 A/AA criterion with the automated result and what a person entered. Agency plans. Only automated comes from the check (fails, no_failures, not_testable); conformance and remarks are only ever what a person sent, and "no automated failures" never becomes "supports". It is a draft, not a VPAT/ACR, until a person has evaluated every criterion (notice).

Parameters

NameInTypeDescription
scan(required) path string The check's id.
lang query string Report language; the site's report language when left out. Values: en, uk, de, pl, cs, ro, ru

Response

200 The draft.

application/json

{
    "data": {
        "scan_id": "01j9z4a7d2f4g6h8j0k2l4z6x8",
        "site_id": "01j9z3m2c5v7b9n1m3k5j7h9g1",
        "locale": "en",
        "notice": "Draft based on an automated check. Not a VPAT/ACR until a person has evaluated every criterion. Not legal advice.",
        "generated_by": "Nova Studio",
        "site": {
            "name": "Example Shop",
            "url": "https://shop.example/"
        },
        "checked_at": "2026-10-14T06:03:41+00:00",
        "pages_checked": 37,
        "standard": "WCAG 2.2 A/AA",
        "engine": {
            "axe": "4.10.0"
        },
        "counts": {
            "fails": 6,
            "no_failures": 17,
            "not_testable": 32
        },
        "filled": 1,
        "levels": [
            "supports",
            "partially_supports",
            "does_not_support",
            "not_applicable",
            "not_evaluated"
        ],
        "criteria": [
            {
                "criterion": "1.1.1",
                "name": "Non-text Content",
                "level": "A",
                "url": "https://www.w3.org/WAI/WCAG22/Understanding/non-text-content",
                "automated": "fails",
                "failures": [
                    {
                        "rule": "image-alt",
                        "title": "Images without a text alternative",
                        "count": 4,
                        "pages": 2
                    }
                ],
                "conformance": "partially_supports",
                "remarks": "Product photos in the gallery have no alt text; fixed in the next release."
            },
            {
                "criterion": "1.2.1",
                "name": "Audio-only and Video-only (Prerecorded)",
                "level": "A",
                "url": "https://www.w3.org/WAI/WCAG22/Understanding/audio-only-and-video-only-prerecorded",
                "automated": "not_testable",
                "failures": [],
                "conformance": null,
                "remarks": null
            }
        ],
        "unmapped": [],
        "links": {
            "html": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/export?format=html&lang=en",
            "pdf": "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/export?format=pdf&lang=en"
        }
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 Without the ability the request needs, without the API in the plan, or without ACR drafts in the plan (Agency plans). ACR drafts are not included in the current plan of this team. / Invalid ability provided.
404 Not found in this team, or older than the plan's history. Not found.
409 The check is still queued or running, or it failed. The scan is not completed yet.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json"

JavaScript

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->get('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr')
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Fill in one ACR criterion

PUT /scans/{scan}/acr/{criterion} Token ability: write

Saves what a person decided for one criterion: the conformance level (VPAT 2.x terms, lower case as in levels) and remarks. A field left out keeps its value, null clears it; both empty removes the entry. Agency plans, ability write. Values are never set by Pandus itself: only what you send is stored. 60 requests a minute.

Parameters

NameInTypeDescription
scan(required) path string The check's id.
criterion(required) path string Number of a WCAG 2.2 A/AA success criterion. 4.1.1 Parsing is not part of WCAG 2.2.
lang query string Report language; the site's report language when left out. Values: en, uk, de, pl, cs, ro, ru

Request body application/json

FieldTypeDescription
conformance string | null Values: supports, partially_supports, does_not_support, not_applicable, not_evaluated
remarks string | null (max. 5000)

Example

{
    "conformance": "partially_supports",
    "remarks": "Product photos in the gallery have no alt text; fixed in the next release."
}

Response

200 The criterion as saved.

application/json

{
    "data": {
        "criterion": "1.1.1",
        "name": "Non-text Content",
        "level": "A",
        "url": "https://www.w3.org/WAI/WCAG22/Understanding/non-text-content",
        "automated": "fails",
        "failures": [
            {
                "rule": "image-alt",
                "title": "Images without a text alternative",
                "count": 4,
                "pages": 2
            }
        ],
        "conformance": "partially_supports",
        "remarks": "Product photos in the gallery have no alt text; fixed in the next release."
    }
}
Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 Without the ability the request needs, without the API in the plan, or without ACR drafts in the plan (Agency plans). ACR drafts are not included in the current plan of this team. / Invalid ability provided.
404 Not found in this team, or older than the plan's history. Not found.
409 The check is still queued or running, or it failed. The scan is not completed yet.
422 An unknown criterion, a level that is not one of levels, remarks over 5000 characters, or an empty body. The selected conformance is invalid.
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.

Code samples

curl

curl -X PUT "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/1.1.1" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "conformance": "partially_supports",
  "remarks": "Product photos in the gallery have no alt text; fixed in the next release."
}'

JavaScript

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/1.1.1', {
    method: 'PUT',
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
        Accept: 'application/json',
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        conformance: 'partially_supports',
        remarks: 'Product photos in the gallery have no alt text; fixed in the next release.',
    }),
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
const { data } = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->acceptJson()
    ->put('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/1.1.1', [
        'conformance' => 'partially_supports',
        'remarks' => 'Product photos in the gallery have no alt text; fixed in the next release.',
    ])
    ->throw();

$data = $response->json('data');

Python

import os

import requests

response = requests.put(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/1.1.1",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}", "Accept": "application/json"},
    json={
        "conformance": "partially_supports",
        "remarks": "Product photos in the gallery have no alt text; fixed in the next release.",
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Download the ACR draft

GET /scans/{scan}/acr/export Token ability: read

The ACR draft as a PDF or an HTML file, under the team's brand on plans with white-label, the same file as in the cabinet. The draft wording is at the start, at the end and, in the PDF, in the footer of every page. Made on every request (remarks change), synchronously: keep the read timeout of your client at 120 seconds. Agency plans. 20 requests a minute.

Parameters

NameInTypeDescription
scan(required) path string The check's id.
format query string pdf (default) or html, a single file that opens in any browser. Values: pdf, html Default: pdf
lang query string Report language; the site's report language when left out. Values: en, uk, de, pl, cs, ro, ru

Response

200 The file (Content-Disposition attachment, e.g. acr-draft-shop.example-2026-10-14.pdf).

application/pdf

A file; no example shown.

text/html

A file; no example shown.

Errors
StatusWhenmessage
401 No token, an unknown token or a deleted one. Unauthenticated.
403 Without the ability the request needs, without the API in the plan, or without ACR drafts in the plan (Agency plans). ACR drafts are not included in the current plan of this team. / Invalid ability provided.
404 Not found in this team, or older than the plan's history. Not found.
409 The check is still queued or running, or it failed. The scan is not completed yet.
422 The request is not valid or a plan limit stops it. The url field is required. (and 1 more error)
429 Too many requests in a minute. Wait Retry-After seconds. Too Many Attempts.
503 The PDF could not be prepared right now; try again after Retry-After seconds. The PDF could not be prepared right now. Please try again in a minute.

Code samples

curl

curl "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/export?format=pdf&lang=en" \
  -H "Authorization: Bearer $PANDUS_TOKEN" \
  --fail -o acr-draft.pdf

JavaScript

import { writeFile } from 'node:fs/promises';

const response = await fetch('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/export?format=pdf&lang=en', {
    headers: {
        Authorization: `Bearer ${process.env.PANDUS_TOKEN}`,
    },
});
if (!response.ok) throw new Error(`Pandus API: HTTP ${response.status}`);
await writeFile('acr-draft.pdf', Buffer.from(await response.arrayBuffer()));

PHP

use Illuminate\Support\Facades\Http;

$response = Http::withToken(env('PANDUS_TOKEN'))
    ->get('https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/export', [
        'format' => 'pdf',
        'lang' => 'en',
    ])
    ->throw();

file_put_contents('acr-draft.pdf', $response->body());

Python

import os

import requests

response = requests.get(
    "https://pandus.online/api/v1/scans/01j9z4a7d2f4g6h8j0k2l4z6x8/acr/export",
    headers={"Authorization": f"Bearer {os.environ['PANDUS_TOKEN']}"},
    params={
        "format": "pdf",
        "lang": "en",
    },
    timeout=30,
)
response.raise_for_status()
with open("acr-draft.pdf", "wb") as file:
    file.write(response.content)