openapi: "3.1.0" info: title: ShieldLabs API version: "1.2" description: | The ShieldLabs API surfaces visitor identification, anonymity detection, and abuse and fraud prevention for your application. There are three surfaces: - The **JS snippet** posts collected signals to `rest.shieldlabs.ai` automatically. You do not call this endpoint yourself. See [Install the snippet](/setup/snippet). - **Webhooks** deliver the Risk Score and signals to your server shortly after a visit. See [Webhooks](/api/webhooks). Contract: schema_version `2026-06-01` (`identification.scored` / `webhook.ping`), signature in `X-Shield-Signature`. - The **Server API** has two backend hosts: - **History API** (recommended) on `account.shieldlabs.ai/api` — implemented by **Shield.Portal.Admin**. Private API Key (`Authorization: Bearer sec_…`). Envelope `{ data, total }`. See [Server API](/api/server-api). - **Management API** on `api.shieldlabs.ai` — implemented by **Shield.Core**. Secret Key in headers (`X-Shield-Domain` + `Authorization: Bearer`). Profile, balance, and alternate billed History path (PascalCase array). The Risk Score is an integer from 0 to 100. ShieldLabs scores visits; your own code decides whether to allow, challenge, review, or block. contact: email: support@shieldlabs.ai servers: - url: https://account.shieldlabs.ai/api description: History API (Shield.Portal.Admin — Private API Key, recommended for snapshot reads) - url: https://api.shieldlabs.ai description: Management API (Shield.Core — Secret Key — profile, balance, billed History) tags: - name: history-api description: | Recommended snapshot reads on account.shieldlabs.ai (service: Shield.Portal.Admin). Authenticated with Private API Key. Response envelope `{ data, total }`. - name: management-api description: | Profile, balance, and alternate History on api.shieldlabs.ai (service: Shield.Core). Authenticated with Secret Key headers. History returns a PascalCase JSON array. paths: /api/v1/history/{search_type}/{value}: get: tags: [history-api] summary: Search history by identifier (recommended) description: | Returns stored snapshots that match an identifier, newest first, in a paginated `{ data, total }` envelope. Does not consume request balance. Served by Shield.Portal.Admin on account.shieldlabs.ai. operationId: searchHistoryAccount security: - PrivateApiKey: [] parameters: - name: search_type in: path required: true schema: type: string enum: [ip, user_hid, visitor_id, request_id, device_id, session_id, cookie_id] - name: value in: path required: true schema: type: string - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 20 - name: offset in: query schema: type: integer minimum: 0 default: 0 responses: "200": description: Paginated snapshot list content: application/json: schema: $ref: "#/components/schemas/HistoryEnvelope" "401": $ref: "#/components/responses/UnauthorizedJson" /v1/profile: get: tags: [management-api] summary: Get domain profile description: | Returns the profile for a domain: remaining request balance and masked keys. Free (0 requests). operationId: getProfileV1 security: - SecretKeyHeader: [] responses: "200": description: Domain profile content: application/json: schema: $ref: "#/components/schemas/Profile" "401": $ref: "#/components/responses/UnauthorizedEmpty" /v1/history/{type}/{value}: get: tags: [management-api] summary: Search history by identifier (Management API) description: | Returns stored snapshots in a PascalCase JSON array, newest first. Each returned row consumes one request from your balance (an empty result still consumes one). Prefer `/api/v1/history/…` on account.shieldlabs.ai for new integrations. Served by Shield.Core on api.shieldlabs.ai. operationId: searchHistoryV1 security: - SecretKeyHeader: [] parameters: - name: type in: path required: true schema: type: string enum: [ip, user_hid, visitor_id, request_id, device_id, session_id, cookie_id] - name: value in: path required: true schema: type: string - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 100 responses: "200": description: Matching snapshots, newest first content: application/json: schema: type: array items: $ref: "#/components/schemas/Snapshot" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/UnauthorizedEmpty" "402": $ref: "#/components/responses/OutOfRequests" "404": description: Unsupported identifier type "429": $ref: "#/components/responses/TooManyRequests" "503": $ref: "#/components/responses/ServiceUnavailable" webhooks: identificationScored: post: summary: identification.scored delivered description: | ShieldLabs POSTs a signed JSON envelope to your configured webhook endpoint after a visit is scored. One delivery per check (at-most-once, no retries). Verify `X-Shield-Signature: sha256=` (HMAC-SHA256 over the raw body with your endpoint secret). Treat `data.request_id` as an idempotency key. Schema version is `2026-06-01` (`schema_version` field). Ping/test deliveries use `event_type: webhook.ping` with no `data`. parameters: - name: X-Shield-Signature in: header required: true description: | HMAC-SHA256 of the raw request body, hex-encoded, prefixed with `sha256=`. Example: `sha256=9f1c2b3a…`. schema: type: string - name: Content-Type in: header required: true schema: type: string enum: [application/json] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/WebhookEvent" examples: scored: summary: identification.scored value: event_type: identification.scored schema_version: "2026-06-01" created_at: "2026-06-26T14:20:42Z" data: request_id: "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11" visitor_id: "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d" device_id: "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f" session_id: "7a1b2c3d-4e5f-6789-abcd-ef0123456789" cookie_id: "3f2e1d0c-9b8a-7654-3210-fedcba987654" user_hid: null domain: example.com public_ip: { ip: "203.0.113.42", country: "US" } local_ip: { ip: "198.51.100.23", country: "DE" } connection_type: proxy os: Windows browser: Chrome device_type: desktop traffic_source: channel: "Google Ads" referrer_domain: google.com landing_url: "https://example.com/lp?gclid=abc123" click_id_type: gclid utm_source: google utm_medium: cpc utm_campaign: summer_sale utm_content: ad_a utm_term: "buy shoes" risk_score: 30 signals: - { name: proxy, weight: 10 } - { name: datacenter_ip, weight: 10 } - { name: abuser, weight: 10 } detection_flags: vpn: false privacy_relay: false browser_vpn_proxy: false tor: false proxy: true datacenter_ip: true abuser: true os_mismatch: false os_not_detected: false timezone_mismatch: false anti_detect_browser: false browser_automation: false ip_mismatch: true incognito: false search_bot: false suspicious_paid_click: false javascript_disabled: false stun_request_seen: true stun_not_checked: false observed_at: "2026-06-26T14:20:42Z" ping: summary: webhook.ping value: event_type: webhook.ping schema_version: "2026-06-01" created_at: "2026-06-26T14:20:42Z" responses: "200": description: Acknowledged. Return 200 quickly. components: securitySchemes: PrivateApiKey: type: http scheme: bearer description: Private API Key from the dashboard API tab (`sec_…`). SecretKeyHeader: type: http scheme: bearer description: Secret Key plus `X-Shield-Domain` header with your registered domain. responses: UnauthorizedEmpty: description: Missing or invalid credentials, or the domain is disabled (empty body) UnauthorizedJson: description: Missing or invalid Private API Key content: application/json: schema: $ref: "#/components/schemas/Error" BadRequest: description: Invalid parameters (bare JSON string on Management API) OutOfRequests: description: The domain has no remaining request balance (empty body) TooManyRequests: description: Per-IP rate limit exceeded content: application/json: schema: $ref: "#/components/schemas/Error" ServiceUnavailable: description: Gateway concurrency cap reached content: application/json: schema: $ref: "#/components/schemas/Error" schemas: Error: type: object properties: error: type: string examples: ["too many requests", "invalid api key"] HistoryEnvelope: type: object properties: data: type: array items: $ref: "#/components/schemas/AccountSnapshot" total: type: integer AccountSnapshot: type: object description: Snapshot row from the History API (snake_case). properties: request_id: type: string format: uuid session_id: type: string format: uuid cookie_id: type: string format: uuid device_id: type: string format: uuid visitor_id: type: string format: uuid ip: type: string score: type: integer score_details: type: string description: JSON string of signal objects with Value and Description fields. created_at: type: string ScoreDetail: type: object description: Signal row on Management API Snapshot.Details (PascalCase). Not used in webhooks. properties: Value: type: integer Description: type: string WebhookIPAddress: type: object properties: ip: type: string country: type: string WebhookSignal: type: object required: [name, weight] properties: name: type: string description: Stable signal slug (e.g. proxy, vpn, datacenter_ip). weight: type: integer description: Points contributed to risk_score. Debug entries with weight 0 are omitted. WebhookTrafficSource: type: object properties: channel: type: string referrer_domain: type: string landing_url: type: string click_id_type: type: string utm_source: type: string utm_medium: type: string utm_campaign: type: string utm_content: type: string utm_term: type: string WebhookDetectionFlags: type: object description: Denormalized detection booleans from Shield.Core entity.WebhookDetectionFlags. properties: vpn: type: boolean privacy_relay: type: boolean browser_vpn_proxy: type: boolean tor: type: boolean proxy: type: boolean datacenter_ip: type: boolean abuser: type: boolean os_mismatch: type: boolean os_not_detected: type: boolean timezone_mismatch: type: boolean anti_detect_browser: type: boolean browser_automation: type: boolean ip_mismatch: type: boolean incognito: type: boolean search_bot: type: boolean suspicious_paid_click: type: boolean javascript_disabled: type: boolean stun_request_seen: type: boolean stun_not_checked: type: boolean WebhookScoredData: type: object description: Payload inside identification.scored (Shield.Core entity.WebhookScoredData). required: - request_id - visitor_id - device_id - risk_score - signals - detection_flags - observed_at properties: request_id: type: string format: uuid description: Unique per visit. Use as an idempotency key. visitor_id: type: string format: uuid device_id: type: string format: uuid session_id: type: string format: uuid cookie_id: type: string format: uuid user_hid: type: ["string", "null"] description: Your hashed user identifier, if provided; null when empty. domain: type: string public_ip: $ref: "#/components/schemas/WebhookIPAddress" local_ip: $ref: "#/components/schemas/WebhookIPAddress" connection_type: type: string description: Observed connection class; new values may appear. enum: [direct, mobile, vpn, proxy, tor, privacy_relay, browser_vpn_proxy, unknown] os: type: string browser: type: string device_type: type: string traffic_source: $ref: "#/components/schemas/WebhookTrafficSource" risk_score: type: integer minimum: 0 maximum: 100 signals: type: array items: $ref: "#/components/schemas/WebhookSignal" detection_flags: $ref: "#/components/schemas/WebhookDetectionFlags" observed_at: type: string format: date-time WebhookEvent: type: object description: | POST body envelope (Shield.Core entity.WebhookEvent). Signature is NOT in the body — verify the X-Shield-Signature header. required: [event_type, schema_version, created_at] properties: event_type: type: string enum: [identification.scored, webhook.ping] schema_version: type: string const: "2026-06-01" created_at: type: string format: date-time data: $ref: "#/components/schemas/WebhookScoredData" description: Present for identification.scored; omitted for webhook.ping. Snapshot: type: object description: Snapshot from the Management API History path (PascalCase array element). properties: RequestID: type: string format: uuid SessionID: type: string format: uuid CookieID: type: string format: uuid DeviceID: type: string format: uuid VisitorID: type: string format: uuid IP: type: string ConnectionType: type: string enum: [direct, mobile, vpn, proxy, tor, privacy_relay, unknown] WebRtcHIP: type: string OS: type: string Browser: type: string DeviceType: type: string Country: type: string UserHID: type: string Score: type: integer Details: type: array items: $ref: "#/components/schemas/ScoreDetail" LastRequestTime: type: string format: date-time Profile: type: object properties: Domain: type: string Weight: type: integer PublicKey: type: string Secret: type: string CreatedAt: type: string format: date-time