Integrationen

CI/CD mit der API

Aktualisiert: 3. November 2026 Für: Entwickler Tarif: Professional und höher

Ein Deployment, das die Messung zerstört, fängt man am besten ab, bevor es in Produktion geht. Die API spielt Ihre gespeicherten Journeys gegen die Vorschau einer konkreten Änderung ab, vergleicht das Ergebnis mit dem erwarteten Zustand und liefert ein eindeutiges „bestanden“ oder „nicht bestanden“, an dem der CI-Job endet. Diese Anleitung zeigt, wie Sie diesen Ablauf aufbauen und wo er üblicherweise hängen bleibt.

Der Ablauf hat vier Schritte: starten, warten, das Gate auswerten und bei Bedarf SARIF oder einen Kommentar ausgeben.

API-Schlüssel, Abfragen und der Export der Belege gibt es ab Starter; Journeys gegen Vorschau-URLs abzuspielen erfordert Professional.

POST · 202 /targets/{id}/scans {"url": Vorschau} GET · WARTEN /scans/{id}?wait=90 status nicht endgültig ∨ settled = false GATE · ODER gate.passed ?gate={…} false → exit 1 SARIF 2.1.0 ?format=sarif POST · KOMMENTAR …/explain
Der Start liefert eine Scan-ID. Das Warten wiederholt sich, bis der Scan beendet und seine Befunde gespeichert sind. Das Gate entscheidet über das Ergebnis des Jobs; SARIF und Kommentar sind nur Ausgaben.

GitHub App oder eigene Aufrufe

Sie haben zwei Möglichkeiten, beide auf derselben API:

  • Die GitHub App (Pull-Request-Prüfung) richten Sie im Dashboard ein; ins Repository kommt nichts. Ihre Regel ist fest: Sie meldet nur direkte Änderungen im Data Layer der Website (window.dataLayer, window.adobeDataLayer) und blockiert nur kritische Befunde.
  • Eigene API-Aufrufe funktionieren in jeder CI, und die Gate-Regel bestimmen Sie selbst. Warten und Auswertung enthalten die CI-Rezepte für GitHub, GitLab und Bitbucket bereits.

Reicht Ihnen die Standardregel auf GitHub? Nehmen Sie die App. Eine andere CI oder eine andere Regel heißt API.

Der Schlüssel

Den Schlüssel legt ein Editor oder eine höhere Rolle unter Projekteinstellungen › API-Schlüssel an. Ein Schlüssel hat eine Berechtigung: Lesen, Bearbeiten oder Admin. CI löst Scans aus und liest sie, braucht also Bearbeiten. Mehr braucht der Schlüssel nicht. Der Ablauf ist optional, und den vollständigen Schlüssel sehen Sie nur einmal, direkt nach dem Anlegen.

Der Schlüssel beginnt mit ap_live_ und gehört in einen Header Authorization: Bearer … oder X-API-Key: …. Speichern Sie ihn als maskierte Variable oder Secret, nie im Repository. Ein Schlüssel funktioniert ab dem Starter-Tarif. Ein Aufruf, den der Tarif nicht enthält, bekommt 403 plan_required, und error.meta.minTier nennt den nötigen Tarif: Ein Vorschau-Lauf, um den es in dieser Anleitung geht, braucht Professional. Fällt ein Projekt auf Free zurück, bleiben seine Schlüssel gespeichert und funktionieren nach einem Upgrade wieder. Details stehen in der API-Referenz.

Start gegen eine Vorschau

Einen Scan starten Sie mit POST /api/v1/targets/{id}/scans; die ID des Tests finden Sie über GET /api/v1/targets. Enthält der Body eine url mit der Vorschau-Adresse, läuft der Scan als Dry-Run: Er erzeugt Abweichungen und ein Ergebnis für das Gate, in die Journey wird aber nichts zurückgeschrieben. Erwarteter Zustand, überwachte Tools und Regressionsverlauf bleiben unverändert. Ohne url startet derselbe Aufruf einen normalen Scan des gespeicherten Tests.

