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. Senden Sie ihn als Bearer-Token oder X-API-Key-Header. Jeder Schlüssel hat Scopes; der vollständige Schlüssel wird nur einmal bei der Erstellung angezeigt.
# Preferred
Authorization: Bearer ap_live_xxxxxxxxxxxxxxxx
# Alternative
X-API-Key: ap_live_xxxxxxxxxxxxxxxx Scopes
| Scope | Beschreibung |
|---|---|
scan:read | List targets, journeys, and scan history; poll a scan and evaluate a CI gate. |
scan:write | Trigger ad-hoc scans, re-run saved tests, and cancel a pending/running scan. |
upload:write | Upload a CI APK/XAPK artifact for a mobile scan (valid 14 days). |
Endpunkte
Alle Endpunkte hängen an der API-Basis-URL und erfordern den angegebenen Scope.
| Methode | Endpunkt | Scope | Zusammenfassung |
|---|---|---|---|
| GET | /api/v1/scans | scan:read | List scan history |
| POST | /api/v1/scans | scan:write | Trigger an ad-hoc scan |
| GET | /api/v1/scans/{id} | scan:read | Poll a scan + evaluate a CI gate |
| DELETE | /api/v1/scans/{id} | scan:write | Cancel a pending/running scan |
| GET | /api/v1/targets | scan:read | List saved tests |
| POST | /api/v1/targets/{id}/scans | scan:write | Re-run a saved test |
| GET | /api/v1/journeys | scan:read | List saved journeys |
| POST | /api/v1/uploads | upload:write | Upload a CI APK/XAPK |
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). |
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"[] | Fail when the scan ends in any of the listed statuses (status_<status>). |
failOnNewVendors | boolean | Fail when a first-seen vendor appears in the monitored dataLayer (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. elementKey is
# "event:<vendor>::<name>", "param:<vendor>::<event>::<param>", or
# "consent:<vendor>::<event>::<signal>".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. Die verknüpfte Journey wird als Dry-Run wiedergegeben: Sie erzeugt Diffs und einen Score für das Gate, schreibt aber nichts in die Journey zurück.
# Gate a per-PR preview deploy WITHOUT mutating the saved journey baseline.
# The target's linked journey is replayed against $PREVIEW_URL as a dry-run:
# it still produces diffs + score for the gate, but writes nothing back to the
# journey's expected schema, vendor watch-list, or regressions.
SID=$(curl -fsS -X POST \
-H "Authorization: Bearer $AP_KEY" \
-H 'content-type: application/json' \
-d "{\"url\":\"$PREVIEW_URL\"}" \
"$BASE/api/v1/targets/$TARGET_ID/scans" | jq -r .scanId)
# 400 journey_required is returned when the target has no journey to replay.Cursor-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. - An in-flight duplicate is held for 5 minutes; a completed key replays for 24 hours. A replay returns
200withidempotentReplay: 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. |
tier_forbidden | The project tier does not include programmatic API access (Enterprise only). |
insufficient_scope | The key lacks the scope this operation requires. |
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. |
not_cancellable | The scan is already terminal and cannot be cancelled. |
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. |
no_url | A web/hbbtv 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. |
invalid_target | The target is not valid for this scanType. |
dispatch_failed | The scan could not be enqueued. |
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: the journey replay broke mid-flow. |
status_failed | gate: the scan ended in a failed status. |
status_cancelled | gate: the scan ended cancelled. |
new_vendors | gate: a first-seen vendor appeared in the dataLayer. |
CI-Rezepte
Lösen Sie einen Scan aus, fragen Sie erneut ab, bis er einen Endzustand erreicht, und werten Sie dann das Gate aus. Bei einem Fehlschlag gibt das Rezept die betreffenden Diffs aus und endet mit einem Wert ungleich null.
Discover the target ID
# 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
- name: AnalyticsProof compliance gate
env:
AP_KEY: ${{ secrets.ANALYTICSPROOF_API_KEY }}
BASE: https://app.analyticsproof.com
TARGET_ID: <TARGET_ID> # discover via: GET $BASE/api/v1/targets
run: |
set -euo pipefail
# Trigger a re-scan of the saved target (idempotent per commit SHA)
SID=$(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" | jq -r .scanId)
GATE=$(jq -rn '{minComplianceScore:90,failOnNewDataLayerRegressions:true,failOnBrokenStep:true} | @uri')
# Re-poll until the scan reaches a terminal status.
# ?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")
case "$(echo "$RES" | jq -r .status)" in
completed|failed|cancelled|broken_step) break ;;
esac
done
echo "$RES" | jq '{status, score, gate, warnings}'
if [ "$(echo "$RES" | jq -r '.gate.passed')" != "true" ]; then
echo "::error::AnalyticsProof gate failed"
echo "$RES" | jq -r '.diffs[]? | " [\(.severity)] step \(.stepIndex) \(.vendor // "-"): \(.description)"'
exit 1
fi GitLab CI
# .gitlab-ci.yml
analyticsproof:
stage: verify
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
set -euo pipefail
SID=$(curl -fsS -X POST \
-H "Authorization: Bearer $ANALYTICSPROOF_API_KEY" \
-H "Idempotency-Key: $CI_COMMIT_SHA" \
-H 'content-type: application/json' -d '{}' \
"https://app.analyticsproof.com/api/v1/targets/$TARGET_ID/scans" | jq -r .scanId)
GATE=$(jq -rn '{minComplianceScore:90,failOnNewDataLayerRegressions:true,failOnBrokenStep:true} | @uri')
RES=''
for _ in $(seq 1 20); do
RES=$(curl -fsS -H "Authorization: Bearer $ANALYTICSPROOF_API_KEY" \
"https://app.analyticsproof.com/api/v1/scans/$SID?wait=120&gate=$GATE&include=diffs")
case "$(echo "$RES" | jq -r .status)" in
completed|failed|cancelled|broken_step) break ;;
esac
done
echo "$RES" | jq '{status, score, gate, warnings}'
[ "$(echo "$RES" | jq -r '.gate.passed')" = "true" ] || {
echo "Compliance gate failed"
echo "$RES" | jq -r '.diffs[]?'
exit 1
}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.json Die 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.