API v1.2
Entwickler-API
Eine CI-native REST-API, um Scans auszulösen, Pull Requests bei dataLayer- und Compliance-Regressionen zu blockieren und Ergebnisse direkt in Ihre Pipeline zu holen.
Authentifizierung
Jede Anfrage trägt einen Projekt-API-Schlüssel, erstellt unter Projekteinstellungen › API-Schlüssel ab dem Starter-Tarif. Senden Sie ihn als Bearer-Token oder X-API-Key-Header. Jeder Schlüssel hat eine Berechtigung (read, edit oder admin); der vollständige Schlüssel wird nur einmal bei der Erstellung angezeigt. Fällt das Projekt auf Free zurück, bleiben seine Schlüssel gespeichert, antworten aber mit plan_required, bis der Tarif wieder hochgestuft ist.
# Preferred
Authorization: Bearer ap_live_xxxxxxxxxxxxxxxx
# Alternative
X-API-Key: ap_live_xxxxxxxxxxxxxxxxBerechtigungen
| Berechtigung | Beschreibung |
|---|---|
read | List targets, journeys, schedules and scan history; read a journey definition, monitored vendors and a scan’s full evidence; poll a scan, evaluate a CI gate and explain a result. |
edit | Everything Read can do, plus trigger, re-run and cancel scans, upload APK/XAPK files, acknowledge dataLayer diffs and change monitored vendors. |
admin | Everything Edit can do, plus manage the project’s API keys. An admin key cannot mint another admin key. |
Endpunkte
Alle Endpunkte hängen an der API-Basis-URL und erfordern die angegebene Berechtigung. Die beiden öffentlichen Scan-Endpunkte brauchen keinen Schlüssel.
| Methode | Endpunkt | Berechtigung | Zusammenfassung |
|---|---|---|---|
| GET | /api/v1/scans | read | List scan history |
| POST | /api/v1/scans | edit | Trigger an ad-hoc scan |
| GET | /api/v1/scans/{id} | read | Poll a scan + evaluate a CI gate |
| DELETE | /api/v1/scans/{id} | edit | Cancel a pending/running scan |
| POST | /api/v1/scans/{id}/explain | read | Explain a scan in plain language |
| GET | /api/v1/scans/{id}/evidence | read | Download all evidence of a scan |
| GET | /api/v1/targets | read | List saved tests |
| POST | /api/v1/targets/{id}/scans | edit | Re-run a saved test, "Scan again" |
| GET | /api/v1/journeys | read | List saved journeys |
| GET | /api/v1/journeys/{id} | read | Get a saved journey with its steps |
| GET | /api/v1/schedules | read | List scan schedules |
| POST | /api/v1/uploads | edit | Upload a CI APK/XAPK |
| POST | /api/v1/journeys/{id}/datalayer/approve | edit | Approve a dataLayer diff into the journey baseline |
| POST | /api/v1/journeys/{id}/datalayer/approve-all | edit | Approve all pending dataLayer diffs for a scan |
| POST | /api/v1/journeys/{id}/datalayer/ignore | edit | Ignore a dataLayer element journey-wide |
| DELETE | /api/v1/journeys/{id}/datalayer/ignores/{decisionId} | edit | Remove a standing dataLayer ignore |
| GET | /api/v1/journeys/{id}/datalayer/decisions | read | List standing dataLayer ignore decisions |
| GET | /api/v1/targets/{id}/vendors | read | List a target's vendors and their monitoring state |
| PUT | /api/v1/targets/{id}/vendors | edit | Set vendor-monitoring states across a target's journeys |
| GET | /api/v1/whoami | read | Identify the calling key |
| GET | /api/v1/keys | admin | List project API keys |
| POST | /api/v1/keys | admin | Mint a project API key |
| DELETE | /api/v1/keys/{keyId} | admin | Revoke a project API key |
| POST | /api/v1/public/scans | Request a free public Web Compliance scan (public, no API key) | |
| GET | /api/v1/public/scans/{slug} | Read a public scan by slug (public, no API key) |
CI-Gate-Optionen
Übergeben Sie beim Abfragen eines Scans ein URL-kodiertes JSON-Gate als ?gate=. Prädikate werden mit ODER kombiniert — das Gate schlägt fehl, sobald ein Prädikat zutrifft. CI sollte mit einem Wert ungleich null enden, wenn gate.passed false ist.
| Option | Typ | Beschreibung |
|---|---|---|
minComplianceScore | number (0–100) | Fail when the compliance score is below this threshold (score_below_threshold). |
failOnNewDataLayerRegressions | boolean | Fail on new, unacknowledged dataLayer regressions (datalayer_regressions) in watched vendors. |
failOnSeverity | "info" | "warning" | "critical" | Fail when any diff meets or exceeds this severity (severity_exceeded). |
failOnBrokenStep | boolean | Fail when the journey replay broke mid-flow (status_broken_step). |
failOnStatus | "failed" | "cancelled" | "broken_step" | "no_data"[] | Fail when the scan ends in any of the listed statuses (status_<status>). Note: status_no_data always fails, whether or not it is listed. |
failOnNewVendors | boolean | Fail when a vendor appears for the first time that the journey watches by default (dataLayer, adobeDataLayer, Google Analytics 4, Adobe Analytics; on mobile app targets also the app's own tag container (Google Tag Manager)) on a journey that already has a vendor list (new_vendors). |
Diff-Detail (include=diffs)
Fügen Sie ?include=diffs zu einer Scan-Abfrage hinzu, um die vollständige Liste ungelöster Diffs einzubetten. Der Diff ist wertblind: Er meldet Parameternamen und JS-Typen jedes Events, niemals deren Werte.
# Ask for the full unresolved-diff list on a terminal scan:
GET /api/v1/scans/{id}?include=diffs
# Response adds a diffs[] array (value-blind: name + JS-type only):
{
"status": "completed",
"gate": { "passed": false, "failures": [ { "code": "new_vendors", "detail": "1 new-vendor diff(s)" } ] },
"warnings": [],
"diffs": [
{
"stepIndex": 2,
"elementKey": "event:tiktok::purchase",
"vendor": "tiktok",
"diffType": "extra_event",
"severity": "warning",
"description": "Event present in the scan but not in the expected schema"
}
]
}
# diffType is one of: missing_event | extra_event | event_drifted | missing_param |
# extra_param | param_type_changed | consent_value_changed | consent_unreadable |
# step_not_reached. elementKey is "event:<vendor>::<name>",
# "param:<vendor>::<event>::<param>", "consent:<vendor>::<event>::<country>.<signal>",
# or "step:<index>" for step_not_reached, which belongs to no vendor.Dry-Run-Vorschau-Gate
Richten Sie einen gespeicherten Test auf eine PR-Vorschau-URL, um das Deployment zu blockieren, ohne die Produktions-Baseline zu beschädigen. Mit scanType web_journey wird eine Journey (journeyId, sonst die erste ausführbare) als Dry-Run wiedergegeben: Sie erzeugt Diffs für das Gate, schreibt aber nichts in die Journey zurück. Ohne scanType startet ein Website-Test seinen Standard-Scan, ein Compliance-Audit. Mit journeys: all werden alle ausführbaren Journeys wiedergegeben, und die Antwort enthält statt einer einzelnen scanId eine Liste scans. Vorschau-Läufe erfordern den Professional-Tarif; in niedrigeren Tarifen antworten sie mit 403 plan_required.
# Gate a per-PR preview deploy WITHOUT mutating the saved journey baseline.
# "scanType":"web_journey" replays one journey against $PREVIEW_URL as a
# dry-run: it produces diffs for the gate, but writes nothing back to the
# journey's expected schema, vendor watch-list, or regressions. Without a
# scanType a website test runs its default scan, a compliance audit.
# Add "journeyId" to pick the journey; omitted, the first runnable one is used.
#
# Idempotency-Key: commit + CI run ($RUN), mode, target and a HASH of the preview
# URL (plus any journeyId / maxJourneys you send). A retry of the same request
# replays its scan; a new push gets a fresh one (a finished key replays for 24 h),
# and the hash keeps the key within 200 chars. RUN below is for GitHub Actions;
# on another CI use its commit SHA and job id (GitLab: $CI_COMMIT_SHA-$CI_JOB_ID).
RUN="${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}"
URL_HASH=$(printf '%s' "$PREVIEW_URL" | cksum | cut -d' ' -f1)
SID=$(curl -fsS -X POST \
-H "Authorization: Bearer $AP_KEY" \
-H "Idempotency-Key: $RUN-web_journey-$TARGET_ID-$URL_HASH" \
-H 'content-type: application/json' \
-d "$(jq -cn --arg url "$PREVIEW_URL" '{url: $url, scanType: "web_journey"}')" \
"$BASE/api/v1/targets/$TARGET_ID/scans" | jq -r .scanId)
# 400 journey_required is returned when the target has no journey to replay.
# Every runnable journey at once: "journeys":"all" (optional "maxJourneys").
# The response has NO top-level scanId, only scans[] (scanId + pollUrl per
# journey) and failed[] (journeys that could not be started).
RES=$(curl -fsS -X POST \
-H "Authorization: Bearer $AP_KEY" \
-H "Idempotency-Key: $RUN-all-$TARGET_ID-$URL_HASH" \
-H 'content-type: application/json' \
-d "$(jq -cn --arg url "$PREVIEW_URL" '{url: $url, journeys: "all"}')" \
"$BASE/api/v1/targets/$TARGET_ID/scans")
[ "$(echo "$RES" | jq '.failed | length')" = "0" ] || { echo "$RES" | jq .failed; exit 1; }
# Then poll each "$BASE$POLL" in turn (not in parallel: five waits per key) with
# the single-scan loop from the CI recipes, and fail the job unless every gate passed.
for POLL in $(echo "$RES" | jq -r '.scans[].pollUrl'); do echo "$BASE$POLL"; doneCursor-Paginierung
Listen-Endpunkte verwenden Keyset-Paginierung. Folgen Sie nextCursor, bis es null ist; das ältere Flag truncated bleibt aus Kompatibilitätsgründen erhalten.
# Keyset pagination (ordered createdAt DESC, id DESC):
curl -fsS -H "Authorization: Bearer $AP_KEY" \
"$BASE/api/v1/scans?limit=20" | jq '{next: .nextCursor, count: (.scans | length)}'
# Pass the previous response's nextCursor to fetch the next page;
# nextCursor is null on the final page (truncated is kept for back-compat):
curl -fsS -H "Authorization: Bearer $AP_KEY" \
"$BASE/api/v1/scans?limit=20&cursor=$NEXT_CURSOR"Ratenlimits
Limits werden pro API-Schlüssel durchgesetzt und in jeder Antwort ausgewiesen.
- 60 requests / minute per key. Every response carries
X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Reset(seconds until the window resets). - A
429response addsRetry-After(seconds). - 5 concurrent long-polls per key. Exceeding the cap returns
429 too_many_long_pollswithRetry-After: 5.
Idempotenz
Trigger- und Rerun-Endpunkte können mit einem Idempotenzschlüssel sicher wiederholt werden.
- Send
Idempotency-Key(1–200 chars) as a header, or anidempotencyKeybody field — the header wins. - While the first request is still being processed, a duplicate gets
409 idempotent_in_progresswithRetry-After: 5— it is retryable. The in-flight hold is 5 minutes onPOST /api/v1/scansand 60 seconds onPOST /api/v1/targets/{id}/scans. - A completed key replays for 24 hours. A preview scan on a test (a
request with
url) is tied to the key and the request body: the same key with the same body returns the same scans for as long as they exist, also after 24 hours. A replay returns200withidempotentReplay: true. - A key identifies one logical operation, scoped per project across both trigger endpoints — reusing the same key for a different call replays the original scan's id rather than starting a new scan, so use a fresh key (e.g. the commit SHA) per distinct scan.
# Make retries safe: repeat the same Idempotency-Key header (or "idempotencyKey"
# body field) and a replay returns HTTP 200 with idempotentReplay=true and the
# original scanId instead of starting a second scan.
curl -fsS -X POST \
-H "Authorization: Bearer $AP_KEY" \
-H "Idempotency-Key: $GITHUB_SHA" \
-H 'content-type: application/json' -d '{}' \
"$BASE/api/v1/targets/$TARGET_ID/scans"Fehlercodes
Jede Fehlerantwort hat eine stabile Form: ein error-Objekt mit code, message und optionalem details sowie ein requestId auf oberster Ebene. Gate-Fehlercodes erscheinen in gate.failures[].code, nicht in error.code.
| Code | Beschreibung |
|---|---|
unauthorized | No usable credentials were presented. |
missing_credentials | Neither an Authorization Bearer token nor an X-API-Key header was sent. |
invalid_key | The API key is not recognised. |
key_revoked | The API key has been revoked. |
key_expired | The API key is past its expiry date. |
plan_required | The project's plan does not include this operation. error.details.minTier names the lowest plan that does: starter for any keyed API call, professional for Android/TV scans, APK uploads and preview runs (dryRun, a journey replayed against another url, journeys:"all"). |
insufficient_permission | The key’s permission is below the one this operation requires (read < edit < admin). |
rate_limited | Per-key request rate limit exceeded — see the Retry-After header. |
too_many_long_polls | Too many concurrent long-poll requests for this key (max 5). |
invalid_request | The request body or query failed validation. |
not_found | The referenced scan, target, journey, or upload does not exist for this project. |
idempotent_in_progress | A prior request with the same Idempotency-Key is still in flight. Retryable: wait for Retry-After (5 s) and send the same request again. |
idempotency_conflict | The same Idempotency-Key was reused with a different payload on the same resource (e.g. single-journey vs journeys:"all"). Use a new key when the payload changes. |
idempotency_corrupt | The stored idempotency record for this key could not be read. Retry with a new Idempotency-Key. |
not_cancellable | The scan is already terminal and cannot be cancelled. |
scan_not_terminal | The scan has not reached a terminal status yet, so the requested artifact (SARIF / explain) is not available. Poll GET /scans/{id} first. |
evidence_too_large | The evidence document is larger than 25 MB. Request fewer steps with ?steps= (error.details.stepCount says how many were selected). |
journey_mismatch | The supplied journeyId does not belong to the target. |
journey_required | A preview-URL gate needs a journey to replay, but the target has none. |
journey_sleeping | The journeyId is ranked beyond the plan's per-target journey quota. Reorder the target's journeys or upgrade the plan. |
journey_over_step_cap | The journeyId has more steps than the plan's per-journey step limit. Shorten the journey (re-record it) or upgrade the plan. |
no_url | A Web or TV scan requires a url. |
scan_type_mismatch | The scanType disagrees with the target/upload type. |
scan_in_progress | The target already has an active scan (public scans: the same URL is already queued or running; poll the returned statusUrl). |
target_quota_exceeded | persist: true would save a second test where the project already has one of the same family on that target type — one saved test per family, as in the dashboard (Web Compliance and Web Journey share the one website test; likewise TV and Android). |
invalid_target | The target is not valid for this scanType. |
dispatch_failed | The scan could not be enqueued. |
public_scans_disabled | Public scans are temporarily switched off by the operator. Retry later; keyed scans are unaffected. |
target_has_no_journeys | A target-scoped vendor-monitoring write had no journeys to fan out to. Record a journey on the target first. |
open_regression | Approving this missing event or param would remove an element that is marked as an error at the same step. Resolve that error first. |
internal_error | Unexpected server-side failure — safe to retry. |
file_too_large | The uploaded artifact exceeds the size limit. |
package_parse_failed | The APK/XAPK package name could not be parsed. |
upload_failed | The artifact could not be stored. |
score_below_threshold | gate: compliance score fell below minComplianceScore. |
datalayer_regressions | gate: new, unacknowledged dataLayer regressions were found. |
severity_exceeded | gate: a diff met or exceeded failOnSeverity. |
status_broken_step | gate/warning: the journey replay broke mid-flow. It fails only when explicitly configured as a generic API gate predicate; the fixed GitHub policy reports it as a non-blocking warning. |
status_failed | gate: the scan ended in a failed status. |
status_cancelled | gate: the scan ended cancelled. |
status_no_data | gate: the scan produced no trustworthy measurement, so it has no score and cannot be gated on one. Always fails, whether or not it is listed in failOnStatus. |
new_vendors | gate: a first-seen vendor appeared in the dataLayer. |
diffs_not_persisted | gate: the scan finished but its findings could not be stored (including a diff-engine error), so the diff list is incomplete. Always fails — an empty diff list here means "not written", not "nothing found". An automatic replay within 6 hours of the scan clears it when it succeeds; otherwise re-run the scan. |
verdict_scope_partial | gate: the consent banner could not be shown or offers no accept and no reject control, so the compliance score covers only how the page behaves before consent — the accept and reject flows were not tested. Always fails, whatever the score; score_below_threshold is still reported beside it when the score is also low. |
no_tracking_observed | gate: the page loaded but no tracking tool and no consent banner were observed on it, so it has no compliance score. Always fails — a preview that lost its tracking looks exactly like this. |
infrastructure_interrupted | gate: the scan was interrupted on the AnalyticsProof side (for example a restart, a shutdown or our own database failing), so the result says nothing about the page. Reported instead of status_failed, with no score_below_threshold beside it; always fails (the API stays fail-closed) — re-run the scan. failureOrigin on such a scan is normally "infrastructure", but failureOrigin is broader: a scanner-side failure can carry it and still fail as status_failed. |
CI-Rezepte
Lösen Sie einen Scan aus, fragen Sie erneut ab, bis er einen Endzustand erreicht und settled nicht mehr false ist, und werten Sie dann das Gate aus. Eine Endantwort mit settled: false ist vorläufig — die Befunde werden noch gespeichert. Bei einem Fehlschlag gibt das Rezept die betreffenden Diffs aus und endet mit einem Wert ungleich null. Ein Vorschau-Deployment zu prüfen, wie es diese Rezepte tun, erfordert den Professional-Tarif; einen gespeicherten Web-Journey-Test auslösen und abfragen geht ab dem Starter-Tarif, Web-Compliance-, Android- und TV-Läufe auf Abruf ab Professional.
Die ganze Anleitung: CI/CD mit der API
GitHub Action (mehrere Journeys)
Die Composite Action kapselt die gesamte Schleife aus Auslösen, Abfragen, Gate, SARIF und Kommentar in einem einzigen uses:-Block. Sie führt jede ausführbare, vom Kontingent zugelassene Journey gegen Ihre Vorschau-URL aus (Entwürfe und fehlerhafte Journeys eingeschlossen; pausierte ausgeschlossen), meldet nur direkte dataLayer-Änderungen und blockiert nur kritische Befunde. Warnungen, Hinweise und eine unvollständige Journey-Wiedergabe bleiben nicht blockierende Rückmeldungen; Ausführungs- und Scannerfehler schlagen weiterhin sicher fehl. Sie benötigt das Repository-Secret ANALYTICSPROOF_API_KEY sowie die Berechtigungen contents: read, pull-requests: write, security-events: write.
# .github/workflows/analyticsproof.yml
name: AnalyticsProof
on: pull_request
concurrency:
group: analyticsproof-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read # checkout
pull-requests: write # PR comment
security-events: write # SARIF upload
jobs:
gate:
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository
timeout-minutes: 20 # poll deadline (15 min default) + at least 5 min (start retries, SARIF, comment)
steps:
- uses: actions/checkout@v4
- uses: your-org/your-repo/.github/actions/analyticsproof@v1
with:
api-key: ${{ secrets.ANALYTICSPROOF_API_KEY }}
target-id: <TARGET_ID> # discover via: GET /api/v1/targets
url: https://preview-${{ github.event.number }}.example.com
all-journeys: 'true' # run EVERY runnable, quota-admitted journey
- if: always() && hashFiles('analyticsproof.sarif') != ''
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: analyticsproof.sarif
category: analyticsproofTest-ID ermitteln
# Find the TARGET_ID of a saved test (project-scoped):
curl -fsS -H "Authorization: Bearer $AP_KEY" \
"$BASE/api/v1/targets" | jq -r '.targets[] | "\(.id)\t\(.name)"'GitHub Actions
# .github/workflows/analyticsproof.yml — gate one journey of a PR preview (dry run)
# Run it only on pull requests (on: pull_request), so the preview belongs to the PR.
- name: AnalyticsProof dataLayer gate
env:
AP_KEY: ${{ secrets.ANALYTICSPROOF_API_KEY }}
BASE: https://analyticsproof.com
TARGET_ID: <TARGET_ID> # discover via: GET $BASE/api/v1/targets
PREVIEW_URL: https://preview-${{ github.event.number }}.example.com
run: |
set -euo pipefail
# scanType web_journey replays the first runnable journey (add "journeyId"
# to pick one); without it a website test runs its compliance audit.
# Key = commit + run id + run attempt + mode + target + preview-URL hash (add any
# journeyId you send): a retry replays its scan, a new push, workflow run or
# re-run gets a fresh one, and it stays under 200 chars.
URL_HASH=$(printf '%s' "$PREVIEW_URL" | cksum | cut -d' ' -f1)
SID=$(curl -fsS -X POST \
-H "Authorization: Bearer $AP_KEY" \
-H "Idempotency-Key: $GITHUB_SHA-$GITHUB_RUN_ID-${GITHUB_RUN_ATTEMPT:-1}-web_journey-$TARGET_ID-$URL_HASH" \
-H 'content-type: application/json' \
-d "$(jq -cn --arg url "$PREVIEW_URL" '{url: $url, scanType: "web_journey"}')" \
"$BASE/api/v1/targets/$TARGET_ID/scans" | jq -r .scanId)
# A journey is judged by its diffs; minComplianceScore belongs to a compliance test.
GATE=$(jq -rn '{failOnSeverity:"critical",failOnBrokenStep:true} | @uri')
# Re-poll until the scan reaches a terminal status and its findings have
# settled (a terminal answer with settled:false is provisional).
# ?wait long-polls up to 120s per attempt (server cap 300s).
RES=''
for _ in $(seq 1 20); do
RES=$(curl -fsS -H "Authorization: Bearer $AP_KEY" \
"$BASE/api/v1/scans/$SID?wait=120&gate=$GATE&include=diffs&scope=direct-datalayer")
case "$(echo "$RES" | jq -r 'if .settled == false then "settling" else .status end')" in
completed|failed|cancelled|broken_step|no_data) break ;;
esac
done
echo "$RES" | jq '{status, settled, score, gate, warnings}'
if [ "$(echo "$RES" | jq -r '.settled != false and .gate.passed == true')" != "true" ]; then
echo "::error::AnalyticsProof gate failed"
echo "$RES" | jq -r '.diffs[]? | " [\(.severity)] step \(.stepIndex) \(.vendor // "-"): \(.description)"'
exit 1
fiGitLab CI
# .gitlab-ci.yml — gate one journey of a merge-request preview (dry run)
analyticsproof:
stage: verify
image: alpine:latest
variables:
BASE: https://analyticsproof.com
TARGET_ID: <TARGET_ID> # discover via: GET $BASE/api/v1/targets
PREVIEW_URL: https://preview-$CI_MERGE_REQUEST_IID.example.com
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
before_script:
- apk add --no-cache curl jq
script:
- |
set -euo pipefail
# scanType web_journey replays the first runnable journey (add "journeyId"
# to pick one); without it a website test runs its compliance audit.
# Key = commit + job + mode + target + preview-URL hash (add any journeyId you
# send): a retry of the same request replays its scan, a new push or a job
# retry gets a fresh one, and it stays under 200 chars.
URL_HASH=$(printf '%s' "$PREVIEW_URL" | cksum | cut -d' ' -f1)
SID=$(curl -fsS -X POST \
-H "Authorization: Bearer $ANALYTICSPROOF_API_KEY" \
-H "Idempotency-Key: $CI_COMMIT_SHA-$CI_JOB_ID-web_journey-$TARGET_ID-$URL_HASH" \
-H 'content-type: application/json' \
-d "$(jq -cn --arg url "$PREVIEW_URL" '{url: $url, scanType: "web_journey"}')" \
"$BASE/api/v1/targets/$TARGET_ID/scans" | jq -r .scanId)
# A journey is judged by its diffs; minComplianceScore belongs to a compliance test.
GATE=$(jq -rn '{failOnSeverity:"critical",failOnBrokenStep:true} | @uri')
RES=''
for _ in $(seq 1 20); do
RES=$(curl -fsS -H "Authorization: Bearer $ANALYTICSPROOF_API_KEY" \
"$BASE/api/v1/scans/$SID?wait=120&gate=$GATE&include=diffs&scope=direct-datalayer")
case "$(echo "$RES" | jq -r 'if .settled == false then "settling" else .status end')" in
completed|failed|cancelled|broken_step|no_data) break ;;
esac
done
echo "$RES" | jq '{status, settled, score, gate, warnings}'
[ "$(echo "$RES" | jq -r '.settled != false and .gate.passed == true')" = "true" ] || {
echo "AnalyticsProof gate failed"
echo "$RES" | jq -r '.diffs[]?'
exit 1
}API-Änderungen
Seit 2026-10-10: Jede Journey überwacht standardmäßig dataLayer, adobeDataLayer, Google Analytics 4, Adobe Analytics (in einer mobilen App auch ihren eigenen Google Tag Manager-Container), jeweils nur, wenn vorhanden. Jeder Anbieter lässt sich über den Vendors-Endpunkt ein- und ausschalten; 422 vendor_not_alert_ready wird nicht mehr zurückgegeben. Approve und approve-all akzeptieren einen optionalen mode (standardmäßig required, oder optional). scope=direct-datalayer hängt nicht von der Überwachungsliste ab.
Für KI-Agenten
Ein Agent kann ohne Schlüssel den kostenlosen Web-Compliance-Scan einer öffentlichen URL anfordern: POST /api/v1/public/scans antwortet mit einem Slug zum Abfragen oder mit dem zwischengespeicherten Ergebnis, wenn die URL in den letzten 28 Tagen gescannt wurde (Query-String und Fragment werden ignoriert). Mit einem Schlüssel mit der Berechtigung read liefert GET /api/v1/scans/<Scan-ID>/evidence alles, was zu einem Scan gespeichert ist, mit Screenshot-Links, die 15 Minuten gültig sind.
# No key: request the free Web Compliance scan of one URL.
# 202 with a slug to poll, or 200 with cached: true and the result when the
# URL (query string and fragment ignored) was scanned in the last 28 days.
curl -fsS -X POST "$BASE/api/v1/public/scans" \
-H "Content-Type: application/json" -d '{"url":"https://www.example.com"}'
curl -fsS "$BASE/api/v1/public/scans/<SLUG>"
# With a key of permission read: everything stored about one scan.
# Screenshot links in it work without a session for 15 minutes.
curl -fsS -H "Authorization: Bearer $AP_KEY" \
"$BASE/api/v1/scans/<SCAN_ID>/evidence?steps=0,3"OpenAPI-Spezifikation
Die vollständige API wird durch ein maschinenlesbares OpenAPI-3.0-Dokument beschrieben — zum Lesen ist keine Authentifizierung erforderlich.
https://analyticsproof.com/api/v1/openapi.jsonDie Server-URL ist absolut, Sie können diese URL also direkt in editor.swagger.io oder Redoc (redocly.com/redoc) einfügen und die API von dort aufrufen.