Bei einem Website-Test reicht eine url allein für eine Journey nicht. Ohne Scan-Typ startet die Anfrage den Standard-Scan des Tests, ein Compliance-Audit der Vorschau. Um eine Journey abzuspielen, senden Sie "scanType": "web_journey" und optional eine journeyId; ohne sie wird die erste Journey des Tests genommen, die Ihr Tarif ausführen lässt. Die Antwort enthält dann einen Scan: seine id und pollUrl.

Mit "journeys": "all" werden alle Journeys des Tests abgespielt, die Ihr Tarif ausführen lässt, und maxJourneys begrenzt ihre Anzahl. Die Antwort hat dann eine andere Form: Es gibt keine id auf oberster Ebene, nur ein Feld scans mit id und pollUrl je Journey und ein Feld failed mit Journeys, die sich nicht starten ließen, jeweils mit error.code. Ihr Skript muss auf jede scans[].pollUrl warten. Ein nicht leeres failed sollte den Job scheitern lassen.

Zwei Dinge lohnt es sich gleich einzustellen:

  • Einen Header Idempotency-Key. Ein erneuter Versuch mit demselben Schlüssel startet keinen zweiten Scan, sondern liefert den ersten. Der Schlüssel steht für genau eine Anfrage: den Commit, den CI-Lauf (auf GitHub den Run und seinen Versuch, auf GitLab den Job), den Test, die Auswahl der Journeys und die Vorschau-URL. Lassen Sie ihn bei Wiederholungen dieser Anfrage unverändert. Solange die erste Anfrage noch verarbeitet wird, bekommt eine Wiederholung 409 idempotent_in_progress mit Retry-After; warten Sie und senden Sie sie erneut. Ein abgeschlossener Schlüssel liefert dieselben Scans, solange sie existieren, auch nach 24 Stunden, und die Vorschau-URL bleibt meist für den ganzen Pull Request gleich, also muss sich der Schlüssel mit jedem neuen Push ändern; deshalb gehört der Commit hinein. Der CI-Lauf sorgt zusätzlich dafür, dass ein erneut gestarteter Job einen frischen Scan bekommt. Ohne die URL bekäme derselbe Commit auf einer zweiten Vorschau 422 idempotency_conflict. Nehmen Sie einen Hash der URL statt der URL selbst auf: Ein Schlüssel mit mehr als 200 Zeichen wird mit 400 abgelehnt.
  • Das Scan-Land. Die Vorschau wird aus dem Land gescannt, das beim Anlegen des Tests gewählt wurde. Liegt die Vorschau hinter einer IP-Allowlist, geben Sie die Adresse dieses Landes frei.

Alle Felder des Request-Bodys beschreibt die OpenAPI-Spezifikation.

Auf das Ergebnis warten

Den Zustand des Scans lesen Sie mit GET /api/v1/scans/{id}?wait=N. Der Server hält die Anfrage offen, bis der Scan beendet und seine Befunde gespeichert sind, höchstens N Sekunden. Er wartet nie länger als 90 Sekunden; wiederholen Sie die Anfrage, bis der Scan beendet ist.

Der Scan ist beendet, wenn sein status einer dieser Werte ist:

Wert in der API Im Dashboard
completed Abgeschlossen
failed Fehlgeschlagen
cancelled Abgebrochen
broken_step Abgebrochener Schritt
no_data Keine Daten

Wiederholen Sie, bis status endgültig und settled gleich true ist. settled: false heißt, dass Ihre Wartezeit abgelaufen ist, während die Befunde noch gespeichert wurden. Fragen Sie einfach erneut.

Ein Schlüssel darf höchstens fünf wartende Anfragen gleichzeitig halten; die sechste bekommt 429 too_many_long_polls. Wenn Sie mehrere Journeys auf einmal starten, lesen Sie sie nacheinander, nicht parallel.

Das Gate entwerfen

