API publique pour les scans, le monitoring, la corrélation de versions et les workflows de portefeuille.
Une source unique de vérité, des modèles de requêtes et réponses typés, un OpenAPI téléchargeable et un client TypeScript généré pour des intégrations professionnelles.
Authentification
Utilisez `Authorization: Bearer <api_key>`. Les clés API sont limitées en portée et gérées depuis les Paramètres du compte.
Limites de débit
Le budget par défaut de l'API publique est de `100 req/min` par clé, avec des limites plus strictes par endpoint documentées dans la spécification.
Modèle Webhook
Configurez les livraisons sortantes pour `scan_complete` et `regression_alert`, signées avec des en-têtes HMAC documentés ci-dessous.
Premier scan
cURLcurl -fsS -X POST "$BASE_URL/api/v1/scan" \
-H "Authorization: Bearer $GDPRMONITOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"viewport": "desktop"
}'Client TypeScript
SDK généréimport { Configuration, ScansApi } from "@gdprmonitor/public-api-client";
const client = new ScansApi(
new Configuration({
basePath: process.env.GDPRMONITOR_BASE_URL,
accessToken: process.env.GDPRMONITOR_API_KEY,
})
);
const queued = await client.createScan({
scanCreateRequest: {
url: "https://example.com",
viewport: "desktop",
},
});
const result = await client.getScanStatus({ scanId: queued.id });Référence
Documentation API en direct
La référence ci-dessous est rendue directement à partir du même contrat `docs/openapi.yaml` utilisé pour la génération du SDK et les vérifications de dérive CI.
Rendered contract
This view is rendered directly from `docs/openapi.yaml` without runtime style injection, so it remains compatible with the app CSP.
openapi: 3.1.0
info:
title: GDPR Privacy Monitor Public API
version: 1.0.0
summary: Contract-first public API for customer integrations.
description: |-
GDPR Privacy Monitor exposes a scoped bearer API for scans, monitoring, release correlation, portfolio reporting, and webhook configuration.
Design constraints:
- This specification covers only the customer-facing `api/v1` surface.
- Internal and admin endpoints are intentionally excluded.
- Authentication uses scoped bearer API keys, not OAuth2 tokens.
servers:
- url: https://gdprprivacymonitor.eu
description: Production public API base URL. Override `basePath` in the SDK for self-hosted or staging deployments.
tags:
- name: Scans
- name: Monitoring
- name: Release Markers
- name: Webhooks
- name: Portfolio
- name: Status
- name: Disputes
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: API Key
description: Scoped bearer API key managed in Account Settings.
parameters:
ScanID:
in: path
name: scanId
required: true
schema:
type: string
format: uuid
description: Scan UUID.
MonitorID:
in: path
name: monitorId
required: true
schema:
type: string
format: uuid
description: Monitor UUID.
MarkerID:
in: path
name: markerId
required: true
schema:
type: string
format: uuid
description: Release marker UUID.
responses:
BadRequest:
description: Invalid request payload, query, or path parameter.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
Unauthorized:
description: Missing or invalid API key.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
Forbidden:
description: Scope or plan restriction prevented this action.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
NotFound:
description: Resource not found.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
Conflict:
description: Resource state does not allow the requested action.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
Unprocessable:
description: Semantically invalid resource state or relationship.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
TooManyRequests:
description: Rate limit exceeded.
headers:
Retry-After:
schema:
type: string
description: Seconds until the next allowed request.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
InternalError:
description: Internal server error.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
ServiceUnavailable:
description: Temporary service or rate limit backend issue.
headers:
Retry-After:
schema:
type: string
description: Suggested retry window in seconds.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
examples:
WebhookScanComplete:
summary: scan_complete webhook delivery
value:
event_type: scan_complete
sent_at: '2026-03-03T12:00:00.000Z'
scan:
id: 11111111-1111-4111-8111-111111111111
url: https://example.com
monitor_id: 22222222-2222-4222-8222-222222222222
risk_score: 42
risk_level: Medium
regression_message: null
regression_details: {}
WebhookRegressionAlert:
summary: regression_alert webhook delivery
value:
event_type: regression_alert
sent_at: '2026-03-07T09:30:00.000Z'
scan:
id: 33333333-3333-4333-8333-333333333333
url: https://shop.example.com
monitor_id: 44444444-4444-4444-8444-444444444444
risk_score: 71
risk_level: High
regression_message: New trackers load before consent.
regression_details:
severity: critical
finding_types:
- new_tracker_detected
- risk_score_regression
schemas:
APIError:
type: object
properties:
error:
type: string
retry_after:
type: integer
minimum: 1
required:
- error
additionalProperties: false
OKResponse:
type: object
properties:
ok:
type: boolean
required:
- ok
additionalProperties: false
ScanCreateRequest:
type: object
properties:
url:
type: string
format: uri
viewport:
type: string
enum: [desktop, mobile]
default: desktop
required:
- url
additionalProperties: false
ScanSubmissionResponse:
type: object
properties:
id:
type: string
format: uuid
status:
type: string
enum: [queued]
required:
- id
- status
additionalProperties: false
ButtonMetrics:
type: object
properties:
tag_name:
type: string
background_color:
type: string
font_size_px:
type: number
color:
type: string
opacity:
type: number
display:
type: string
visibility:
type: string
width_px:
type: number
height_px:
type: number
required:
- tag_name
- background_color
- font_size_px
- color
- opacity
- display
- visibility
- width_px
- height_px
additionalProperties: false
ProminenceSignal:
type: object
properties:
name:
type: string
description:
type: string
is_dark_pattern:
type: boolean
weight:
type: number
required:
- name
- description
- is_dark_pattern
- weight
additionalProperties: false
ProminenceOutput:
type: object
properties:
suppressed:
type: boolean
confidence:
type: number
cannot_determine:
type: boolean
reason:
type: string
accept_metrics:
$ref: '#/components/schemas/ButtonMetrics'
reject_metrics:
$ref: '#/components/schemas/ButtonMetrics'
signals:
type: array
items:
$ref: '#/components/schemas/ProminenceSignal'
required:
- suppressed
- confidence
- cannot_determine
- accept_metrics
- reject_metrics
additionalProperties: false
BannerOutput:
type: object
properties:
detected:
type: boolean
cmp:
type: string
cmp_level:
type: string
detection_mode:
type: string
screenshot_mode:
type: string
reject_available:
type: boolean
reject_below_fold:
type: boolean
cookie_wall:
type: boolean
prominence:
$ref: '#/components/schemas/ProminenceOutput'
vendor_list:
$ref: '#/components/schemas/VendorListOutput'
first_layer:
$ref: '#/components/schemas/FirstLayerOutput'
required:
- detected
additionalProperties: false
CookieOutput:
type: object
properties:
name:
type: string
domain:
type: string
category:
type: string
service:
type: string
required:
- name
- domain
- category
additionalProperties: false
TrackerOutput:
type: object
properties:
service:
type: string
category:
type: string
url:
type: string
info_only:
type: boolean
required:
- service
- category
- url
additionalProperties: false
CookieDiffOutput:
type: object
properties:
pre_consent:
type: array
items:
$ref: '#/components/schemas/CookieOutput'
post_consent_only:
type: array
items:
$ref: '#/components/schemas/CookieOutput'
persistent:
type: array
items:
$ref: '#/components/schemas/CookieOutput'
required:
- pre_consent
- post_consent_only
- persistent
additionalProperties: false
TrackerDiffOutput:
type: object
properties:
pre_consent:
type: array
items:
$ref: '#/components/schemas/TrackerOutput'
post_consent_only:
type: array
items:
$ref: '#/components/schemas/TrackerOutput'
required:
- pre_consent
- post_consent_only
additionalProperties: false
ThirdPartyDomainOutput:
type: object
properties:
domain:
type: string
request_count:
type: integer
required:
- domain
- request_count
additionalProperties: false
ThirdPartyDomainsOutput:
type: object
properties:
count:
type: integer
top:
type: array
items:
$ref: '#/components/schemas/ThirdPartyDomainOutput'
required:
- count
- top
additionalProperties: false
PreferenceCategoryOutput:
type: object
properties:
category:
type: string
label:
type: string
enabled_by_default:
type: boolean
disabled:
type: boolean
required:
- category
- label
- enabled_by_default
additionalProperties: false
PreferenceCategoryScanOutput:
type: object
properties:
category:
type: string
label:
type: string
cookies:
type: array
items:
$ref: '#/components/schemas/CookieOutput'
trackers:
type: array
items:
$ref: '#/components/schemas/TrackerOutput'
removal_verified:
type: boolean
warnings:
type: array
items:
type: string
required:
- category
- label
additionalProperties: false
PreferenceCenterOutput:
type: object
properties:
detected:
type: boolean
categories:
type: array
items:
$ref: '#/components/schemas/PreferenceCategoryOutput'
category_scans:
type: array
items:
$ref: '#/components/schemas/PreferenceCategoryScanOutput'
withdrawal_reachability:
$ref: '#/components/schemas/WithdrawalReachabilityOutput'
warnings:
type: array
items:
type: string
required:
- detected
additionalProperties: false
ConsentProofOutput:
type: object
properties:
phase:
type: string
context:
type: string
api:
type: string
available:
type: boolean
tc_string_present:
type: boolean
gdpr_applies:
type:
- boolean
- 'null'
purpose_consents_granted:
type: integer
vendor_consents_granted:
type: integer
gpp_string_present:
type: boolean
capture_error:
type: string
required:
- phase
- context
- available
additionalProperties: false
VendorListOutput:
type: object
properties:
tcf_detected:
type: boolean
found:
type: boolean
layer:
type: string
text_snippet:
type: string
href:
type: string
visible_in_dom:
type: boolean
required:
- tcf_detected
- found
additionalProperties: false
FirstLayerOutput:
type: object
properties:
banner_text:
type: string
policy_link_found:
type: boolean
policy_link_href:
type: string
policy_link_text:
type: string
purpose_keywords:
type: array
items:
type: string
purposes_found:
type: boolean
controller_found:
type: boolean
confidence:
type: string
required:
- policy_link_found
- purposes_found
- controller_found
additionalProperties: false
WithdrawalReachabilityOutput:
type: object
properties:
evaluated:
type: boolean
reachable:
type: boolean
method:
type: string
api_only:
type: boolean
launcher_text:
type: string
withdrawal_clicks:
type: integer
accept_clicks:
type: integer
required:
- evaluated
- reachable
additionalProperties: false
RejectFlowOutput:
type: object
properties:
executed:
type: boolean
reject_worked:
type: boolean
inconclusive:
type: boolean
persistent_cookies:
type: array
items:
$ref: '#/components/schemas/CookieOutput'
persistent_trackers:
type: array
items:
$ref: '#/components/schemas/TrackerOutput'
warnings:
type: array
items:
type: string
required:
- executed
- reject_worked
additionalProperties: false
AccessibilityCheckOutput:
type: object
properties:
name:
type: string
wcag_criterion:
type: string
passed:
type: boolean
severity:
type: string
description:
type: string
details:
type: string
recommendation:
type: string
required:
- name
- wcag_criterion
- passed
- severity
- description
additionalProperties: false
AccessibilityOutput:
type: object
properties:
score:
type: number
level:
type: string
checks:
oneOf:
- type: array
items:
$ref: '#/components/schemas/AccessibilityCheckOutput'
- type: 'null'
warnings:
type: array
items:
type: string
required:
- score
- level
additionalProperties: false
ConsentModeCheckOutput:
type: object
properties:
parameter:
type: string
declared_state:
type: string
phase:
type: string
passed:
type: boolean
violations:
type: array
items:
type: string
required:
- parameter
- declared_state
- phase
- passed
additionalProperties: false
ConsentModeMismatchOutput:
type: object
properties:
parameter:
type: string
declared:
type: string
observed:
type: string
phase:
type: string
severity:
type: string
required:
- parameter
- declared
- observed
- phase
- severity
additionalProperties: false
ConsentMap:
type: object
additionalProperties:
type: string
ConsentModeOutput:
type: object
properties:
detected:
type: boolean
mode:
type: string
google_signals_present:
type: boolean
default_consent:
$ref: '#/components/schemas/ConsentMap'
update_consent:
$ref: '#/components/schemas/ConsentMap'
update_timing_ms:
type: integer
update_timing_basis:
type: string
default_conflicts:
type: array
items:
type: string
update_conflicts:
type: array
items:
type: string
server_side_gtm_detected:
type: boolean
cannot_determine:
type: boolean
cannot_determine_reason:
type: string
extraction_error:
type: string
checks:
type: array
items:
$ref: '#/components/schemas/ConsentModeCheckOutput'
mismatches:
type: array
items:
$ref: '#/components/schemas/ConsentModeMismatchOutput'
required:
- detected
- mode
- google_signals_present
additionalProperties: false
FingerprintSignalOutput:
type: object
properties:
api:
type: string
detail:
type: string
count:
type: integer
phase:
type: string
stack:
type: string
required:
- api
- count
- phase
additionalProperties: false
FingerprintingOutput:
type: object
properties:
pre_consent_score:
type: number
pre_consent_likelihood:
type: string
pre_consent_signals:
type: array
items:
$ref: '#/components/schemas/FingerprintSignalOutput'
post_consent_signals:
type: array
items:
$ref: '#/components/schemas/FingerprintSignalOutput'
suspected_scripts:
type: array
items:
type: string
required:
- pre_consent_score
- pre_consent_likelihood
additionalProperties: false
PolicyGapOutput:
type: object
properties:
type:
type: string
severity:
type: string
explanation:
type: string
policy_claim:
type: string
scan_evidence:
type: string
recommendation:
type: string
required:
- type
- severity
- explanation
additionalProperties: false
PolicyLLMOutput:
type: object
properties:
provider:
type: string
model:
type: string
input_tokens:
type: integer
output_tokens:
type: integer
estimated_cost_usd:
type: number
budget_usd:
type: number
skipped_reason:
type: string
required:
- provider
- model
- input_tokens
- output_tokens
- estimated_cost_usd
additionalProperties: false
PolicyAnalysisOutput:
type: object
properties:
enabled:
type: boolean
policy_url:
type: string
detected_by:
type: string
fetch_status:
type: string
language:
type: string
text_chars:
type: integer
gaps:
type: array
items:
$ref: '#/components/schemas/PolicyGapOutput'
summary:
type: string
warnings:
type: array
items:
type: string
llm:
$ref: '#/components/schemas/PolicyLLMOutput'
required:
- enabled
additionalProperties: false
FixGuideOutput:
type: object
properties:
title:
type: string
steps:
type: array
items:
type: string
docs_url:
type: string
format: uri
last_verified:
type: string
required:
- title
- steps
- last_verified
additionalProperties: false
FindingOutput:
type: object
properties:
type:
type: string
severity:
type: string
description:
type: string
points:
type: integer
count:
type: integer
fix_guide:
$ref: '#/components/schemas/FixGuideOutput'
required:
- type
- severity
- description
- points
additionalProperties: false
DataFlowCountryOutput:
type: object
properties:
organization_count:
type: integer
organizations:
type: array
items:
type: string
adequacy_status:
type: string
adequacy_note:
type: string
required:
- organization_count
- organizations
- adequacy_status
additionalProperties: false
DataFlowRowOutput:
type: object
properties:
domain:
type: string
organization:
type: string
country:
type: string
country_name:
type: string
adequacy_status:
type: string
request_count:
type: integer
pre_consent:
type: boolean
required:
- domain
- organization
- country
- country_name
- adequacy_status
- request_count
- pre_consent
additionalProperties: false
DataFlowCountriesMap:
type: object
additionalProperties:
$ref: '#/components/schemas/DataFlowCountryOutput'
DataFlowOutput:
type: object
properties:
total_third_party_domains:
type: integer
total_organizations:
type: integer
countries:
$ref: '#/components/schemas/DataFlowCountriesMap'
flows:
type: array
items:
$ref: '#/components/schemas/DataFlowRowOutput'
unmapped_domains:
type: array
items:
type: string
required:
- total_third_party_domains
- total_organizations
- countries
- flows
additionalProperties: false
ScanReliabilityOutput:
type: object
properties:
level:
type: string
enum: [high, degraded]
request_log_truncated:
type: boolean
dropped_requests:
type: integer
har_excluded:
type: boolean
pdf_failed:
type: boolean
required:
- level
additionalProperties: false
MetadataOutput:
type: object
properties:
scanner_version:
type: string
wait_strategy:
type: string
viewport:
type: string
selector_version:
type: string
required:
- scanner_version
- wait_strategy
- viewport
additionalProperties: false
EnforcementNotableCaseOutput:
type: object
properties:
company:
type: string
fine:
type: string
date:
type: string
required:
- company
- fine
- date
additionalProperties: false
EnforcementFindingContextOutput:
type: object
properties:
finding_type:
type: string
enforcement_level:
type: string
historical_actions:
type: integer
historical_fines_total:
type: string
notable_case:
$ref: '#/components/schemas/EnforcementNotableCaseOutput'
context_text:
type: string
required:
- finding_type
- enforcement_level
- historical_actions
- historical_fines_total
- context_text
additionalProperties: false
EnforcementDPAOutput:
type: object
properties:
code:
type: string
name:
type: string
country:
type: string
summary:
type: string
required:
- code
- name
- country
- summary
additionalProperties: false
EnforcementContextOutput:
type: object
properties:
jurisdiction:
type: string
dpa:
oneOf:
- $ref: '#/components/schemas/EnforcementDPAOutput'
- type: 'null'
findings:
type: array
items:
$ref: '#/components/schemas/EnforcementFindingContextOutput'
overall_risk_context:
type: string
disclaimer:
type: string
db_version:
type: string
required:
- jurisdiction
- dpa
- findings
- overall_risk_context
- disclaimer
- db_version
additionalProperties: false
ScanResultOutput:
type: object
properties:
url:
type: string
format: uri
final_url:
type: string
format: uri
scan_duration_ms:
type: integer
risk_score:
type: integer
risk_level:
type: string
banner:
$ref: '#/components/schemas/BannerOutput'
preference_center:
$ref: '#/components/schemas/PreferenceCenterOutput'
reject_flow:
$ref: '#/components/schemas/RejectFlowOutput'
accessibility:
$ref: '#/components/schemas/AccessibilityOutput'
consent_mode:
$ref: '#/components/schemas/ConsentModeOutput'
fingerprinting:
$ref: '#/components/schemas/FingerprintingOutput'
policy_analysis:
$ref: '#/components/schemas/PolicyAnalysisOutput'
findings:
type: array
items:
$ref: '#/components/schemas/FindingOutput'
cookies:
$ref: '#/components/schemas/CookieDiffOutput'
trackers:
$ref: '#/components/schemas/TrackerDiffOutput'
third_party_domains:
$ref: '#/components/schemas/ThirdPartyDomainsOutput'
data_flows:
$ref: '#/components/schemas/DataFlowOutput'
consent_proof:
type: array
items:
$ref: '#/components/schemas/ConsentProofOutput'
enforcement_context:
$ref: '#/components/schemas/EnforcementContextOutput'
warnings:
type: array
items:
type: string
scan_reliability:
$ref: '#/components/schemas/ScanReliabilityOutput'
metadata:
$ref: '#/components/schemas/MetadataOutput'
required:
- url
- final_url
- scan_duration_ms
- risk_score
- risk_level
- banner
- findings
- cookies
- trackers
- third_party_domains
- warnings
- metadata
additionalProperties: false
ScanEvidenceArtifact:
type: object
properties:
kind:
type: string
artifact_id:
type: string
storage_key:
type: string
file_name:
type: string
content_type:
type: string
sha256:
type: string
bytes:
type: integer
required:
- kind
- artifact_id
- storage_key
- file_name
- content_type
- sha256
- bytes
additionalProperties: false
ScanEvidenceOutput:
type: object
properties:
anchored_at:
oneOf:
- type: string
format: date-time
- type: 'null'
expires_at:
oneOf:
- type: string
format: date-time
- type: 'null'
evidence_retention_days:
type:
- integer
- 'null'
legal_hold:
type: boolean
legal_hold_reason:
type:
- string
- 'null'
legal_hold_created_at:
oneOf:
- type: string
format: date-time
- type: 'null'
artifact_count:
type: integer
artifacts:
type: array
items:
$ref: '#/components/schemas/ScanEvidenceArtifact'
required:
- anchored_at
- expires_at
- evidence_retention_days
- legal_hold
- artifact_count
- artifacts
additionalProperties: false
ScanQueuedStateResponse:
type: object
properties:
id:
type: string
format: uuid
status:
type: string
enum: [queued]
url:
type: string
format: uri
viewport:
type: string
enum: [desktop, mobile]
created_at:
type: string
format: date-time
required:
- id
- status
- url
- viewport
- created_at
additionalProperties: false
ScanRunningStateResponse:
type: object
properties:
id:
type: string
format: uuid
status:
type: string
enum: [running]
url:
type: string
format: uri
viewport:
type: string
enum: [desktop, mobile]
created_at:
type: string
format: date-time
required:
- id
- status
- url
- viewport
- created_at
additionalProperties: false
ScanFailedStateResponse:
type: object
properties:
id:
type: string
format: uuid
status:
type: string
enum: [failed]
url:
type: string
format: uri
viewport:
type: string
enum: [desktop, mobile]
error:
type: string
created_at:
type: string
format: date-time
evidence:
$ref: '#/components/schemas/ScanEvidenceOutput'
required:
- id
- status
- url
- viewport
- error
- created_at
- evidence
additionalProperties: false
ScanCompletedStateResponse:
type: object
properties:
id:
type: string
format: uuid
status:
type: string
enum: [completed]
url:
type: string
format: uri
viewport:
type: string
enum: [desktop, mobile]
created_at:
type: string
format: date-time
duration_ms:
type: integer
risk_score:
type:
- integer
- 'null'
risk_level:
type:
- string
- 'null'
result:
$ref: '#/components/schemas/ScanResultOutput'
evidence:
$ref: '#/components/schemas/ScanEvidenceOutput'
required:
- id
- status
- url
- viewport
- created_at
- result
- evidence
additionalProperties: false
ScanStatusResponse:
oneOf:
- $ref: '#/components/schemas/ScanQueuedStateResponse'
- $ref: '#/components/schemas/ScanRunningStateResponse'
- $ref: '#/components/schemas/ScanFailedStateResponse'
- $ref: '#/components/schemas/ScanCompletedStateResponse'
DriftAlert:
type: object
properties:
type:
type: string
enum:
- new_tracker_detected
- tracker_removed
- new_preconsent_cookie
- cookie_category_changed
- banner_configuration_changed
- reject_button_missing
- reject_button_added
- new_third_party_domain
- risk_score_regression
- risk_score_improvement
- new_finding_type
- finding_resolved
severity:
type: string
enum: [high, medium, low, info]
title:
type: string
description:
type: string
key:
type: string
details:
type: object
additionalProperties: true
required:
- type
- severity
- title
- description
additionalProperties: false
ScanDiff:
type: object
properties:
score_change:
type: integer
alerts:
type: array
items:
$ref: '#/components/schemas/DriftAlert'
summary:
type: string
required:
- score_change
- alerts
- summary
additionalProperties: false
ScanCompareSnapshot:
type: object
properties:
id:
type: string
format: uuid
url:
type: string
format: uri
created_at:
type: string
format: date-time
risk_score:
type:
- integer
- 'null'
findings_count:
type: integer
evidence:
$ref: '#/components/schemas/ScanEvidenceOutput'
required:
- id
- url
- created_at
- risk_score
- findings_count
- evidence
additionalProperties: false
ScanCompareDelta:
type: object
properties:
risk_score:
type:
- integer
- 'null'
findings_count:
type:
- integer
- 'null'
new_findings:
type: array
items:
type: string
resolved_findings:
type: array
items:
type: string
required:
- risk_score
- findings_count
- new_findings
- resolved_findings
additionalProperties: false
ReleaseMarker:
type: object
properties:
id:
type: string
format: uuid
label:
type: string
version:
type:
- string
- 'null'
description:
type:
- string
- 'null'
released_at:
type: string
format: date-time
source:
type: string
enum: [manual, api]
metadata:
type: object
additionalProperties: true
created_at:
type: string
format: date-time
required:
- id
- label
- version
- description
- released_at
- source
- metadata
- created_at
additionalProperties: false
ReleaseCorrelation:
type: object
properties:
marker:
$ref: '#/components/schemas/ReleaseMarker'
status:
type: string
enum: [awaiting_scan, regression, stable, improvement]
confidence:
type: string
enum: [high, medium, low]
reason:
type: string
disclaimer:
type: string
regression_detected:
type: boolean
hours_to_first_scan:
type:
- number
- 'null'
marker_overlap_count:
type: integer
baseline_scan_id:
type:
- string
- 'null'
baseline_scan_created_at:
oneOf:
- type: string
format: date-time
- type: 'null'
correlated_scan_id:
type:
- string
- 'null'
correlated_scan_created_at:
oneOf:
- type: string
format: date-time
- type: 'null'
current_scan_is_first_post_release:
type: boolean
score_delta:
type:
- integer
- 'null'
diff_summary:
type:
- string
- 'null'
diff_alert_types:
type: array
items:
type: string
regression_alert_id:
type:
- string
- 'null'
regression_alert_severity:
type:
- string
- 'null'
required:
- marker
- status
- confidence
- reason
- disclaimer
- regression_detected
- hours_to_first_scan
- marker_overlap_count
- baseline_scan_id
- baseline_scan_created_at
- correlated_scan_id
- correlated_scan_created_at
- current_scan_is_first_post_release
- score_delta
- diff_summary
- diff_alert_types
- regression_alert_id
- regression_alert_severity
additionalProperties: false
ScanCompareResponse:
type: object
properties:
baseline_kind:
type: string
enum: [latest]
current:
$ref: '#/components/schemas/ScanCompareSnapshot'
baseline:
oneOf:
- $ref: '#/components/schemas/ScanCompareSnapshot'
- type: 'null'
delta:
$ref: '#/components/schemas/ScanCompareDelta'
diff:
oneOf:
- $ref: '#/components/schemas/ScanDiff'
- type: 'null'
release_correlation:
oneOf:
- $ref: '#/components/schemas/ReleaseCorrelation'
- type: 'null'
required:
- baseline_kind
- current
- baseline
- delta
- diff
- release_correlation
additionalProperties: false
ScanDiffSnapshot:
type: object
properties:
id:
type: string
format: uuid
url:
type: string
format: uri
created_at:
type: string
format: date-time
risk_score:
type:
- integer
- 'null'
evidence:
$ref: '#/components/schemas/ScanEvidenceOutput'
required:
- id
- url
- created_at
- risk_score
- evidence
additionalProperties: false
ScanDiffResponse:
type: object
properties:
previous:
$ref: '#/components/schemas/ScanDiffSnapshot'
current:
$ref: '#/components/schemas/ScanDiffSnapshot'
diff:
$ref: '#/components/schemas/ScanDiff'
release_correlation:
oneOf:
- $ref: '#/components/schemas/ReleaseCorrelation'
- type: 'null'
required:
- previous
- current
- diff
- release_correlation
additionalProperties: false
NumericMetricBenchmark:
type: object
properties:
value:
type: number
percentile:
type: number
average:
type: number
best_in_class:
type: number
required:
- value
- percentile
- average
- best_in_class
additionalProperties: false
BooleanMetricBenchmark:
type: object
properties:
value:
type: boolean
percentage_true:
type: number
required:
- value
- percentage_true
additionalProperties: false
ScanBenchmarkResponse:
type: object
properties:
available:
type: boolean
sample_size:
type: integer
period:
type: string
enum: [all_time, last_90d]
last_computed:
oneOf:
- type: string
format: date-time
- type: 'null'
reason:
type: string
enum: [insufficient_sample, benchmark_unavailable]
risk_score:
$ref: '#/components/schemas/NumericMetricBenchmark'
pre_consent_cookie_count:
$ref: '#/components/schemas/NumericMetricBenchmark'
pre_consent_tracker_count:
$ref: '#/components/schemas/NumericMetricBenchmark'
third_party_domain_count:
$ref: '#/components/schemas/NumericMetricBenchmark'
has_banner:
$ref: '#/components/schemas/BooleanMetricBenchmark'
has_reject:
$ref: '#/components/schemas/BooleanMetricBenchmark'
reject_suppressed:
$ref: '#/components/schemas/BooleanMetricBenchmark'
required:
- available
- sample_size
- period
- last_computed
additionalProperties: false
RiskScoreTrendPoint:
type: object
properties:
scan_id:
type: string
format: uuid
date:
type: string
format: date-time
risk_score:
type: integer
scanner_version:
type: string
required:
- scan_id
- date
- risk_score
- scanner_version
additionalProperties: false
RiskScoreTrendSeries:
type: object
properties:
viewport:
type: string
enum: [desktop, mobile]
trend:
type: string
enum: [improving, degrading, stable, insufficient_data]
scanner_versions:
type: array
items:
type: string
total_points:
type: integer
returned_points:
type: integer
truncated_points:
type: integer
points:
type: array
items:
$ref: '#/components/schemas/RiskScoreTrendPoint'
required:
- viewport
- trend
- scanner_versions
- total_points
- returned_points
- truncated_points
- points
additionalProperties: false
RiskScoreTrendResponse:
type: object
properties:
url:
type: string
format: uri
days:
oneOf:
- type: integer
enum: [30, 90, 365]
- type: 'null'
excluded_scan_count:
type: integer
series:
type: array
items:
$ref: '#/components/schemas/RiskScoreTrendSeries'
required:
- url
- days
- excluded_scan_count
- series
additionalProperties: false
LastScan:
type: object
properties:
id:
type: string
format: uuid
status:
type: string
risk_score:
type:
- integer
- 'null'
risk_level:
type:
- string
- 'null'
created_at:
type: string
format: date-time
required:
- id
- status
- risk_score
- risk_level
- created_at
additionalProperties: false
MonitorAlertConfig:
type: object
properties:
minimum_severity:
type: string
enum: [high, medium, low, info]
enabled_types:
type: array
items:
type: string
required:
- minimum_severity
- enabled_types
additionalProperties: false
Monitor:
type: object
properties:
id:
type: string
format: uuid
url:
type: string
format: uri
interval_hours:
type: integer
evidence_retention_days:
type:
- integer
- 'null'
enabled:
type: boolean
next_run_at:
type: string
format: date-time
last_error:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
last_scan:
oneOf:
- $ref: '#/components/schemas/LastScan'
- type: 'null'
alert_count:
type: integer
alert_config:
$ref: '#/components/schemas/MonitorAlertConfig'
required:
- id
- url
- interval_hours
- evidence_retention_days
- enabled
- next_run_at
- last_error
- created_at
- updated_at
- last_scan
- alert_count
- alert_config
additionalProperties: false
MonitorUpsertRequest:
type: object
properties:
url:
type: string
format: uri
interval_hours:
type: integer
minimum: 1
maximum: 720
enabled:
type: boolean
required:
- url
- interval_hours
additionalProperties: false
MonitorPatchRequest:
type: object
properties:
interval_hours:
type: integer
minimum: 1
maximum: 720
enabled:
type: boolean
evidence_retention_days:
oneOf:
- type: integer
minimum: 7
maximum: 3650
- type: 'null'
alert_config:
$ref: '#/components/schemas/MonitorAlertConfig'
minProperties: 1
additionalProperties: false
MonitorListPagination:
type: object
properties:
page:
type: integer
page_size:
type: integer
total:
type: integer
total_pages:
type: integer
has_prev:
type: boolean
has_next:
type: boolean
required:
- page
- page_size
- total
- total_pages
- has_prev
- has_next
additionalProperties: false
MonitorListResponse:
type: object
properties:
monitors:
type: array
items:
$ref: '#/components/schemas/Monitor'
pagination:
$ref: '#/components/schemas/MonitorListPagination'
required:
- monitors
- pagination
additionalProperties: false
MonitorMutationResponse:
type: object
properties:
ok:
type: boolean
monitor:
$ref: '#/components/schemas/Monitor'
required:
- ok
- monitor
additionalProperties: false
ReleaseMarkerCreateRequest:
type: object
properties:
label:
type: string
maxLength: 120
version:
type: string
maxLength: 80
description:
type: string
maxLength: 500
released_at:
type: string
format: date-time
metadata:
type: object
additionalProperties: true
required:
- label
additionalProperties: false
ReleaseMarkersListResponse:
type: object
properties:
markers:
type: array
items:
$ref: '#/components/schemas/ReleaseMarker'
correlations:
type: array
items:
$ref: '#/components/schemas/ReleaseCorrelation'
required:
- markers
- correlations
additionalProperties: false
ReleaseMarkerMutationResponse:
type: object
properties:
ok:
type: boolean
marker:
$ref: '#/components/schemas/ReleaseMarker'
required:
- ok
- marker
additionalProperties: false
Webhook:
type: object
properties:
id:
type: string
format: uuid
url:
type: string
format: uri
active:
type: boolean
events:
type: array
minItems: 1
items:
type: string
enum: [scan_complete, regression_alert]
last_used_at:
oneOf:
- type: string
format: date-time
- type: 'null'
last_status:
type:
- string
- 'null'
last_error:
type:
- string
- 'null'
updated_at:
type: string
format: date-time
required:
- id
- url
- active
- events
- last_used_at
- last_status
- last_error
- updated_at
additionalProperties: false
WebhookConfigResponse:
type: object
properties:
webhook:
oneOf:
- $ref: '#/components/schemas/Webhook'
- type: 'null'
required:
- webhook
additionalProperties: false
WebhookUpsertRequest:
type: object
properties:
url:
type: string
format: uri
events:
type: array
minItems: 1
items:
type: string
enum: [scan_complete, regression_alert]
active:
type: boolean
rotate_secret:
type: boolean
required:
- url
additionalProperties: false
WebhookUpsertResponse:
type: object
properties:
ok:
type: boolean
created:
type: boolean
webhook:
$ref: '#/components/schemas/Webhook'
signing_secret:
type: string
description: Returned only on create or when `rotate_secret=true`.
required:
- ok
- created
- webhook
additionalProperties: false
WebhookPayloadScan:
type: object
properties:
id:
type: string
format: uuid
url:
type: string
format: uri
monitor_id:
type:
- string
- 'null'
risk_score:
type:
- integer
- 'null'
risk_level:
type:
- string
- 'null'
regression_message:
type:
- string
- 'null'
regression_details:
type: object
additionalProperties: true
required:
- id
- url
additionalProperties: false
WebhookPayload:
type: object
properties:
event_type:
type: string
enum: [scan_complete, regression_alert]
sent_at:
type: string
format: date-time
scan:
$ref: '#/components/schemas/WebhookPayloadScan'
required:
- event_type
- sent_at
- scan
additionalProperties: false
DisputeCreateRequest:
type: object
properties:
scan_id:
type: string
format: uuid
finding_type:
type: string
finding_identifier:
type: string
reason:
type: string
required:
- scan_id
- finding_type
- finding_identifier
additionalProperties: false
DisputeCreateResponse:
type: object
properties:
dispute_id:
type: string
format: uuid
status:
type: string
required:
- dispute_id
- status
additionalProperties: false
PortfolioDomain:
type: object
properties:
id:
type: string
format: uuid
url:
type: string
format: uri
interval_hours:
type: integer
enabled:
type: boolean
cmp:
type:
- string
- 'null'
risk_score:
type:
- integer
- 'null'
risk_level:
type:
- string
- 'null'
score_delta:
type:
- integer
- 'null'
last_scan_at:
oneOf:
- type: string
format: date-time
- type: 'null'
last_completed_scan_at:
oneOf:
- type: string
format: date-time
- type: 'null'
last_scan_status:
type:
- string
- 'null'
last_scan_error:
type:
- string
- 'null'
pending_alerts_count:
type: integer
open_alerts_count:
type: integer
oldest_open_issue_at:
oneOf:
- type: string
format: date-time
- type: 'null'
next_run_at:
type: string
format: date-time
overdue_seconds:
type: integer
monitor_status:
type: string
enum: [active, paused, error, pending]
sla_status:
type: string
enum: [healthy, at_risk, breached, paused, pending]
benchmark_context:
oneOf:
- $ref: '#/components/schemas/PortfolioBenchmarkContext'
- type: 'null'
required:
- id
- url
- interval_hours
- enabled
- cmp
- risk_score
- risk_level
- score_delta
- last_scan_at
- last_completed_scan_at
- last_scan_status
- last_scan_error
- pending_alerts_count
- open_alerts_count
- oldest_open_issue_at
- next_run_at
- overdue_seconds
- monitor_status
- sla_status
additionalProperties: false
PortfolioBenchmarkContext:
type: object
properties:
percentile:
type: integer
previous_percentile:
type:
- integer
- 'null'
change:
type:
- integer
- 'null'
direction:
type: string
enum: [improving, worsening, stable, insufficient_data]
sample_size:
type: integer
last_computed:
oneOf:
- type: string
format: date-time
- type: 'null'
required:
- percentile
- previous_percentile
- change
- direction
- sample_size
- last_computed
additionalProperties: false
PortfolioSummary:
type: object
properties:
total:
type: integer
matching_total:
type: integer
high:
type: integer
medium:
type: integer
low:
type: integer
pending:
type: integer
errors:
type: integer
paused:
type: integer
alerts_pending:
type: integer
sla_breached:
type: integer
benchmarked_domains:
type: integer
peer_outliers:
type: integer
top_decile:
type: integer
worsening_domains:
type: integer
improving_domains:
type: integer
required:
- total
- matching_total
- high
- medium
- low
- pending
- errors
- paused
- alerts_pending
- sla_breached
additionalProperties: false
PortfolioPagination:
type: object
properties:
page:
type: integer
page_size:
type: integer
total:
type: integer
total_pages:
type: integer
has_prev:
type: boolean
has_next:
type: boolean
required:
- page
- page_size
- total
- total_pages
- has_prev
- has_next
additionalProperties: false
PortfolioFilters:
type: object
properties:
page:
type: integer
page_size:
type: integer
sort:
type: string
enum: [risk_score, last_scan_at, url, alerts, sla_status, aging, benchmark_percentile, benchmark_change]
order:
type: string
enum: [asc, desc]
risk_level:
type: string
enum: [all, high, medium, low, unknown]
monitor_status:
type: string
enum: [all, active, paused, error, pending]
has_alerts:
type: boolean
cmp:
type: string
q:
type: string
required:
- page
- page_size
- sort
- order
- risk_level
- monitor_status
- has_alerts
- cmp
- q
additionalProperties: false
PortfolioMeta:
type: object
properties:
available_cmps:
type: array
items:
type: string
benchmarks:
$ref: '#/components/schemas/PortfolioBenchmarkMeta'
required:
- available_cmps
additionalProperties: false
PortfolioBenchmarkMeta:
type: object
properties:
available:
type: boolean
sample_size:
type: integer
last_computed:
oneOf:
- type: string
format: date-time
- type: 'null'
required:
- available
- sample_size
- last_computed
additionalProperties: false
PortfolioResponse:
type: object
properties:
summary:
$ref: '#/components/schemas/PortfolioSummary'
domains:
type: array
items:
$ref: '#/components/schemas/PortfolioDomain'
pagination:
$ref: '#/components/schemas/PortfolioPagination'
filters:
$ref: '#/components/schemas/PortfolioFilters'
meta:
$ref: '#/components/schemas/PortfolioMeta'
required:
- summary
- domains
- pagination
- filters
- meta
additionalProperties: false
PublicStatusIncident:
type: object
properties:
id:
type: string
title:
type: string
message:
type: string
impact:
type: string
enum: [degraded, outage]
status:
type: string
enum: [investigating, identified, monitoring, resolved]
started_at:
type: string
format: date-time
resolved_at:
oneOf:
- type: string
format: date-time
- type: 'null'
updated_at:
type: string
format: date-time
required:
- id
- title
- message
- impact
- status
- started_at
- resolved_at
- updated_at
additionalProperties: false
CMPHealth:
type: object
properties:
cmp:
type: string
total:
type: integer
detected:
type: integer
detection_rate:
type: number
status:
type: string
enum: [operational, degraded]
degraded_since:
oneOf:
- type: string
format: date-time
- type: 'null'
recovering_since:
oneOf:
- type: string
format: date-time
- type: 'null'
required:
- cmp
- total
- detected
- detection_rate
- status
- degraded_since
- recovering_since
additionalProperties: false
QueueDepth:
type: object
properties:
pending:
type: integer
processing:
type: integer
required:
- pending
- processing
additionalProperties: false
TimedMetric:
type: object
properties:
last_1h:
type:
- number
- 'null'
last_24h:
type:
- number
- 'null'
required:
- last_1h
- last_24h
additionalProperties: false
PublicUptimeDay:
type: object
properties:
day:
type: string
format: date-time
status:
type: string
enum: [operational, degraded, outage]
required:
- day
- status
additionalProperties: false
PublicStatusPayload:
type: object
properties:
generated_at:
type: string
format: date-time
overall_status:
type: string
enum: [operational, degraded, outage]
scanner_status:
type: string
enum: [operational, degraded, outage]
queue_depth:
$ref: '#/components/schemas/QueueDepth'
success_rate:
$ref: '#/components/schemas/TimedMetric'
average_duration_ms:
$ref: '#/components/schemas/TimedMetric'
cmp_detection:
type: array
items:
$ref: '#/components/schemas/CMPHealth'
incidents:
type: array
items:
$ref: '#/components/schemas/PublicStatusIncident'
uptime_90d:
type: array
items:
$ref: '#/components/schemas/PublicUptimeDay'
required:
- generated_at
- overall_status
- scanner_status
- queue_depth
- success_rate
- average_duration_ms
- cmp_detection
- incidents
- uptime_90d
additionalProperties: false
paths:
/api/v1/scan:
post:
tags: [Scans]
operationId: createScan
summary: Submit a scan
description: Queue a new scan for the requested URL and viewport.
security:
- bearerAuth: []
x-required-scopes:
- scan:write
x-rate-limit:
requests: 100
window_seconds: 60
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ScanCreateRequest'
responses:
'201':
description: Scan queued.
headers:
Location:
schema:
type: string
description: Relative URL for polling the queued scan.
content:
application/json:
schema:
$ref: '#/components/schemas/ScanSubmissionResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/ServiceUnavailable'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/scan/{scanId}:
get:
tags: [Scans]
operationId: getScanStatus
summary: Get scan status or final result
description: Returns only scans owned by the authenticated API key owner.
security:
- bearerAuth: []
x-required-scopes:
- scan:read
x-rate-limit:
requests: 100
window_seconds: 60
parameters:
- $ref: '#/components/parameters/ScanID'
- in: query
name: locale
required: false
schema:
type: string
description: Optional finding localization hint.
- in: query
name: jurisdiction
required: false
schema:
type: string
description: Optional enforcement-context jurisdiction override.
responses:
'200':
description: Current scan state.
content:
application/json:
schema:
$ref: '#/components/schemas/ScanStatusResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/scan/{scanId}/compare:
get:
tags: [Scans]
operationId: compareScanToLatestBaseline
summary: Compare a completed scan to the latest previous same-host baseline
security:
- bearerAuth: []
x-required-scopes:
- scan:read
x-rate-limit:
requests: 30
window_seconds: 60
parameters:
- $ref: '#/components/parameters/ScanID'
- in: query
name: baseline
required: false
schema:
type: string
enum: [latest]
description: Only `latest` is currently supported.
responses:
'200':
description: Baseline comparison payload.
content:
application/json:
schema:
$ref: '#/components/schemas/ScanCompareResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/scan/{scanId}/diff:
get:
tags: [Scans]
operationId: diffTwoCompletedScans
summary: Diff two completed scans for the same host
security:
- bearerAuth: []
x-required-scopes:
- scan:read
x-rate-limit:
requests: 30
window_seconds: 60
parameters:
- $ref: '#/components/parameters/ScanID'
- in: query
name: previous
required: true
schema:
type: string
format: uuid
description: Previous completed scan UUID from the same host.
responses:
'200':
description: Granular drift payload.
content:
application/json:
schema:
$ref: '#/components/schemas/ScanDiffResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/scan/{scanId}/benchmarks:
get:
tags: [Scans]
operationId: getScanBenchmarks
summary: Get benchmark context for a completed scan
security:
- bearerAuth: []
x-required-scopes:
- scan:read
x-rate-limit:
requests: 30
window_seconds: 60
parameters:
- $ref: '#/components/parameters/ScanID'
responses:
'200':
description: Benchmark payload.
content:
application/json:
schema:
$ref: '#/components/schemas/ScanBenchmarkResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/scan/trend:
get:
tags: [Scans]
operationId: getRiskScoreTrend
summary: Get risk-score trend history for a URL
security:
- bearerAuth: []
x-required-scopes:
- scan:read
x-rate-limit:
requests: 30
window_seconds: 60
parameters:
- in: query
name: url
required: true
schema:
type: string
format: uri
description: Target URL to aggregate by normalized scan URL.
- in: query
name: days
required: false
schema:
type: string
enum: ['30', '90', '365', all]
description: Trend window. Omit or use `all` for all retained completed scans.
responses:
'200':
description: Trend response grouped by viewport.
content:
application/json:
schema:
$ref: '#/components/schemas/RiskScoreTrendResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/monitors:
get:
tags: [Monitoring]
operationId: listMonitors
summary: List monitors
security:
- bearerAuth: []
x-required-scopes:
- monitors:read
x-rate-limit:
requests: 100
window_seconds: 60
parameters:
- in: query
name: page
required: false
schema:
type: integer
minimum: 1
default: 1
- in: query
name: page_size
required: false
schema:
type: integer
minimum: 1
maximum: 50
default: 10
responses:
'200':
description: Paginated monitors.
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
post:
tags: [Monitoring]
operationId: upsertMonitor
summary: Create or update a monitor by URL
security:
- bearerAuth: []
x-required-scopes:
- monitors:write
x-rate-limit:
requests: 100
window_seconds: 60
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorUpsertRequest'
responses:
'200':
description: Monitor created or updated.
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorMutationResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/monitors/{monitorId}:
get:
tags: [Monitoring]
operationId: getMonitor
summary: Get a monitor
security:
- bearerAuth: []
x-required-scopes:
- monitors:read
x-rate-limit:
requests: 100
window_seconds: 60
parameters:
- $ref: '#/components/parameters/MonitorID'
responses:
'200':
description: Monitor detail.
content:
application/json:
schema:
$ref: '#/components/schemas/Monitor'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
patch:
tags: [Monitoring]
operationId: patchMonitor
summary: Update a monitor
security:
- bearerAuth: []
x-required-scopes:
- monitors:write
x-rate-limit:
requests: 100
window_seconds: 60
parameters:
- $ref: '#/components/parameters/MonitorID'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorPatchRequest'
responses:
'200':
description: Updated monitor.
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorMutationResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
delete:
tags: [Monitoring]
operationId: deleteMonitor
summary: Delete a monitor
security:
- bearerAuth: []
x-required-scopes:
- monitors:write
x-rate-limit:
requests: 100
window_seconds: 60
parameters:
- $ref: '#/components/parameters/MonitorID'
responses:
'200':
description: Monitor deleted.
content:
application/json:
schema:
$ref: '#/components/schemas/OKResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/monitors/{monitorId}/release-markers:
get:
tags: [Release Markers]
operationId: listReleaseMarkers
summary: List release markers and correlations for a monitor
security:
- bearerAuth: []
x-required-scopes:
- monitors:read
x-rate-limit:
requests: 60
window_seconds: 60
parameters:
- $ref: '#/components/parameters/MonitorID'
- in: query
name: limit
required: false
schema:
type: integer
minimum: 1
description: Positive integer capped server-side.
responses:
'200':
description: Marker and correlation list.
content:
application/json:
schema:
$ref: '#/components/schemas/ReleaseMarkersListResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
post:
tags: [Release Markers]
operationId: createReleaseMarker
summary: Record a release marker for a monitor
description: Use release markers to correlate regressions with deployments.
security:
- bearerAuth: []
x-required-scopes:
- monitors:write
x-rate-limit:
requests: 60
window_seconds: 60
parameters:
- $ref: '#/components/parameters/MonitorID'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ReleaseMarkerCreateRequest'
responses:
'201':
description: Release marker created.
content:
application/json:
schema:
$ref: '#/components/schemas/ReleaseMarkerMutationResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/monitors/{monitorId}/release-markers/{markerId}:
delete:
tags: [Release Markers]
operationId: deleteReleaseMarker
summary: Delete a release marker
security:
- bearerAuth: []
x-required-scopes:
- monitors:write
x-rate-limit:
requests: 30
window_seconds: 60
parameters:
- $ref: '#/components/parameters/MonitorID'
- $ref: '#/components/parameters/MarkerID'
responses:
'200':
description: Marker deleted.
content:
application/json:
schema:
$ref: '#/components/schemas/OKResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/webhooks:
get:
tags: [Webhooks]
operationId: getWebhookConfig
summary: Get webhook configuration
security:
- bearerAuth: []
x-required-scopes:
- webhooks:read
x-rate-limit:
requests: 100
window_seconds: 60
responses:
'200':
description: Current webhook configuration or `null`.
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookConfigResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
post:
tags: [Webhooks]
operationId: upsertWebhookConfig
summary: Create or update webhook configuration
description: |-
Register an outbound webhook endpoint for scan completion and regression alerts.
Delivery headers:
- `X-GDPRMonitor-Event`
- `X-GDPRMonitor-Timestamp`
- `X-GDPRMonitor-Signature`
- `X-GDPRMonitor-Delivery-ID`
Signature base string: `timestamp + "." + raw_json_body`
security:
- bearerAuth: []
x-required-scopes:
- webhooks:write
x-rate-limit:
requests: 100
window_seconds: 60
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookUpsertRequest'
callbacks:
webhookDelivery:
'{$request.body#/url}':
post:
operationId: deliverWebhookEvent
summary: Outbound webhook delivery
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookPayload'
examples:
scan_complete:
$ref: '#/components/examples/WebhookScanComplete'
regression_alert:
$ref: '#/components/examples/WebhookRegressionAlert'
responses:
'200':
description: Return any 2xx response to acknowledge delivery.
responses:
'200':
description: Webhook created or updated.
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookUpsertResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
delete:
tags: [Webhooks]
operationId: deleteWebhookConfig
summary: Delete webhook configuration
security:
- bearerAuth: []
x-required-scopes:
- webhooks:write
x-rate-limit:
requests: 100
window_seconds: 60
responses:
'200':
description: Webhook deleted.
content:
application/json:
schema:
$ref: '#/components/schemas/OKResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/disputes:
post:
tags: [Disputes]
operationId: createDispute
summary: Create a dispute for a scan finding
security:
- bearerAuth: []
x-required-scopes:
- scan:write
x-rate-limit:
requests: 100
window_seconds: 60
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DisputeCreateRequest'
responses:
'200':
description: Dispute created.
content:
application/json:
schema:
$ref: '#/components/schemas/DisputeCreateResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/portfolio:
get:
tags: [Portfolio]
operationId: getPortfolio
summary: Get portfolio overview across monitored domains
security:
- bearerAuth: []
x-required-scopes:
- monitors:read
x-rate-limit:
requests: 100
window_seconds: 60
parameters:
- in: query
name: page
schema:
type: integer
minimum: 1
default: 1
- in: query
name: page_size
schema:
type: integer
minimum: 1
maximum: 100
default: 50
- in: query
name: sort
schema:
type: string
enum: [risk_score, last_scan_at, url, alerts, sla_status, aging, benchmark_percentile, benchmark_change]
- in: query
name: order
schema:
type: string
enum: [asc, desc]
- in: query
name: risk_level
schema:
type: string
enum: [all, high, medium, low, unknown]
- in: query
name: monitor_status
schema:
type: string
enum: [all, active, paused, error, pending]
- in: query
name: has_alerts
schema:
type: boolean
- in: query
name: cmp
schema:
type: string
- in: query
name: q
schema:
type: string
- in: query
name: format
schema:
type: string
enum: [csv]
description: When `csv`, the endpoint returns `text/csv` instead of JSON.
responses:
'200':
description: Portfolio JSON or CSV export.
content:
application/json:
schema:
$ref: '#/components/schemas/PortfolioResponse'
text/csv:
schema:
type: string
description: CSV export of portfolio domains.
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/status:
get:
tags: [Status]
operationId: getPublicStatus
summary: Get public scanner status
description: Public, cached status endpoint. No API key required.
security: []
x-rate-limit:
per_known_ip_requests: 60
per_unknown_shared_requests: 20
window_seconds: 60
responses:
'200':
description: Public service health payload.
content:
application/json:
schema:
$ref: '#/components/schemas/PublicStatusPayload'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
description: Status generation failed.
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
'503':
$ref: '#/components/responses/ServiceUnavailable'