API-Dokumentation
Referenz der Pandus REST API v1: Authentifizierung, Limits, Fehler, Paginierung, jeder Endpunkt mit Parametern und Beispielen in curl, JavaScript, PHP und Python, signierte Webhooks.
- Version:
- 1.1
- Basis-URL:
- https://pandus.online/api/v1
- Aktualisiert:
OpenAPI-Spezifikation (YAML) Wofür die API gedacht ist
Die API liefert die Ergebnisse einer automatischen Prüfung, kein vollständiges Audit. Sie ist keine Rechtsberatung.
Erste Schritte
Die Pandus API bringt die automatischen Barrierefreiheitsprüfungen des Kontos in Ihre eigenen Werkzeuge: ein Deployment-Skript, ein Agentur-Dashboard, ein Ticketsystem. Jede Antwort ist eine automatische Prüfung, kein vollständiges Audit, und keine Rechtsberatung.
- Die API ist in den Tarifen Pro und Agency enthalten, siehe Preise.
- Öffnen Sie im Konto API und Webhooks und erstellen Sie ein Token. Kopieren Sie es sofort: Es wird nur einmal angezeigt.
- Bewahren Sie das Token auf Ihrem Server auf, zum Beispiel in der Umgebungsvariable
PANDUS_TOKEN, und senden Sie die erste Anfrage:
curl https://pandus.online/api/v1/account \
-H "Authorization: Bearer $PANDUS_TOKEN" \
-H "Accept: application/json"
Die Antwort zeigt Ihren Tarif, seine Limits und wie viel davon genutzt ist. Eine typische Integration fügt dann eine Website hinzu (POST /sites), wartet auf die Prüfung (GET /scans/{scan} oder der Webhook scan.completed) und liest den Bericht als JSON oder PDF.
Jede Datei einer Prüfung gibt es auch über die API: den PDF-Bericht, die Exporte der Probleme (CSV, JSON, GitHub-Issues, Jira-Import) und in den Agency-Tarifen den ACR-Entwurf als JSON, HTML oder PDF. GET /sites/{site}/export liefert die Datei der letzten abgeschlossenen Prüfung einer Website, ohne die Prüfung erst nachzuschlagen. PDFs entstehen synchron, beim ersten Mal bis zu einer Minute: Setzen Sie das Lese-Timeout Ihres Clients auf 120 Sekunden.
Alle Adressen unten beziehen sich auf die Basis-URL https://pandus.online/api/v1. Anfragen und Antworten sind JSON in UTF-8 (PDF, Screenshots und Exporte sind Dateien); Datumsangaben sind ISO 8601 mit Zeitzone. Die maschinenlesbare Beschreibung von allem auf dieser Seite ist die OpenAPI-Spezifikation.
Authentifizierung
Senden Sie das Token im Header Authorization jeder Anfrage:
Authorization: Bearer 12|pNd4sY…
Accept: application/json
- Ein Token gehört dem Team, nicht einer Person; es funktioniert weiter, wenn jemand das Team verlässt.
- Berechtigungen.
readerlaubt jedeGET-Anfrage.writeerlaubt das Hinzufügen und Löschen von Websites und das Starten von Prüfungen. Für ein Dashboard reicht ein reines Lese-Token. - Widerrufen. Löschen Sie ein Token im Konto, und es funktioniert sofort nicht mehr. Ein Sitzungs-Cookie des Kontos akzeptiert die API nie.
- Tarif. Wechselt das Team in einen Tarif ohne API, antworten seine Tokens mit
403, bis der Tarif die API wieder enthält; gelöscht werden sie nicht. - Geheim halten. Legen Sie das Token nicht in Code, der im Browser läuft, und nicht in ein öffentliches Repository. Nutzen Sie ein Token pro Integration, damit Sie eines widerrufen können, ohne die anderen zu berühren.
Limits
- Anfragen: 60 pro Minute und Token. PDF-Bericht, Exporte der Probleme und ACR-Dateien: 20 pro Minute.
- Jede Antwort enthält
X-RateLimit-LimitundX-RateLimit-Remaining. Über dem Limit erhalten Sie429mitRetry-After(Sekunden bis zum nächsten Versuch) undX-RateLimit-Reset(Unix-Zeit). - Tariflimits gelten wie im Konto: Anzahl der Websites, Seiten pro Prüfung, Aufbewahrung der Prüfungen (Verlauf) und wie oft eine Prüfung von Hand gestartet werden darf: einmal pro Stunde je Website, nie während eine Prüfung der Website läuft. Eine Anfrage über einem Limit liefert
422mit einer Meldung. GET /accountliefert Limits und Nutzung, daruntermanual_checks_available: die Zahl der Websites, auf denen jetzt sofort eine Prüfung von Hand starten kann.
Fehler
Fehler sind JSON mit einer message auf Englisch. Validierungsfehler und Tariflimits (422) enthalten zusätzlich errors mit den Problemen je Feld:
{
"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 | Bedeutung |
|---|---|
401 |
Kein Token, oder das Token ist unbekannt oder gelöscht. |
403 |
Dem Token fehlt die Berechtigung, der Tarif enthält die API oder das Werkzeug nicht, oder das Team ist gesperrt. |
404 |
In Ihrem Team nicht gefunden oder älter als der Verlauf des Tarifs. Der Inhalt ist immer {"message": "Not found."}. |
409 |
Die Prüfung ist noch nicht abgeschlossen. |
422 |
Die Anfrage ist ungültig, oder ein Tariflimit verhindert sie. |
429 |
Zu viele Anfragen: warten Sie Retry-After Sekunden. |
503 |
Das PDF konnte gerade nicht erstellt werden: versuchen Sie es in einer Minute erneut. |
Entscheiden Sie anhand des Statuscodes, nicht anhand des Wortlauts von message: Der Wortlaut kann sich verbessern.
Paginierung
Listen (GET /sites und GET /sites/{site}/scans) kommen seitenweise. per_page legt die Größe fest, von 1 bis 100 (standardmäßig 50 Websites oder 20 Prüfungen), page die Seitennummer. Jede Liste hat drei Schlüssel:
data: die Einträge dieser Seite;links:first,last,prevundnext, vollständige URLs odernull;meta:current_page,last_page,per_page,total,from,toundpath.
Um alles zu lesen, folgen Sie links.next, bis es null ist:
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 melden Ihrem Server, wenn etwas passiert, damit Sie nicht ständig abfragen müssen. Fügen Sie im Konto unter API und Webhooks einen Endpunkt hinzu: eine öffentliche https-Adresse und die Ereignisse, die er empfängt. Ein Team kann bis zu 5 Endpunkte haben. Das Geheimnis (whsec_…) wird einmal angezeigt, wenn der Endpunkt erstellt wird.
- Zustellung. Ein
POSTmit JSON-Inhalt und den HeadernPandus-Event,Pandus-DeliveryundPandus-Signature. Weiterleitungen werden nicht verfolgt; private und lokale Adressen werden abgelehnt. - Antworten Sie mit einem beliebigen
2xxinnerhalb von 10 Sekunden. Erledigen Sie langsame Arbeit nach der Antwort, zum Beispiel in einer Warteschlange. - Wiederholungen. Andere Antworten und Zeitüberschreitungen werden nach 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden und 6 Stunden wiederholt.
Pandus-Deliverybleibt bei jeder Wiederholung gleich: Nutzen Sie es, um Doppelte zu ignorieren. Nach 20 Zustellungen, die bei jedem Versuch scheiterten, wird der Endpunkt abgeschaltet; schalten Sie ihn im Konto wieder ein. - Signatur.
Pandus-Signature: t=<Unix-Zeit>,v1=<hex>.v1ist HMAC-SHA256 der Zeichenkette"<t>.<roher Inhalt>"mit dem Geheimnis des Endpunkts. Berechnen Sie sie über den rohen Inhalt vor jedem JSON-Parsing, vergleichen Sie in konstanter Zeit und lehnen Sie Zeitstempel ab, die älter als 5 Minuten sind.
Es folgen die Ereignisse, ihre Inhalte und die Prüfung der Signatur in vier Sprachen.
scan.completed · Eine Prüfung ist abgeschlossen
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.
Header
| Name | Beschreibung |
|---|---|
| Pandus-Event | The event type, the same as type in the body. |
| Pandus-Delivery | ID of this delivery; the same on every retry, so you can ignore repeats. |
| Pandus-Signature | t=<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. |
Inhalt
{
"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 · Eine Prüfung hat neue Probleme gefunden
Sent after scan.completed when the check found problems that the previous check did not have (new_issues > 0). Same payload.
Header
| Name | Beschreibung |
|---|---|
| Pandus-Event | The event type, the same as type in the body. |
| Pandus-Delivery | ID of this delivery; the same on every retry, so you can ignore repeats. |
| Pandus-Signature | t=<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. |
Inhalt
{
"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 · Ein Testereignis
Sent when you press "Send a test event" next to an endpoint in the cabinet. Every endpoint receives it, whatever events it listens to.
Header
| Name | Beschreibung |
|---|---|
| Pandus-Event | The event type, the same as type in the body. |
| Pandus-Delivery | ID of this delivery; the same on every retry, so you can ignore repeats. |
| Pandus-Signature | t=<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. |
Inhalt
{
"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.
Signatur prüfen
Berechnen Sie HMAC-SHA256 aus Zeitstempel, einem Punkt und dem rohen Inhalt mit dem Geheimnis des Endpunkts, vergleichen Sie das Ergebnis in konstanter Zeit mit v1 und lehnen Sie Zeitstempel ab, die älter als fünf Minuten sind.
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
Konto
Das Team, dem das Token gehört, sein Tarif, seine Limits und die Nutzung.
Tarif, Limits und Nutzung des Teams
GET
/account
Token-Berechtigung: 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.
Antwort
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
}
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Websites
Websites des Teams. Das Hinzufügen startet die erste Prüfung.
Websites des Teams auflisten
GET
/sites
Token-Berechtigung: read
Sites in alphabetical order of their domain, 50 a page by default.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| 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.
Standard: 50 |
| page | query | integer |
Page number, from 1.
Standard: 1 |
Antwort
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": "« Previous",
"page": null,
"active": false
},
{
"url": "https://pandus.online/api/v1/sites?page=1",
"label": "1",
"page": 1,
"active": true
},
{
"url": null,
"label": "Next »",
"page": null,
"active": false
}
],
"path": "https://pandus.online/api/v1/sites",
"per_page": 50,
"to": 1,
"total": 1
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Website hinzufügen
POST
/sites
Token-Berechtigung: 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.
Anfrageinhalt application/json
| Feld | Typ | Beschreibung |
|---|---|---|
| url(Pflicht) | string (max. 500) |
The address; https:// is added when left out.
|
| name | string | null (max. 120) | |
| report_locale(Pflicht) | string |
Werte: 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.
Standard: true |
Beispiel
{
"url": "shop.example",
"name": "Example Shop",
"report_locale": "de",
"stack": [
"shopify"
],
"tags": [
"client-a"
],
"scan": true
}
Antwort
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"
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Eine Website
GET
/sites/{site}
Token-Berechtigung: read
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| site(Pflicht) | path | string |
The site's id.
|
Antwort
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"
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Website löschen
DELETE
/sites/{site}
Token-Berechtigung: write
Deletes the site with all its checks, reports and screenshots. This cannot be undone.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| site(Pflicht) | path | string |
The site's id.
|
Antwort
204 Deleted; no body.
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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
Prüfungen
Eine Prüfung starten und ihren Status verfolgen.
Prüfungen einer Website
GET
/sites/{site}/scans
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| site(Pflicht) | path | string |
The site's id.
|
| per_page | query | integer (1–100) |
Items per page, 1 to 100.
Standard: 20 |
| page | query | integer |
Page number, from 1.
Standard: 1 |
Antwort
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
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Prüfung jetzt starten
POST
/sites/{site}/scans
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| site(Pflicht) | path | string |
The site's id.
|
Antwort
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
}
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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 einer Prüfung
GET
/scans/{scan}
Token-Berechtigung: read
The status (queued, running, completed, failed), the number of pages and problems.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | path | string |
The check's id.
|
Antwort
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"
}
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Berichte
Ergebnisse einer abgeschlossenen Prüfung als JSON, PDF, Dateien für Entwickler oder Vergleich.
Letzte Prüfung einer Website exportieren
GET
/sites/{site}/export
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| site(Pflicht) | 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.
Werte: json, csv, github, jira, pdf Standard: json |
| lang | query | string |
Report language; the site's report language when left out.
Werte: en, uk, de, pl, cs, ro, ru |
Antwort
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
Eine Datei; kein Beispiel angezeigt.
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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)
Der Bericht als JSON
GET
/scans/{scan}/report
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | path | string |
The check's id.
|
| lang | query | string |
Report language; the site's report language when left out.
Werte: en, uk, de, pl, cs, ro, ru |
Antwort
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
}
]
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Der Bericht als PDF
GET
/scans/{scan}/pdf
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | path | string |
The check's id.
|
| lang | query | string |
Report language; the site's report language when left out.
Werte: en, uk, de, pl, cs, ro, ru |
Antwort
200 The PDF file (Content-Disposition attachment).
application/pdf
Eine Datei; kein Beispiel angezeigt.
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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)
Probleme für Entwickler exportieren
GET
/scans/{scan}/export
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | 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.
Werte: json, csv, github, jira, pdf Standard: json |
| lang | query | string |
Report language; the site's report language when left out.
Werte: en, uk, de, pl, cs, ro, ru |
Antwort
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
Eine Datei; kein Beispiel angezeigt.
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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)
Zwei Prüfungen vergleichen
GET
/scans/{scan}/compare/{other}
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | path | string |
The check's id.
|
| other(Pflicht) | 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.
Werte: en, uk, de, pl, cs, ro, ru |
Antwort
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": []
}
}
]
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Ein Screenshot aus dem Bericht
GET
/scans/{scan}/evidence/{kind}/{id}
Token-Berechtigung: read
The JPEG behind a shot or screen.url link of the JSON report.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | path | string |
The check's id.
|
| kind(Pflicht) | path | string |
page for a page screenshot, issue for the screenshot of one element.
Werte: page, issue |
| id(Pflicht) | path | integer |
The number from the report (examples[].id for an element).
|
Antwort
200 The screenshot.
image/jpeg
Eine Datei; kein Beispiel angezeigt.
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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)
Der ACR-Entwurf als JSON
GET
/scans/{scan}/acr
Token-Berechtigung: 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).
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | path | string |
The check's id.
|
| lang | query | string |
Report language; the site's report language when left out.
Werte: en, uk, de, pl, cs, ro, ru |
Antwort
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"
}
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
Ein ACR-Kriterium ausfüllen
PUT
/scans/{scan}/acr/{criterion}
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | path | string |
The check's id.
|
| criterion(Pflicht) | 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.
Werte: en, uk, de, pl, cs, ro, ru |
Anfrageinhalt application/json
| Feld | Typ | Beschreibung |
|---|---|---|
| conformance | string | null |
Werte: supports, partially_supports, does_not_support, not_applicable, not_evaluated |
| remarks | string | null (max. 5000) |
Beispiel
{
"conformance": "partially_supports",
"remarks": "Product photos in the gallery have no alt text; fixed in the next release."
}
Antwort
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."
}
}
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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"]
ACR-Entwurf herunterladen
GET
/scans/{scan}/acr/export
Token-Berechtigung: 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.
Parameter
| Name | Ort | Typ | Beschreibung |
|---|---|---|---|
| scan(Pflicht) | path | string |
The check's id.
|
| format | query | string |
pdf (default) or html, a single file that opens in any browser.
Werte: pdf, html Standard: pdf |
| lang | query | string |
Report language; the site's report language when left out.
Werte: en, uk, de, pl, cs, ro, ru |
Antwort
200 The file (Content-Disposition attachment, e.g. acr-draft-shop.example-2026-10-14.pdf).
application/pdf
Eine Datei; kein Beispiel angezeigt.
text/html
Eine Datei; kein Beispiel angezeigt.
| Status | Wann | message |
|---|---|---|
| 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. |
Codebeispiele
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)