Das Gate übergeben Sie beim Lesen des Scans als ?gate= mit URL-kodiertem JSON. Prädikate werden mit ODER verknüpft: Das Gate schlägt fehl, sobald eines zutrifft. Alle Prädikate stehen in der Referenz. Für eine Vorschau und den dataLayer ist das ein vernünftiger Anfang:

{ "failOnSeverity": "critical", "failOnBrokenStep": true }

Regeln, die Sie der Prädikat-Tabelle nicht ansehen:

  • Ein Scan mit dem Ergebnis failed, cancelled oder no_data besteht nie ein Gate, egal was Sie einstellen. Ein Scan ohne Daten bekommt auch kein falsches „Score unter Schwelle“, sondern einen eigenen Code.
  • Ein abgebrochener Schritt lässt das Gate standardmäßig nicht scheitern. Er endet als Warnung, und die Abweichungen dieses Scans werden nicht ausgewertet. Zerstört eine Änderung den Warenkorb-Button, besteht ein Gate ohne failOnBrokenStep. Deshalb empfehlen wir, es einzuschalten.
  • Ein Ereignis, das um einen Schritt verrutscht, liegt in der Toleranz: Es erscheint nicht unter den Abweichungen und lässt das Gate nie scheitern. Ein Ereignis, das um mehr als einen Schritt verrutscht, ist eine Abweichung event_drifted mit der Schwere warning und lässt deshalb ein Gate mit failOnSeverity: "warning" oder failOnNewDataLayerRegressions scheitern.
  • Solange eine Journey noch keinen erwarteten Zustand hat, haben die dataLayer-Prädikate nichts zum Vergleichen. Sie können nicht scheitern, und in warnings steht datalayer_gate_no_baseline. Es lohnt sich, in der CI auf diese Warnung zu reagieren.
  • Ohne ?gate= enthält die Antwort gar kein Gate. Ihr Skript muss ein fehlendes gate.passed deshalb als Fehlschlag werten, nicht als Erfolg.
  • minComplianceScore bewertet den Compliance-Score und gehört daher zu einem Compliance-Test. Eine Journey wird an ihren Abweichungen gemessen.
  • Eine Seite ohne Tracking-Tools und ohne Consent-Banner besteht das Gate nicht, der Code ist no_tracking_observed.
  • Ein App-Lauf ohne geladenen GTM-Container besteht ein Diff-Gate nicht, der Code ist vendors_not_evaluated.
  • Ein fehlgeschlagener Scan, der auf Seiten von AnalyticsProof unterbrochen wurde (zum Beispiel durch einen Neustart, ein Herunterfahren oder einen Ausfall unserer Datenbank), lässt das Gate mit dem Code infrastructure_interrupted statt status_failed scheitern, ohne zusätzlichen Score-Code. Das Ergebnis sagt nichts über die Seite aus, starten Sie den Scan deshalb erneut.

Der Parameter scope=direct_datalayer beschränkt die Abweichungen auf das, was die Website direkt in window.dataLayer oder window.adobeDataLayer schreibt. Anfragen, die GA4, Adobe Analytics und andere Tools daraus erzeugen, bleiben im Dashboard, beeinflussen das Gate aber nicht. Die App nutzt dieselbe Einschränkung. Verglichen werden das Vorhandensein von Ereignissen und Parametern und ihr JavaScript-Typ, nicht ihre Werte. Die Ausnahme sind Consent-Signale: Dort ist ein geänderter Wert ein eigener Befund.

SARIF und der Kommentar

Mit ?format=sarif liefert ein beendeter Scan, dessen Befunde gespeichert sind, ein SARIF-2.1.0-Dokument. Bis dahin ist die Antwort das normale JSON: bei einem noch laufenden Scan und bei einem beendeten Scan, dessen Befunde noch gespeichert werden, mit settled: false. Ein unvollständiges SARIF erhalten Sie also nie, eventuell aber JSON an seiner Stelle. Am einfachsten holen Sie SARIF nach einer JSON-Antwort, deren status final ist und deren settled true ist. SARIF enthält dieselben Befunde, die das Gate bewertet (kritisch als error, Warnung als warning, Information als note); um einen Schritt verschobene Ereignisse stehen nicht darin. Das Urteil steht nicht darin; über den Job entscheidet weiterhin gate.passed. Geben Sie denselben scope mit, damit die Datei zum Gate passt.

