API v1.2
Vývojářské API
REST API pro CI: spouštějte skeny, blokujte pull requesty při regresích dataLayer a compliance a načítejte výsledky přímo do své pipeline.
Autentizace
Každý požadavek nese projektový API klíč vytvořený v Nastavení projektu › API klíče. Posílejte jej jako Bearer token nebo hlavičku X-API-Key. Každý klíč má scope; celý klíč se zobrazí pouze jednou při vytvoření.
# Preferred
Authorization: Bearer ap_live_xxxxxxxxxxxxxxxx
# Alternative
X-API-Key: ap_live_xxxxxxxxxxxxxxxxRozsahy oprávnění (scopes)
| Scope | Popis |
|---|---|
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). |
datalayer:write | Acknowledge dataLayer diffs from CI: approve/ignore diffs, and set vendor-monitoring states across a target. |
admin | Manage the project’s own API keys (mint / list / revoke). An `admin` key cannot mint another `admin` key — that is dashboard-only. |
Endpointy
Všechny endpointy vycházejí ze základní URL API a vyžadují uvedený scope.
| Metoda | Endpoint | Scope | Shrnutí |
|---|---|---|---|
| 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 |
| POST | /api/v1/scans/{id}/explain | scan:read | Explain a scan in plain language |
| 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 |
| POST | /api/v1/journeys/{id}/datalayer/approve | datalayer:write | Approve a dataLayer diff into the journey baseline |
| POST | /api/v1/journeys/{id}/datalayer/approve-all | datalayer:write | Approve all pending dataLayer diffs for a scan |
| POST | /api/v1/journeys/{id}/datalayer/ignore | datalayer:write | Ignore a dataLayer element journey-wide |
| DELETE | /api/v1/journeys/{id}/datalayer/ignores/{decisionId} | datalayer:write | Remove a standing dataLayer ignore |
| GET | /api/v1/journeys/{id}/datalayer/decisions | scan:read | List standing dataLayer ignore decisions |
| PUT | /api/v1/targets/{id}/vendors | datalayer:write | Set vendor-monitoring states across a target's journeys |
| GET | /api/v1/whoami | 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 |
Možnosti CI brány
Při dotazování na sken předejte URL-zakódovaný JSON jako ?gate=. Predikáty se kombinují pomocí OR — brána selže, pokud vyhoví kterýkoli predikát. CI by mělo skončit nenulovým kódem, když je gate.passed false.
| Možnost | Typ | Popis |
|---|---|---|
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" | "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 first-seen vendor appears in the monitored dataLayer (new_vendors). |
Detail diffů (include=diffs)
Přidejte ?include=diffs k dotazu na sken, aby se vložil úplný seznam nevyřešených diffů. Diff je hodnotově slepý: hlásí názvy parametrů a JS typy každé události, nikdy jejich hodnoty.
# 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.Náhledová brána v režimu dry-run
Nasměrujte uložený test na náhledovou URL konkrétního PR a zablokujte nasazení bez poškození produkční baseline. Propojená cesta se přehraje v režimu dry-run: vyprodukuje diffy a skóre pro bránu, ale nic nezapíše zpět do cesty.
# 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.Kurzorové stránkování
Endpointy se seznamy používají keyset stránkování. Následujte nextCursor, dokud není null; starší příznak truncated je zachován kvůli zpětné kompatibilitě.
# 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"Limity požadavků
Limity se vynucují na úrovni API klíče a jsou uvedeny v každé odpovědi.
- 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.
Idempotence
Endpointy pro spuštění a opětovné spuštění lze bezpečně opakovat s idempotenčním klíčem.
- 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"Chybové kódy
Každá chybová odpověď má stabilní tvar: objekt error s poli code, message a volitelným details, plus requestId na nejvyšší úrovni. Kódy selhání brány se objevují v gate.failures[].code, nikoli v error.code.
| Kód | Popis |
|---|---|
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. |
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. |
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. |
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. |
target_has_no_journeys | A target-scoped vendor-monitoring write had no journeys to fan out to. Record a journey on the target 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, so the diff list is incomplete. Always fails — an empty diff list here means "not written", not "nothing found". Re-run the scan. |
CI recepty
Spusťte sken, opakovaně se dotazujte, dokud nedosáhne koncového stavu, a poté vyhodnoťte bránu. Při selhání recept vypíše problematické diffy a skončí nenulovým kódem.
GitHub Action (více cest)
Kompozitní action zabalí celou smyčku spuštění, dotazování, brány, SARIF a komentáře do jediného bloku uses:. Proti náhledové URL přehraje všechny spustitelné cesty připuštěné kvótou (koncepty a rozbité cesty zahrne, pozastavené vynechá), hlásí pouze přímé změny dataLayer a blokuje jen kritické nálezy. Varování, informace a nedokončené přehrání cesty zůstávají neblokující zpětnou vazbou; chyby spuštění a scanneru nadále bezpečně selžou. Vyžaduje tajný klíč repozitáře ANALYTICSPROOF_API_KEY a oprávnění 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 # above the Action's default 15-minute poll deadline
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: analyticsproofZjištění ID testu
# 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://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 '{failOnSeverity:"critical"} | @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&scope=direct-datalayer")
case "$(echo "$RES" | jq -r .status)" in
completed|failed|cancelled|broken_step|no_data) 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
fiGitLab 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://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://analyticsproof.com/api/v1/scans/$SID?wait=120&gate=$GATE&include=diffs")
case "$(echo "$RES" | jq -r .status)" in
completed|failed|cancelled|broken_step|no_data) 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 specifikace
Celé API popisuje strojově čitelný dokument OpenAPI 3.0 — ke čtení není potřeba autentizace.
https://analyticsproof.com/api/v1/openapi.jsonURL serveru je absolutní, takže tuto URL můžete vložit přímo do editor.swagger.io nebo Redoc (redocly.com/redoc) a volat API odtud.