generated: '2026-09-04' method: searched source: https://docs.shieldlabs.ai/api/overview derived_from: openapi/shieldlabs-server-api-openapi.yml docs: api_overview: https://docs.shieldlabs.ai/api/overview server_api: https://docs.shieldlabs.ai/api/server-api webhooks: https://docs.shieldlabs.ai/setup/webhooks keys: https://docs.shieldlabs.ai/setup/keys errors: https://docs.shieldlabs.ai/errors rate_limits: https://docs.shieldlabs.ai/rate-limits note: >- ShieldLabs runs two Server API surfaces implemented by two different internal services, and their conventions DIVERGE at almost every level — casing, envelope, auth headers, billing and rate limiting are all different between account.shieldlabs.ai and api.shieldlabs.ai. Anything cross- cutting stated here is stated per surface for that reason. authentication: style: bearer token, per registered domain surfaces: - host: account.shieldlabs.ai header: 'Authorization: Bearer sec_…' credential: Private API Key - host: api.shieldlabs.ai header: 'Authorization: Bearer + X-Shield-Domain: ' credential: Secret Key cross_reference: authentication/shieldlabs-authentication.yml idempotency: supported: true coverage: na write_surface: none read_only_api: true coverage_rationale: >- `coverage` is `na`, not `none` and not `full`. The public ShieldLabs API has NO mutating operations at all — every published operation is a GET (searchHistoryAccount, getProfileV1, searchHistoryV1), so there is no write surface for a replay-protection mechanism to cover and an Idempotency-Key header would have nothing to protect. Scoring this as `none` would penalise the provider for omitting a mechanism that cannot apply. What the provider DOES publish is the inbound half of the same problem — a consumer-side deduplication contract for at-most-once webhook delivery — recorded below, which is real and is why `supported` is true. mechanism: consumer-side deduplication key on the event, not a request header key_field: request_id scope: one identification (one visitor check) header: null detail: >- ShieldLabs publishes a real idempotency contract, but it runs in the opposite direction from the usual one. Webhook delivery is explicitly AT-MOST-ONCE with NO RETRIES, and the provider requires the consumer to treat data.request_id as the idempotency key and make handlers idempotent on it — the OpenAPI webhook description states this verbatim ("Treat `data.request_id` as an idempotency key"), as does the changelog. request_id is documented as unique per visit and is the join key between the webhook payload and a History API read. There is NO Idempotency-Key request header on the Server API, because both operations are GETs and therefore naturally idempotent; there are no write operations on the public API at all. evidence: - openapi/shieldlabs-server-api-openapi.yml#identificationScored - https://docs.shieldlabs.ai/setup/webhooks - https://docs.shieldlabs.ai/changelog pagination: surfaces: - host: account.shieldlabs.ai style: limit/offset params: limit: {default: 20, min: 1, max: 100} offset: {default: 0, min: 0} response_fields: [data, total] envelope: '{ "data": [...], "total": n }' - host: api.shieldlabs.ai style: limit only params: limit: {default: 100, min: 1, max: 100} response_fields: [] envelope: bare JSON array, no total, no cursor ordering: newest first on both surfaces field_casing: account_shieldlabs_ai: snake_case (request_id, device_id, created_at) api_shieldlabs_ai: PascalCase (RequestID, DeviceID, LastRequestTime) webhook: snake_case note: >- The same logical snapshot is returned in two different casings depending on which host you read it from. A client cannot share a model between the two surfaces without a mapping layer. expansion: supported: false sparse_fields: supported: false metadata: supported: partial detail: >- A single caller-supplied field, user_hid, is echoed back on webhooks and History rows. The docs require it to be hashed or pseudonymous before it is passed. There is no general metadata bag. request_tracing: request_id_header: null correlation_field: request_id detail: >- request_id is the correlation key across the snippet call, the webhook delivery and the History read, but it is a payload field — no request-id response header is documented on any surface. versioning: api_version_scheme: uri-path current: v1 spec_version: '1.2' event_schema_version: '2026-06-01' detail: >- Paths are versioned (/api/v1/…, /v1/…). Webhook payloads carry a separate date-based schema_version field, currently 2026-06-01, sent on every envelope. cross_reference: lifecycle/shieldlabs-lifecycle.yml error_envelope: uniform: false guidance: branch on HTTP status, not on a body field cross_reference: errors/shieldlabs-problem-types.yml rate_limit_signalling: headers: [] status_codes: [429, 503] cross_reference: rate-limits/shieldlabs-rate-limits.yml reversibility: grade: na applies: false write_surface: none detail: >- Reversibility is `na` for the same reason idempotency is: there is nothing to reverse. All three published operations are reads, and the one thing a caller can cause — an identification — is not caused through the API at all. It is caused by a visitor loading the page with the snippet on it, and it produces an immutable observation of that visit. There is no cancel, refund, void, undo, rollback or restore operation in the spec, and no docs page describes one, because there is no created object to take back. reversal_operations: [] windows: [] irreversible_effects: - effect: An identification is billed the moment it is scored. reversal: none published note: >- Billing is per identification and the docs describe no credit, refund or void path for one that was collected in error. Cost control is placement of the snippet call, not reversal — the provider's own guidance page is "Optimize usage and cost", not a refund policy. docs: https://docs.shieldlabs.ai/setup/optimizing-usage - effect: A stored snapshot is retained and readable through History. reversal: none published on the API note: >- No DELETE operation exists on either Server API host. Data-subject deletion is described in the privacy policy as a request process to contact@shieldlabs.ai, not an API call, so it is out of band and has no stated window. docs: https://docs.shieldlabs.ai/legal/privacy-policy - effect: A webhook delivery is at-most-once with no retries. reversal: not applicable — recovery is a History API read, not a replay note: >- The provider publishes no webhook replay or redelivery endpoint. If a delivery is missed the documented recovery is to read the snapshot back by request_id from the History API. That is a recovery path, not a reversal, and it is recorded here so an agent does not wait for a retry that will never come. docs: https://docs.shieldlabs.ai/setup/webhooks evidence: - openapi/_original/shieldlabs-openapi.yaml - https://docs.shieldlabs.ai/api/server-api dry_run_mode: supported: na detail: >- No dry-run, preview or simulate mode is published, and none is meaningful against a read-only surface. The provider's stated equivalent is environment separation — a second registered domain with its own keys and balance (see sandbox/shieldlabs-sandbox.yml), not a flag on a request. webhook_conventions: signature_header: 'X-Shield-Signature: sha256=' algorithm: HMAC-SHA256 over the raw request body key: per-endpoint whsec_… secret comparison: constant-time; reject with 401 on mismatch delivery: at-most-once, no retries delay: waits up to 60s for an optional follow-up network check, then always delivers event_types: [identification.scored, webhook.ping] ack: return 200 quickly cross_reference: asyncapi/shieldlabs-webhooks.yml