# Editing click paths through the API

The REST endpoints for click paths, sign-in flows and test runs, with examples, rights and error codes – for your own scripts without an AI assistant.

**In short**

- Everything created through the API is a draft and runs only once a person approves it in the “Testing” tab.
- The token belongs to exactly one website and needs the right “Edit click paths”, granted by an administrator.
- Errors carry a stable “reason”; step errors also carry code, step and field.

This article describes the endpoints through which your own scripts draft and try click paths and the steps of a multi-step sign-in. An AI assistant uses the same rules through the MCP server ([Describing sign-in and click paths](https://barrierepruefung.de/en/help/login-and-click-paths#ki-assistent)). The step language itself is described in [Describing sign-in and click paths](https://barrierepruefung.de/en/help/login-and-click-paths#schritte).

## Token and rights

You need a token bound to the website that carries the right “Edit click paths”. How to create it is described in [Describing sign-in and click paths](https://barrierepruefung.de/en/help/login-and-click-paths#ki-token). With this right the token gets two abilities: `journeys:read` for reading and checking, `journeys:write` for saving drafts and starting test runs.

Every request carries the token as a header; the base address is `https://barrierepruefung.de/api/v1`:

```text
Authorization: Bearer YOUR_TOKEN
Accept: application/json
Accept-Language: en
```

`{site}` in the paths is the “Site ID” from the “Embedding” tab. For any other website the API answers 404. Texts in responses follow `Accept-Language` (`de` or `en`). Writing calls optionally take `actor_name`: the name under which your system knows the acting person. It is recorded on the draft as evidence; the responsible person remains whoever granted the right.

## Endpoints

| Method | Path | Right | Purpose |
| --- | --- | --- | --- |
| `GET` | `/sites/{site}/journeys` | read | all click paths with status, steps and draft |
| `GET` | `/sites/{site}/journeys/{id}` | read | one click path |
| `POST` | `/sites/{site}/journeys/pruefen` | read | check steps without saving |
| `POST` | `/sites/{site}/journeys` | write | create a new click path as a draft |
| `PUT` | `/sites/{site}/journeys/{id}/entwurf` | write | set the draft of an existing click path |
| `DELETE` | `/sites/{site}/journeys/{id}/entwurf` | write | discard the draft; running steps stay |
| `GET` | `/sites/{site}/anmeldung` | read | the sign-in steps, without credentials |
| `PUT` | `/sites/{site}/anmeldung/entwurf` | write | propose the steps of a multi-step sign-in |
| `POST` | `/sites/{site}/probelaeufe` | write | start a test run |
| `GET` | `/sites/{site}/probelaeufe/{id}` | read | state and result of a test run |

The schema of the step language is available without a token at `https://barrierepruefung.de/api/v1/schema/klickpfad.json` and `https://barrierepruefung.de/api/v1/schema/anmeldeablauf.json` (JSON Schema 2020-12).

Approving and deleting are not available through the API. Neither are credentials: no user name, password or one-time code secret, no sign-in services and no confirmation for them are set or returned.

## Checking steps

`POST /sites/{site}/journeys/pruefen` with `steps` and optionally `stop_before` saves nothing and always answers 200. An invalid path is the result here:

```json
{
  "gueltig": false,
  "fehler": [
    {
      "code": "selektor_nicht_erlaubt",
      "schritt": 2,
      "feld": "selector",
      "text": "Schritt 2: Der Selektor enthält unerlaubte Zeichen. Erlaubt sind CSS-Selektoren, role=… und text=…."
    }
  ],
  "bereinigt": [
    {"action": "goto", "url": "/warenkorb"},
    {"action": "click", "selector": "xpath=//button"}
  ]
}
```

The sentence in `text` is currently German only; use `code`, `schritt` and `feld` to react.

## Saving a draft

`POST /sites/{site}/journeys` creates a new click path with `name`, `steps` and optionally `stop_before`. It then has no running steps, only a draft. `PUT /sites/{site}/journeys/{id}/entwurf` sets the draft of an existing path; its name stays, and so do its running steps.

```json
{
  "name": "Checkout",
  "steps": [
    {"action": "goto", "url": "/warenkorb"},
    {"action": "click", "selector": "role=button[name=\"Zur Kasse\"]"}
  ],
  "stop_before": "role=button[name=\"Zahlungspflichtig bestellen\"]",
  "actor_name": "Build script"
}
```

The response (201 on creation, otherwise 200) describes the path:

```json
{
  "data": {
    "id": 42,
    "name": "Checkout",
    "status": "entwurf",
    "schritte": [],
    "stop_before": null,
    "entwurf": {
      "schritte": [
        {"action": "goto", "url": "/warenkorb"},
        {"action": "click", "selector": "role=button[name=\"Zur Kasse\"]"}
      ],
      "stop_before": "role=button[name=\"Zahlungspflichtig bestellen\"]",
      "von": {"weg": "api", "name": "Build script", "token_name": "Build"},
      "am": "2026-10-30T09:15:00+00:00"
    },
    "freigegeben": null,
    "letzter_lauf": {
      "status": "gescheitert",
      "am": "2026-10-30T03:12:44+00:00",
      "lauf_id": "01JBX0A2C4E6G8H1J3K5M7N9P0",
      "seitdem_geaendert": false,
      "grund": null,
      "befund": {
        "code": "element_versteckt",
        "schritt": 2,
        "schritte": 2,
        "aktion": "click",
        "selektor": "role=button[name=\"Zur Kasse\"]",
        "viewport": "mobile",
        "befund": {"treffer": 1, "sichtbar": 0},
        "ueberschrift": "Schritt 2 von 2 gescheitert (click, Mobil, 390 px)",
        "erklaerung": "Der Selektor trifft ein Element, es ist aber nicht sichtbar.",
        "hinweis": "Wählen Sie ein Element, das in dieser Breite sichtbar ist, …"
      }
    },
    "freigabe_url": "…/websites/…/pruefung"
  }
}
```

`status` is `entwurf` (draft), `freigegeben` (approved) or `freigegeben_mit_entwurf` (approved, with a draft). `letzter_lauf` is `null` as long as the path has not appeared in a completed scan. Otherwise it holds the state in the last scan: `status` is `erfolgreich` (succeeded), `gescheitert` (failed) or `nicht_geprueft` (not checked; this is not a failure of the path: sign-in, throttling, deadline, budget or proxy prevented the check, the reason is in `grund`), `am` and `lauf_id` name the scan, `seitdem_geaendert` is `true` if the path was approved after it. Only for `gescheitert` is `befund` set: `code` is one of the codes in [Describing sign-in and click paths](https://barrierepruefung.de/en/help/login-and-click-paths#klickpfad-scheitert), `schritt`, `schritte`, `aktion`, `selektor` and `viewport` say where, `befund` holds the measured values (`treffer`, `sichtbar`, `erster_sichtbar`, `erster_bedienbar`, `abgeloest`, `navigiert`, `adresse`; not all of them for every code), and `ueberschrift`, `erklaerung` and `hinweis` are texts in the language of the request. Branch on `code`, not on the texts.

At `freigabe_url` a person approves the draft ([Describing sign-in and click paths](https://barrierepruefung.de/en/help/login-and-click-paths#freigabe)).

For a multi-step sign-in, `PUT /sites/{site}/anmeldung/entwurf` with `schritte` proposes the steps, with placeholders instead of credentials ([Describing sign-in and click paths](https://barrierepruefung.de/en/help/login-and-click-paths#platzhalter)). This works only if a credential of that kind already exists in the “Testing” tab; otherwise the API answers 404, and for a simple sign-in form `kein_ablauf`.

## Test runs

`POST /sites/{site}/probelaeufe` starts a test run and answers 202 straight away. It needs exactly one source:

- `{"journey_id": 42, "quelle": "entwurf"}` or `"quelle": "freigegeben"`: a saved click path. If a credential is active, the test run signs in first, like a scan.
- `{"anmeldung": "entwurf"}`: only the draft of the sign-in.
- `{"schritte": [...], "stop_before": "…"}`: unsaved steps, without sign-in.

Poll the state with `GET /sites/{site}/probelaeufe/{id}` until `status` is no longer `wartet` or `laeuft`:

```json
{
  "data": {
    "id": "01JBX3K7Q8M4V2N6P0R5S9T1W3",
    "status": "fertig",
    "quelle": "entwurf",
    "journey_id": 42,
    "mit_anmeldung": false,
    "erfolgreich": false,
    "grund": null,
    "schritte": [
      {"nr": 1, "viewport": "desktop", "action": "goto", "ok": true, "grund": null, "adresse": "https://www.example.org/warenkorb", "aria": "- main: …", "befund": null, "bild_url": "…"},
      {"nr": 2, "viewport": "desktop", "action": "click", "ok": false, "grund": "klick_abgelehnt:nicht_gefunden", "adresse": "https://www.example.org/warenkorb", "aria": "- main: …", "befund": {"code": "klick_abgelehnt:nicht_gefunden", "schritt": 2, "schritte": 2, "aktion": "click", "selektor": "role=button[name=\"Zur Kasse\"]", "viewport": "desktop", "befund": {"treffer": 0, "sichtbar": 0}, "ueberschrift": "…", "erklaerung": "…", "hinweis": "…"}, "bild_url": "…"}
    ],
    "anmeldung": null,
    "dauer_ms": 5120,
    "angelegt_am": "2026-10-30T09:16:00+00:00",
    "fertig_am": "2026-10-30T09:16:07+00:00",
    "loeschen_ab": "2026-10-31T09:16:00+00:00",
    "freigabe_hinweis": "Before this path runs in scans, a person has to approve it in the ‘Testing’ tab: …"
  }
}
```

- `status`: `wartet` (waiting), `laeuft` (running), `fertig` (done), `gescheitert` (the test run failed as a whole, reason in `grund`, for example `start_fremder_host`, a proxy error page `proxy_fehler:…`, `budget_ueberschritten`, `abgelaufen` or `scanner_veraltet` during an update) or `abgelehnt` (refused).
- `erfolgreich`: `true` only if the test run is done, no step failed and something actually succeeded (a step with `ok: true` or, for a sign-in-only test run, a successful sign-in); `null` while it runs. Branch on this, not on `status`: a `fertig` test run can still have failed at a step.
- A test run checks every width of the scan profile in turn and ends at the first failure. `schritte` holds one entry per step and width; `viewport` is `desktop`, `mobile` or `reflow-320` (older results without it count as `desktop`). For a failed step `befund` has the same shape as in `letzter_lauf`, including `selektor`; otherwise it is `null`. The image belongs to step and width.
- Per step `ok`: `true`, `false` or `null` for “not executed”. The test run ends at the first failure. The reasons are the same as in a scan ([Describing sign-in and click paths](https://barrierepruefung.de/en/help/login-and-click-paths#fehlergruende)); `stop_before` means the path stopped before the stop target.
- `bild_url` is a signed address valid for 10 minutes. Query the test run again to get a new one.
- If a step lies outside your domain, `adresse` holds only its origin, and `aria` and `bild_url` are empty.
- After `loeschen_ab` the result and images are deleted.

The limits of test runs are described in [Describing sign-in and click paths](https://barrierepruefung.de/en/help/login-and-click-paths#ki-grenzen).

## Errors

Errors come as `application/problem+json` with `status`, `detail` (a sentence in the language of the request) and, where there is one, a stable key `reason`. Branch on `reason`, not on the sentence.

| Status | `reason` | Meaning |
| --- | --- | --- |
| 401 | – | Token missing or invalid. |
| 403 | `recht_fehlt` | The token lacks the right; `url` leads to the “Embedding” tab. |
| 403 | `recht_erloschen` | The person who granted the right may no longer do so. Reading still works. |
| 403 | `konto_geschlossen` | The organization’s account is closed. |
| 404 | – or `nicht_gefunden` | Website, click path, credential or test run does not exist for this token. |
| 409 | `probelauf_laeuft` | A test run is already in progress for this website. It is not queued. |
| 422 | `schritte_ungueltig` | At least one step is invalid; `fehler` names code, step and field. |
| 422 | `domain_nicht_bestaetigt` | The website’s domain is not verified. |
| 422 | `plan` | The plan allows no (further) click paths. |
| 422 | `kein_ablauf` | The credential is a simple sign-in form and has no steps. |
| 422 | `anmeldung_pausiert` | Sign-in is paused; try a new sign-in flow first. |
| 422 | `eingabe_ungueltig` | The test run’s input does not have the expected shape, for example two sources. |
| 422 | – | A field is missing or has the wrong shape; `errors` names it. |
| 429 | `grenze_erreicht` | Too many test runs in the last hour; `wieder_ab` says when it is possible again. |
| 429 | `anmeldung_gesperrt` | Test runs with sign-in are blocked after a failed sign-in; with `wieder_ab`. |

The codes in `fehler[].code` form a closed list:

| Code | Meaning |
| --- | --- |
| `aktion_unbekannt` | The action does not exist. |
| `feld_fehlt` | A required field of the action is missing. |
| `feld_kein_text` | A field is not text. |
| `adresse_unvollstaendig` | The address is incomplete. |
| `schema_nicht_erlaubt` | Only `http` and `https` are allowed. |
| `adresse_fremd` | The address is not on your domain. |
| `selektor_nicht_erlaubt` | The selector is not CSS, `role=…` or `text=…`. |
| `taste_nicht_erlaubt` | The key is not in the list. |
| `zu_viele_schritte` | More steps than allowed. |
| `keine_schritte` | The list is empty. |
| `stop_before_fehlt` | The path touches an order or payment and has no stop target. |
| `erster_schritt_nicht_eigen` | The first step of a sign-in is not a `goto` to your domain. |
| `platzhalter_falsch` | A placeholder is unknown or in the wrong place. |
| `platzhalter_fehlt` | User name or password do not occur in the sign-in. |
| `einmalcode_ohne_geheimnis` | The one-time code is used, but no secret is stored. |
| `schritt_ungueltig` | The step is not a valid object. |
| `anmeldehost_ungueltig` | A sign-in service is not a valid host. |
| `anmeldehost_eigen` | A sign-in service is on your own domain. |
| `zu_viele_anmeldehosts` | More sign-in services than allowed. |
| `anmeldehosts_unbestaetigt` | The sign-in services are not confirmed. |

---

Last checked against the product: 2026-11-03 · https://barrierepruefung.de/en/help/click-paths-api