Einen Kommentar für den Pull oder Merge Request liefert POST /api/v1/scans/{id}/explain: eine Erklärung der Abweichungen in Alltagssprache, ohne Parameterwerte. Vor dem Ende des Scans antwortet der Aufruf mit 409 scan_not_terminal.

Beispiel: GitLab CI und andere CIs

Die Schritte sind in jeder CI gleich, die curl und jq hat:

  1. Schlüssel und Test-ID als maskierte CI-Variablen speichern.
  2. Den Job nach dem Vorschau-Deployment einplanen, damit er die Vorschau-Adresse kennt.
  3. Einen Dry-Run mit url, "journeys": "all" und einem Idempotency-Key aus Commit, CI-Lauf, Test-ID, Journey-Auswahl und einem Hash der Vorschau-URL starten. Alle pollUrl aus scans behalten und bei nicht leerem failed abbrechen.
  4. Jeden Scan mit wait=90, dem Gate und dem scope lesen, bis er endgültig und abgeschlossen ist. Das gesamte Warten über das Job-Timeout begrenzen.
  5. Den Job scheitern lassen, wenn gate.passed bei irgendeiner Journey nicht true ist. gate.failures und warnings ins Log schreiben.
  6. Zeigt Ihre CI kein SARIF an, die Datei als Job-Artefakt ablegen.

Ein vollständiges GitLab-Skript steht in den CI-Rezepten. Es spielt eine Journey gegen die Vorschau des Merge Requests ab, mit dem Body {"url": "…", "scanType": "web_journey"}, einem Idempotency-Key aus Commit, Job und einem Hash der Vorschau-URL und dem Journey-Gate von oben, und liest eine einzelne id. Für "journeys": "all" braucht es eine Schleife über scans[], weil die Antwort keine id auf oberster Ebene hat.

Fehler und was zu tun ist

Code Übliche Ursache Was tun
missing_credentials, invalid_key Das Secret kam nicht im Job an oder ist abgeschnitten Variable und Header prüfen
key_revoked, key_expired Der Schlüssel ist widerrufen oder abgelaufen Neuen anlegen und das Secret tauschen
plan_required Der Tarif enthält den Aufruf nicht (error.meta.minTier) Ein Schlüssel braucht Starter, ein Vorschau-Lauf Professional
insufficient_permission Die Berechtigung des Schlüssels reicht nicht Zum Starten ist Bearbeiten nötig, zum Lesen genügt Lesen
invalid_request mit details[].code journey_required Der Test hat keine Journey zum Abspielen Eine Journey aufzeichnen
journey_sleeping Die Journey liegt über dem Limit des Tarifs Reihenfolge der Journeys oder Tarif ändern
step_cap_exceeded Die Journey hat mehr Schritte, als der Tarif erlaubt Journey kürzen oder Tarif ändern
scan_in_progress Ein normaler Scan des Tests läuft bereits Warten oder eine url für einen Dry-Run senden
idempotency_conflict Derselbe Schlüssel für eine andere Anfrage Für einen anderen Body einen neuen Schlüssel verwenden
rate_limited, too_many_long_polls Mehr als 60 Anfragen pro Minute oder fünf Wartende gleichzeitig So lange warten, wie Retry-After angibt

Codes in gate.failures sind keine API-Fehler, sondern die Gründe, warum das Gate nicht bestanden hat. Alle Codes sind in der Fehlerübersicht beschrieben.



Haben Sie in dieser Anleitung einen Fehler gefunden, oder fehlt etwas? Schreiben Sie uns

Alle Anleitungen