{
  "openapi": "3.1.0",
  "info": {
    "title": "Noxtica public API",
    "version": "0.16.1",
    "summary": "Every public operation and webhook of the Noxtica server APIs in one document.",
    "description": "Noxtica public API, OpenAPI release v0.16.1 (2026-11-07). The identity verification, Pulse and MCP operations are served from their own hosts: every path item carries its `servers`. Reference: https://noxtica.com/docs/api/reference/. Each API is also published on its own at https://noxtica.com/docs/api/openapi/<api>.json.",
    "contact": {
      "name": "Noxtica documentation",
      "url": "https://noxtica.com/docs/api/"
    }
  },
  "servers": [
    {
      "url": "https://collect.noxtica.com",
      "description": "Production",
      "x-noxtica-key-prefix": "sk_prod_"
    }
  ],
  "tags": [
    {
      "name": "KYC Verification",
      "description": "Core single-call identity verification.",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYC Cross-Validation",
      "description": "Self-uploaded supporting-document cross-matches against an already-verified identity.",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYC Batch",
      "description": "CSV/JSON bulk verification submission and result retrieval.",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYC Reverify",
      "description": "Metered re-check of an already-established identity without re-collecting evidence.",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYC Evidence",
      "description": "Ciphertext-only raw evidence (document/selfie image) storage, server-to-server.",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYC Hosted Sessions",
      "description": "Noxtica-hosted verification-page session lifecycle (S2S mint/status + browser-facing session routes).",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYC Compliance",
      "description": "Right-to-erasure and mobile-activation orchestration.",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYC Capacity",
      "description": "Advisory read of the platform's current verification capacity, so your backend can decide when to start, delay or queue a verification.",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYC Webhooks",
      "description": "Signed events Noxtica POSTs to your registered endpoints (documented in the top-level `webhooks` section; the delivery rules are in \"Webhooks (KYC)\" above).",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "KYM",
      "description": "Merchant/transaction monitoring signal intake and hosted onboarding sessions.",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "Identity Details",
      "description": "Read the identity details Noxtica extracted during your own verifications (tenant backend; opt-in, audited, never cached).",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "Review Decisions",
      "description": "Record the decision on a verification that waits for review from your own back office (tenant backend; opt-in at two levels, idempotent, audited, never cached).",
      "externalDocs": {
        "description": "Identity verification API: reference page",
        "url": "https://noxtica.com/docs/api/reference/kyc/"
      }
    },
    {
      "name": "MCP Server",
      "description": "The MCP JSON-RPC 2.0 endpoint.",
      "externalDocs": {
        "description": "MCP server: reference page",
        "url": "https://noxtica.com/docs/api/reference/mcp/"
      }
    },
    {
      "name": "Tenant",
      "description": "Server-to-server tenant plane (`/pulse/v1/tenant/*`) — subject status, response decisions, link codes and links, called from the tenant's own back end with a tenant API key.",
      "externalDocs": {
        "description": "Pulse tenant API: reference page",
        "url": "https://noxtica.com/docs/api/reference/pulse/"
      }
    },
    {
      "name": "Devices",
      "description": "Device fingerprint lookup and listing.",
      "externalDocs": {
        "description": "Device intelligence API: reference page",
        "url": "https://noxtica.com/docs/api/reference/device/"
      }
    },
    {
      "name": "Events & Visits",
      "description": "Raw event and visit reads.",
      "externalDocs": {
        "description": "Device intelligence API: reference page",
        "url": "https://noxtica.com/docs/api/reference/device/"
      }
    },
    {
      "name": "Exports",
      "description": "Bulk export of fingerprints/events/visits/scored-events, plus the emerging-threats feed.",
      "externalDocs": {
        "description": "Device intelligence API: reference page",
        "url": "https://noxtica.com/docs/api/reference/device/"
      }
    },
    {
      "name": "APM Observability",
      "description": "Project and environment inventory, bounded APM queries, asynchronous exports and privacy-request status. Customer and general capture are not yet available; an approved proof-of-concept is limited to synthetic, no-personal-data telemetry.",
      "externalDocs": {
        "description": "Observability query API: reference page",
        "url": "https://noxtica.com/docs/api/reference/observability/"
      }
    },
    {
      "name": "APM Ingest",
      "description": "OTLP/HTTP trace, metric, log and profile ingestion, plus deployment markers. Write-only telemetry, distinct from the read-only \"APM Observability\" tag. Authenticated with an `sk_apm_` ingestion key (never a server API key) and served from the ingestion hosts, and also from the collector hosts.",
      "externalDocs": {
        "description": "Telemetry ingestion API: reference page",
        "url": "https://noxtica.com/docs/api/reference/ingest/"
      }
    },
    {
      "name": "Sealed Results",
      "description": "Cryptographically sealed high-assurance result retrieval.",
      "externalDocs": {
        "description": "Device intelligence API: reference page",
        "url": "https://noxtica.com/docs/api/reference/device/"
      }
    },
    {
      "name": "Labels",
      "description": "Ground-truth fraud/legitimate labeling.",
      "externalDocs": {
        "description": "Device intelligence API: reference page",
        "url": "https://noxtica.com/docs/api/reference/device/"
      }
    },
    {
      "name": "Generator",
      "description": "Synthetic fingerprint generation for testing/load.",
      "externalDocs": {
        "description": "Device intelligence API: reference page",
        "url": "https://noxtica.com/docs/api/reference/device/"
      }
    },
    {
      "name": "Reputation",
      "description": "Email, website and phone reputation checks.",
      "externalDocs": {
        "description": "Risk scoring and agent trust API: reference page",
        "url": "https://noxtica.com/docs/api/reference/score/"
      }
    },
    {
      "name": "Crawl Vouchers",
      "description": "Signed crawler-access vouchers.",
      "externalDocs": {
        "description": "Risk scoring and agent trust API: reference page",
        "url": "https://noxtica.com/docs/api/reference/score/"
      }
    },
    {
      "name": "Behavioral Baseline",
      "description": "Behavioral-subject baseline read and erase (not enabled by default).",
      "externalDocs": {
        "description": "Device intelligence API: reference page",
        "url": "https://noxtica.com/docs/api/reference/device/"
      }
    },
    {
      "name": "Agent Trust",
      "description": "Web Bot Auth (RFC 9421) agent verification and signed Agent Passports.",
      "externalDocs": {
        "description": "Risk scoring and agent trust API: reference page",
        "url": "https://noxtica.com/docs/api/reference/score/"
      }
    },
    {
      "name": "IP & Risk Scoring",
      "description": "Standalone IP reputation and composite risk scoring.",
      "externalDocs": {
        "description": "Risk scoring and agent trust API: reference page",
        "url": "https://noxtica.com/docs/api/reference/score/"
      }
    },
    {
      "name": "Protected Actions",
      "description": "Server-side redemption of the `requestId` a browser obtains for a declared protected action (login, signup, checkout, ...).",
      "externalDocs": {
        "description": "Protected actions API: reference page",
        "url": "https://noxtica.com/docs/api/reference/protected-actions/"
      }
    },
    {
      "name": "Device Attestation",
      "description": "Mobile platform attestation (Play Integrity / App Attest) and PoW/integrity verification.",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Web/App Integrity",
      "description": "Web/app integrity verification distinct from device attestation.",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Mobile App Config",
      "description": "Mobile app runtime configuration delivery.",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Passkey",
      "description": "WebAuthn passkey challenge, register and assert (not enabled by default).",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Device Handoff",
      "description": "Cross-device session handoff mint and consume (not enabled by default).",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Device Delegation",
      "description": "Subject-scoped device delegation lifecycle (write:identity, not enabled by default).",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Account Recovery",
      "description": "Account-recovery case and appeal lifecycle (write:identity, not enabled by default).",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Wallet Status",
      "description": "Device-bound wallet status and suspend operations (write:identity, not enabled by default).",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Offline Capture",
      "description": "Offline-first nonce pool and capture-bundle intake.",
      "externalDocs": {
        "description": "Mobile and device identity API: reference page",
        "url": "https://noxtica.com/docs/api/reference/mobile/"
      }
    },
    {
      "name": "Coding Agent",
      "description": "Read-only access for coding agents to your organisation's recent errors, traces and signals, authenticated with a `cak_` key on the ingestion hosts.",
      "externalDocs": {
        "description": "Observability query API: reference page",
        "url": "https://noxtica.com/docs/api/reference/observability/"
      }
    },
    {
      "name": "Health and Keys",
      "description": "The unauthenticated collector health probe and the public signing-key documents used to verify signed envelopes offline.",
      "externalDocs": {
        "description": "Health and public key endpoints: reference page",
        "url": "https://noxtica.com/docs/api/reference/status/"
      }
    },
    {
      "name": "Webhook Endpoints",
      "description": "Register and manage the HTTPS endpoints that receive Noxtica's signed webhook events, choose their events, rotate secrets, send test deliveries and read delivery results.",
      "externalDocs": {
        "description": "Webhook endpoints API: reference page",
        "url": "https://noxtica.com/docs/api/reference/webhooks/"
      }
    },
    {
      "name": "Verdict Webhooks",
      "description": "Signed events Noxtica POSTs to your registered endpoints when a submission is scored, flagged, or linked to an agent (documented in the top-level `webhooks` section).",
      "externalDocs": {
        "description": "Device intelligence API: reference page",
        "url": "https://noxtica.com/docs/api/reference/device/"
      }
    }
  ],
  "paths": {
    "/v1/kyc/verify": {
      "post": {
        "operationId": "kycVerify",
        "tags": [
          "KYC Verification"
        ],
        "summary": "Verify an identity",
        "description": "Two branches, selected by the body's `action` field (`verify` is the implicit default when `action` is omitted — every pre-existing caller is unaffected):\n\n`action:'verify'` admits, screens, policy-enforces, and finalizes a single identity verification against the tenant's configured provider. The admission → spend-reservation → provider-call → finalize pipeline is shared byte-for-byte with the hosted-session submit path (`POST /v1/kyc/session-submit`) and the batch processor — there is exactly one verify pipeline in this system. It requires a `nonce` obtained from a prior `action:'mint'` call to the SAME endpoint (the only route that legitimately issues one) or a valid hosted-session-derived nonce.\n\n`action:'mint'` issues that admission nonce: a fresh, tenant-scoped, single-use nonce bound in KV to the calling tenant (TTL 300s). It is the ONLY way a server-to-server caller can obtain a nonce a subsequent `action:'verify'` call will accept — without it, `evidence-handle`/`cross-match`/`reverify` are unreachable S2S, since every one of those requires an admitted `verificationId` a real `verify` call produces. Mint runs the SAME auth/module-gate/legal-basis preflight as verify, but NEVER meters, NEVER reserves spend, and makes ZERO provider calls.\n\n**Your own capture screens.** If your application captures the document and selfie itself, mint with `evidenceGroup: true`. The response also carries an `evidenceGroupId`. Upload each capture into that group with `POST /v1/kyc/evidence/handle` (send `evidenceGroupId` instead of `verificationId`) and `POST /v1/kyc/evidence/finalize`, then verify with `payload: {evidenceGroupId}` and a fresh nonce. Noxtica resolves the uploaded evidence from the group — you never name evidence object ids — and the `verificationId` of the result equals the `evidenceGroupId`. A group accepts up to 16 evidence objects, closes after one hour, and serves exactly one verification (a retake needs a new group). A verify that names a group is refused before anything is admitted when the group is unknown (`404 evidence_group_not_found`, also for another organisation's group), just expired (`410 evidence_group_expired`), incomplete (`409 evidence_incomplete`, with `missingEvidence`), or malformed (`400`); no nonce is spent and nothing is billed.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycVerifyRequest"
              },
              "examples": {
                "mint": {
                  "summary": "action:'mint' — issue a fresh admission nonce",
                  "value": {
                    "action": "mint"
                  }
                },
                "mintWithEvidenceGroup": {
                  "summary": "action:'mint' with an evidence group — for your own capture screens",
                  "value": {
                    "action": "mint",
                    "evidenceGroup": true
                  }
                },
                "verifyEvidenceGroup": {
                  "summary": "action:'verify' — verify the evidence uploaded into a group",
                  "value": {
                    "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                    "id_type": "national_id",
                    "payload": {
                      "evidenceGroupId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  }
                },
                "verify": {
                  "summary": "action:'verify' (default) — spend a previously minted nonce",
                  "value": {
                    "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                    "id_type": "passport",
                    "purpose": "liveness_check",
                    "payload": {
                      "scenario": "verified"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`action:'mint'`: a freshly minted admission nonce (`KycVerifyMintResponse`) — zero provider calls, zero spend. `action:'verify'`: a resolved verify attempt (verified, unverified, or an infra `error` verdict), a nonce-replay of a prior identical request, or an admission-level refusal (`KycVerifyResponse`). `verdict:'error'` and every nonce-fault outcome (e.g. `nonce_unknown` for a never-minted or wrong-tenant nonce, `nonce_expired` for one past its 300s TTL) are HTTP 200 by design (FR-6) — a happy-path 5xx is never returned for `action:'verify'`; mint's own KV-write fault is the one 5xx exception (503, below).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/KycVerifyMintResponse"
                    },
                    {
                      "$ref": "#/components/schemas/KycVerifyResponse"
                    }
                  ]
                },
                "examples": {
                  "mint": {
                    "summary": "A freshly minted nonce",
                    "value": {
                      "ok": true,
                      "nonce": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "ttlSeconds": 300
                    }
                  },
                  "mintWithEvidenceGroup": {
                    "summary": "A nonce and an evidence group (`evidenceGroup:true`)",
                    "value": {
                      "ok": true,
                      "nonce": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "ttlSeconds": 300,
                      "evidenceGroupId": "7c1d9a52-0b6e-4f3a-9d8e-2a5b4c6d7e8f",
                      "evidenceGroupExpiresAt": 1768438800000,
                      "evidenceGroupTtlSeconds": 3600
                    }
                  },
                  "verified": {
                    "summary": "A verified identity",
                    "value": {
                      "ok": true,
                      "verdict": "verified",
                      "verificationLevel": "verified",
                      "reasonCodes": [
                        "name_match",
                        "document_authentic"
                      ],
                      "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "requestId": "req-test"
                    }
                  },
                  "nonceUnknown": {
                    "summary": "action:'verify' with a never-minted (or foreign-tenant) nonce — terminal unverified, zero provider calls",
                    "value": {
                      "ok": true,
                      "verdict": "unverified",
                      "verificationLevel": "unverified",
                      "reasonCodes": [
                        "nonce_unknown"
                      ],
                      "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "402": {
            "description": "`spend_cap_reached` — `action:'verify'` only. The request was admitted, but no active pricing is configured for your organisation, so the verification was refused before any provider call. Never returned for `action:'mint'` (a mint never reserves spend). See the `403` response for the other refusal of this kind.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "spend_cap_reached"
                      ]
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "spend_cap_reached"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "spend_cap_reached",
                  "code": "spend_cap_reached",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "Either the presented key lacks the required scope (`ScopeForbidden` shape), or — `action:'verify'` only — `tenant_inactive`: your organisation became inactive between nonce admission and the spend check, so the verification was refused. No provider call is made either way.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "additionalProperties": false,
                      "properties": {
                        "error": {
                          "type": "string",
                          "example": "API key does not have write:identity scope"
                        },
                        "requestId": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "additionalProperties": false,
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "tenant_inactive"
                          ]
                        },
                        "code": {
                          "type": "string",
                          "enum": [
                            "tenant_inactive"
                          ]
                        },
                        "requestId": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "scopeForbidden": {
                    "summary": "The presented key lacks the required scope",
                    "value": {
                      "error": "API key does not have write:identity scope",
                      "requestId": "req-test"
                    }
                  },
                  "tenantInactive": {
                    "summary": "action:'verify' — tenant went inactive before spend reservation",
                    "value": {
                      "error": "tenant_inactive",
                      "code": "tenant_inactive",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "409": {
            "description": "`action:'verify'` only: a conflicting in-flight or idempotency-key-bound request, or — when `payload.evidenceGroupId` names a group that does not yet hold every required capture — `evidence_incomplete` with the missing roles in `missingEvidence` (`front`, `back`, and/or `selfie`). The second form is refused before admission: the nonce is not spent, so retry with the same nonce once the evidence is uploaded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                },
                "examples": {
                  "idempotencyConflict": {
                    "summary": "The same Idempotency-Key was used with a different body",
                    "value": {
                      "error": "idempotency_conflict",
                      "code": "example-code",
                      "ok": true,
                      "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "reasonCodes": [
                        "request_in_progress"
                      ],
                      "requestId": "req-test"
                    }
                  },
                  "evidenceIncomplete": {
                    "summary": "The evidence group is missing the back of the document",
                    "value": {
                      "error": "evidence_incomplete",
                      "missingEvidence": [
                        "back"
                      ],
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "`action:'verify'` with `payload.evidenceGroupId`: the evidence group has just expired (`evidence_group_expired`). Groups close after one hour; once the group record is gone the answer is the same `404` as for an unknown group. Mint a new group and upload again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycEvidenceGroupRefusal"
                },
                "example": {
                  "error": "evidence_group_expired",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`action:'mint'`: the nonce-issuing KV write failed (fail-safe: no partially-written or unusable nonce is ever returned to the caller). `action:'verify'`: `kyc_verify_unavailable` — a transient fault while resolving pricing or reserving spend. Retryable; `Retry-After` carries the suggested backoff in seconds. Distinct from `spend_cap_reached` (402, a real business-state refusal, never an infrastructure fault); the request is not terminally denied.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until a retry is likely to succeed (`action:'verify'` faults only)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "mintKvFault": {
                    "summary": "action:'mint' — nonce KV write failed",
                    "value": {
                      "error": "invalid_request",
                      "requestId": "req-test"
                    }
                  },
                  "verifyUnavailable": {
                    "summary": "action:'verify' — transient reservation-authority fault",
                    "value": {
                      "error": "kyc_verify_unavailable",
                      "code": "kyc_verify_unavailable",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verify/{verificationId}/address-document": {
      "post": {
        "operationId": "kycAddressDocumentCrossMatch",
        "tags": [
          "KYC Cross-Validation"
        ],
        "summary": "Cross-match a self-uploaded proof-of-address document",
        "description": "Re-OCRs a proof-of-address document (already uploaded as KYC evidence) and the target verification's already-captured ID-front document, then runs a deterministic name/address/freshness cross-match. This is the **only** cross-validation capability Noxtica offers today — it is a self-uploaded-document cross-match, **never** an authoritative registry lookup (USPS/Loqate/Experian), as the response's own `disclosure` field states. There is no generic OCR↔MRZ↔barcode consistency check.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "name": "verificationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evidenceObjectId"
                ],
                "properties": {
                  "evidenceObjectId": {
                    "type": "string",
                    "description": "The `objectId` of an already-finalized (`usable`) evidence object of kind `proof_of_address`, minted via `POST /v1/kyc/evidence/handle` + `POST /v1/kyc/evidence/finalize` against this same `verificationId`."
                  }
                }
              },
              "example": {
                "evidenceObjectId": "obj-front"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A resolved cross-match verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "verdict",
                    "disclosure",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "verdict": {
                      "type": "string",
                      "enum": [
                        "pass",
                        "review",
                        "reject"
                      ]
                    },
                    "disclosure": {
                      "type": "string",
                      "description": "The fixed, always-identical non-authoritative-result disclosure string."
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "verdict": "pass",
                  "disclosure": "example-disclosure",
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "409": {
            "description": "The verification has no captured front-document evidence to cross-match against.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Evidence unavailable, or the owned OCR inference service could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "partial"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verify/batch": {
      "post": {
        "operationId": "kycVerifyBatchSubmit",
        "tags": [
          "KYC Batch"
        ],
        "summary": "Submit a batch verification manifest",
        "description": "Submits a CSV (`text/csv`) or JSON (`application/json`, `{\"items\":[...]}`) manifest of up to 500 rows. Each well-formed row is admitted **asynchronously**, off the response path, through the identical `POST /v1/kyc/verify` admission entry point — this endpoint does not verify anything itself, it only records per-item outcomes.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycBatchManifestJson"
              },
              "example": {
                "items": [
                  {
                    "id_type": "passport",
                    "purpose": "identity_verification",
                    "payload": {}
                  }
                ]
              }
            },
            "text/csv": {
              "schema": {
                "type": "string",
                "description": "Header row must include `id_type` (required); `purpose` and payload columns are optional."
              },
              "example": "id_type,purpose\npassport,identity_verification\n"
            }
          }
        },
        "responses": {
          "200": {
            "description": "A replayed idempotency key — the original batch job, unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycBatchSubmitResponse"
                },
                "example": {
                  "ok": true,
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "status": "processing",
                  "itemCount": 1,
                  "items": [
                    {
                      "rowIndex": 0,
                      "status": "pending",
                      "errorCode": "missing_id_type"
                    }
                  ],
                  "requestId": "req-test"
                }
              }
            }
          },
          "202": {
            "description": "Accepted; items begin admitting asynchronously.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycBatchSubmitResponse"
                },
                "example": {
                  "ok": true,
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "status": "processing",
                  "itemCount": 1,
                  "items": [
                    {
                      "rowIndex": 0,
                      "status": "pending",
                      "errorCode": "missing_id_type"
                    }
                  ],
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "415": {
            "description": "Unrecognized content type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Batch admission is temporarily unavailable (digest secret unset).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verify/batch/{batchId}/status": {
      "get": {
        "operationId": "kycVerifyBatchStatus",
        "tags": [
          "KYC Batch"
        ],
        "summary": "Read a batch job's aggregate status",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "name": "batchId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job-level aggregate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycBatchStatusResponse"
                },
                "example": {
                  "ok": true,
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "status": "processing",
                  "itemCount": 1,
                  "pendingCount": 0,
                  "admittedCount": 1,
                  "completedCount": 0,
                  "rejectedCount": 0,
                  "cancelledCount": 0,
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verify/batch/{batchId}/results": {
      "get": {
        "operationId": "kycVerifyBatchResults",
        "tags": [
          "KYC Batch"
        ],
        "summary": "Read a batch job's per-item outcomes",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "name": "batchId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every row's outcome, malformed or not — never silently dropped.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycBatchResultsResponse"
                },
                "example": {
                  "ok": true,
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "items": [
                    {
                      "rowIndex": 0,
                      "status": "pending",
                      "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "verdict": "verified",
                      "errorCode": "missing_id_type"
                    }
                  ],
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verify/batch/{batchId}/cancel": {
      "post": {
        "operationId": "kycVerifyBatchCancel",
        "tags": [
          "KYC Batch"
        ],
        "summary": "Cancel every still-pending item of a batch",
        "description": "A compare-and-swap `UPDATE ... WHERE status = 'pending'`. An item already `admitted`/`evaluating`/`completed` already holds (or held) a real verification claim and is never retroactively un-admitted.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "name": "batchId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancellation applied to every still-pending row.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "batchId": {
                      "type": "string"
                    },
                    "cancelledCount": {
                      "type": "integer"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "cancelledCount": 0,
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/reverify": {
      "post": {
        "operationId": "kycReverify",
        "tags": [
          "KYC Reverify"
        ],
        "summary": "Re-check an already-established identity's device/verification level",
        "description": "A metered, read-shaped check against the authoritative device registry — it does not re-run document/biometric capture. Requires the device-registry module to be enabled for your organisation in addition to the KYC module; when it is not, the route answers the `GatedNotFound` 404.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subjectId"
                ],
                "properties": {
                  "subjectId": {
                    "type": "string"
                  },
                  "requiredLevel": {
                    "type": "string",
                    "enum": [
                      "verified",
                      "partial",
                      "unverified",
                      "inconclusive"
                    ]
                  },
                  "stepUpReference": {
                    "type": "string",
                    "maxLength": 128,
                    "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$",
                    "description": "Optional biometric step-up claim reference (single-use)."
                  }
                }
              },
              "example": {
                "subjectId": "subj_4821",
                "requiredLevel": "verified",
                "stepUpReference": "example-stepUpReference"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200 (FR-6 fail-closed discipline) — a subject-not-found or infra fault is a `verified:false` body, never a 5xx.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "verified": {
                      "type": "boolean"
                    },
                    "verificationLevel": {
                      "type": "string",
                      "enum": [
                        "verified",
                        "partial",
                        "unverified",
                        "inconclusive"
                      ]
                    },
                    "stepUp": {
                      "type": "string",
                      "enum": [
                        "satisfied",
                        "unconfigured",
                        "reference_required",
                        "reference_replayed",
                        "no_match",
                        "error"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "verified": true,
                  "verificationLevel": "verified",
                  "stepUp": "satisfied",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/evidence/handle": {
      "post": {
        "operationId": "kycEvidenceHandleMint",
        "tags": [
          "KYC Evidence"
        ],
        "summary": "Mint a single-use ciphertext-upload handle",
        "description": "Step 1 of 2 for raw evidence storage. Mints an opaque, short-lived (15-minute) upload handle plus a fresh per-object Data Encryption Key (DEK) wrapped under the tenant's active Key Encryption Key (KEK). The caller encrypts the evidence bytes client-side with the returned `dek` and presents only ciphertext to `POST /v1/kyc/evidence/finalize` — Noxtica's server never sees plaintext evidence at this step. No R2 storage URL, internal key, or `sk_` value is ever returned (D27).",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jurisdiction",
                  "evidenceKind",
                  "purpose",
                  "contentType",
                  "contentLength"
                ],
                "properties": {
                  "verificationId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The verification the evidence belongs to. Exactly one of this and `evidenceGroupId` is required; naming both, or neither, is a `400`."
                  },
                  "evidenceGroupId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The group minted by `POST /v1/kyc/verify` with `action:'mint'` and `evidenceGroup:true`, for captures you upload before the verification exists. Only the image roles `document_front`, `document_back`, `passport_page` and `selfie_image` are accepted; a group holds at most 16 objects. An unknown, expired, or another organisation's group is the same `404`."
                  },
                  "jurisdiction": {
                    "type": "string",
                    "enum": [
                      "TR",
                      "EU",
                      "US"
                    ],
                    "description": "Must equal the tenant's configured evidence residency; any mismatch is a 404 (no residency-probe oracle)."
                  },
                  "evidenceKind": {
                    "type": "string",
                    "enum": [
                      "id_document_front",
                      "id_document_back",
                      "document_front",
                      "document_back",
                      "passport_page",
                      "proof_of_address",
                      "selfie_image",
                      "document_near_face",
                      "liveness_video",
                      "liveness_recording",
                      "liveness_sequence",
                      "voice_sample",
                      "speech_audio",
                      "supporting_document",
                      "document_data"
                    ],
                    "description": "Closed evidence-kind catalog (this enum is the complete set). e.g. id_document_front, proof_of_address, selfie_image. NOTE: id_front/id_back/selfie are NOT members of this catalog (a prior stale example here caused exactly that drift in downstream SDKs)."
                  },
                  "purpose": {
                    "type": "string",
                    "description": "Closed evidence-purpose catalog (e.g. identity_verification, address_verification)."
                  },
                  "contentType": {
                    "type": "string",
                    "example": "image/jpeg"
                  },
                  "contentLength": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              },
              "example": {
                "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                "jurisdiction": "TR",
                "evidenceKind": "id_document_front",
                "purpose": "identity_verification",
                "contentType": "image/jpeg",
                "contentLength": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Handle minted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "handle": {
                      "type": "string",
                      "description": "Opaque signed single-use upload handle."
                    },
                    "dek": {
                      "type": "string",
                      "description": "Base64 plaintext DEK — hold only in memory, never log or persist raw."
                    },
                    "wrappedDek": {
                      "type": "string",
                      "description": "The same DEK wrapped under the active KEK, for reference/audit only."
                    },
                    "envelope": {
                      "type": "object",
                      "properties": {
                        "envelope_version": {
                          "type": "integer"
                        },
                        "cipher_suite": {
                          "type": "string"
                        },
                        "kek_kid": {
                          "type": "string"
                        }
                      }
                    },
                    "uploadTarget": {
                      "type": "object",
                      "properties": {
                        "objectId": {
                          "type": "string"
                        },
                        "schemaVersion": {
                          "type": "integer"
                        }
                      }
                    },
                    "expiresAt": {
                      "type": "integer",
                      "description": "Unix ms handle expiry."
                    },
                    "evidenceGroupId": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Echo of the request's `evidenceGroupId`; present only for an evidence-group handle."
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "handle": "example-handle",
                  "dek": "example-dek",
                  "wrappedDek": "example-wrappedDek",
                  "envelope": {
                    "envelope_version": 1,
                    "cipher_suite": "example-cipher_suite",
                    "kek_kid": "example-kek_kid"
                  },
                  "uploadTarget": {
                    "objectId": "example-objectId",
                    "schemaVersion": 1
                  },
                  "expiresAt": 1768435200,
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Evidence storage temporarily unavailable (handle secret or KEK unresolved).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/evidence/finalize": {
      "post": {
        "operationId": "kycEvidenceFinalize",
        "tags": [
          "KYC Evidence"
        ],
        "summary": "Upload sealed ciphertext and finalize the evidence object",
        "description": "Step 2 of 2. Presents the handle from `POST /v1/kyc/evidence/handle` plus the chunked-AEAD-sealed ciphertext body. The server performs a controlled in-memory decrypt → structural inspection (never persisting plaintext) and promotes the object to `usable` or `rejected`. A repeated call with the same handle/body replays the stored terminal result byte-for-byte (idempotent by construction, not by an `Idempotency-Key` header).",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "handle",
                  "objectId",
                  "commitment",
                  "chunks"
                ],
                "properties": {
                  "handle": {
                    "type": "string",
                    "description": "The opaque handle from the mint step."
                  },
                  "objectId": {
                    "type": "string"
                  },
                  "commitment": {
                    "type": "object",
                    "required": [
                      "total_chunks",
                      "total_plaintext_length"
                    ],
                    "properties": {
                      "total_chunks": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "total_plaintext_length": {
                        "type": "integer",
                        "minimum": 0
                      }
                    }
                  },
                  "chunks": {
                    "type": "array",
                    "description": "Chunked-AEAD-sealed ciphertext chunks (schema version carried in the mint-step envelope).",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "handle": "example-handle",
                "objectId": "example-objectId",
                "commitment": {
                  "total_chunks": 1,
                  "total_plaintext_length": 0
                },
                "chunks": [
                  "example-chunks"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Terminal inspection outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "state",
                    "objectId",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "usable",
                        "rejected"
                      ]
                    },
                    "reasonCode": {
                      "type": "string",
                      "enum": [
                        "evidence_kind_forbidden",
                        "evidence_mime_mismatch",
                        "evidence_size_mismatch",
                        "evidence_pixels_invalid",
                        "evidence_unreadable"
                      ],
                      "description": "Present only when `state` is `rejected`."
                    },
                    "objectId": {
                      "type": "string"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "state": "usable",
                  "reasonCode": "evidence_kind_forbidden",
                  "objectId": "example-objectId",
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "description": "The presented handle failed verification (signature, expiry, manifest, or binding mismatch) — a single uniform refusal, no probe oracle.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Evidence storage temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verifications/{verificationId}/evidence": {
      "get": {
        "operationId": "kycVerificationEvidenceList",
        "tags": [
          "KYC Evidence"
        ],
        "summary": "List the evidence images of a verification",
        "description": "Server-to-server. Lists the images a person captured during one of your own verifications,\nso that your own operators can look at them: the front and back of an identity document, a\npassport page, a proof of address, a selfie, a capture of the document held next to the face,\nand supporting documents. Only still images whose storage finished successfully are listed.\nVideos, voice recordings, the identity details Noxtica extracted (read those with\n`GET /v1/kyc/verifications/{verificationId}/identity`) and anything Noxtica derived from the\nimages are never listed and cannot be opened through these routes. The response holds no image\nbytes: ask for one with `POST /v1/kyc/verifications/{verificationId}/evidence/{itemId}/access`,\nthen fetch the signed URL it returns.\n\n**Opt-in.** The key needs the `read:evidence` scope, and every evidence image route answers\n`403 evidence_read_not_enabled` until a tenant administrator enables evidence image access for\nyour organisation in the Backoffice (KYC → Invitations → Settings tab, card \"Evidence image API\"):\nthe administrator records the legal basis for receiving these images, Article 6 and Article 9 of\nthe GDPR, with step-up multi-factor authentication. Identity document images and selfies are\nspecial-category personal data: enable the access only for the operators who need it. Evidence\nstorage must also be armed for your organisation (card \"Evidence storage\" on the same tab),\notherwise the answer is `403 evidence_storage_not_armed`. While Noxtica has the evidence image API\noff for the platform, every route answers the byte-identical `404` of an unknown path. A key keeps its scopes\nuntil a tenant administrator changes them (Edit scopes on the key in the Backoffice, API\nConfiguration): add `read:evidence` to the key, or create a new key that carries it. When a\ntenant administrator withdraws the enablement, the very next request is refused, a signed URL\nthat has not expired yet included.\n\n**Which id.** `verificationId` is the id returned by `POST /v1/kyc/verify` (a UUID) or the\n`verificationId` of a hosted session (`hosted:` followed by 64 lower-case hexadecimal\ncharacters); see `KycVerificationId` for the formats and the 128-character maximum. The id of\na hosted session and the `evidenceGroupId` of a verification made from your own capture\nscreens are accepted too and resolve to the same verification. An id that is malformed,\nunknown, or belongs to another organisation gets the same byte-identical `404` as an unknown\npath.\n\n**What is listed.** Newest first, at most 100 items, only images that are ready to be opened.\n`type` and `side` tell the images apart:\n\n| `type` | `side` | The image shows |\n|---|---|---|\n| `document` | `front` or `back` | One side of an identity document. |\n| `passport_page` | `null` | A page of a passport. |\n| `proof_of_address` | `null` | A proof of address. |\n| `selfie` | `null` | A selfie of the person. |\n| `document_near_face` | `null` | The document held next to the face. |\n| `supporting_document` | `null` | A supporting document the person added. |\n\n`contentType` is `image/jpeg`, `image/png` or `image/webp`, `size` is the size in bytes and\n`capturedAt` is when Noxtica received the image. An item has no hash, purpose or storage\ndetail.\n\n**Audit and handling.** Listing does not release an image and is not itself recorded in the\naccess log; asking for access and fetching an image are (see `kycVerificationEvidenceAccess`\nand `kycEvidenceContent`). Every response of this route carries `Cache-Control: no-store,\nprivate`. Images are never sent in webhooks.\n\n**Rate limit.** A per-key limit applies to this route, the access route and the access log\ntogether; a limited request answers `429` with `Retry-After`. Fetching a signed URL has a\nseparate per-key limit.",
        "security": [
          {
            "ApiKeyAuth": [
              "read:evidence"
            ]
          }
        ],
        "parameters": [
          {
            "name": "verificationId",
            "in": "path",
            "required": true,
            "description": "The `verificationId` of the verification.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9:_-]{1,128}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The images of the verification that can be opened, newest first.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/EvidenceNoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycEvidenceList"
                },
                "example": {
                  "ok": true,
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "items": [
                    {
                      "itemId": "evd_example_0003",
                      "type": "selfie",
                      "side": null,
                      "contentType": "image/png",
                      "size": 95311,
                      "capturedAt": "2026-11-05T10:12:40.000Z"
                    },
                    {
                      "itemId": "evd_example_0002",
                      "type": "document",
                      "side": "back",
                      "contentType": "image/jpeg",
                      "size": 171903,
                      "capturedAt": "2026-11-05T10:11:58.000Z"
                    },
                    {
                      "itemId": "evd_example_0001",
                      "type": "document",
                      "side": "front",
                      "contentType": "image/jpeg",
                      "size": 183422,
                      "capturedAt": "2026-11-05T10:11:12.000Z"
                    }
                  ],
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/EvidenceInvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/EvidenceReadForbidden"
          },
          "404": {
            "$ref": "#/components/responses/EvidenceReadNotFound"
          },
          "410": {
            "$ref": "#/components/responses/EvidenceVerificationDeleted"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "$ref": "#/components/responses/EvidenceReadUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verifications/{verificationId}/evidence/{itemId}/access": {
      "post": {
        "operationId": "kycVerificationEvidenceAccess",
        "tags": [
          "KYC Evidence"
        ],
        "summary": "Get a short-lived signed URL for one evidence image",
        "description": "Server-to-server. Returns a signed URL for one image listed by\n`GET /v1/kyc/verifications/{verificationId}/evidence`. Hand the URL to the operator's browser,\nor fetch it from your backend and pass the bytes on. The URL is the credential:\n`GET /v1/kyc/evidence/content` needs no `Authorization` header. The key needs the\n`read:evidence` scope, and the opt-in described on `kycVerificationEvidenceList` applies.\n\n**The URL.** It is valid for 120 seconds from the grant (see `expiresAt`). The lifetime is\nfixed: it cannot be extended or requested longer. Within that time the URL can be fetched\nmore than once, for example when a browser reloads the image; every fetch is recorded and\nrate limited. Anyone who holds the URL can fetch the image until it expires, so treat it as a\nsecret: give it only to the operator who asked to open the image, never log or store it, and\nnever put it on a page that loads content from other sites. Ask for a new URL instead of\nkeeping one. The URL has the host you called, and its query holds an opaque token: do not\nparse, shorten or rebuild it.\n\n**What stops a URL early.** A URL stops working at once when the key that asked for it is\nrevoked or loses the `read:evidence` scope, when a tenant administrator withdraws the\nenablement, and when the image no longer exists.\n\n**Which ids.** `verificationId` is accepted in the same forms as on\n`kycVerificationEvidenceList`. `itemId` is the `itemId` of an item of that list. A malformed\nor unknown id, an item that belongs to another verification or another organisation, and an\nitem that is not a readable image (a video, a voice recording, extracted data) all get the\nsame byte-identical `404` as an unknown path.\n\n**Availability.** An item of this verification that is an image but whose storage did not\nfinish successfully answers `409 evidence_not_available` with `reason` `not_usable`. A\nverification you deleted answers `410 verification_deleted`.\n\n**Audit and handling.** Each grant is recorded for your organisation before the URL is\nreleased: the key, the verification, the type of image, the time and, as Noxtica saw them,\nthe caller address and user agent; never the image. If the record cannot be written, nothing\nis released and the answer is `503 evidence_audit_unavailable`. Read the record with\n`GET /v1/kyc/verifications/{verificationId}/evidence/access-log`. Every response of this route\ncarries `Cache-Control: no-store, private`: do not cache or log the body.\n\n**Request body.** None is needed. An empty JSON object is accepted and ignored.\n\n**Rate limit.** The same per-key limit as `kycVerificationEvidenceList`.",
        "security": [
          {
            "ApiKeyAuth": [
              "read:evidence"
            ]
          }
        ],
        "parameters": [
          {
            "name": "verificationId",
            "in": "path",
            "required": true,
            "description": "The `verificationId` of the verification.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9:_-]{1,128}$"
            }
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "description": "The `itemId` of an item of the evidence list of that verification.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,128}$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Ignored. Send no body or an empty object."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A signed URL for the image. It is valid until `expiresAt`, 120 seconds after the grant.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/EvidenceNoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycEvidenceAccess"
                },
                "example": {
                  "ok": true,
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "itemId": "evd_example_0001",
                  "url": "https://collect.noxtica.com/v1/kyc/evidence/content?token=v1.EXAMPLE_PAYLOAD.EXAMPLE_SIGNATURE",
                  "expiresAt": "2026-11-05T10:13:12.000Z",
                  "expiresInSeconds": 120,
                  "contentType": "image/jpeg",
                  "size": 183422,
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/EvidenceInvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/EvidenceReadForbidden"
          },
          "404": {
            "$ref": "#/components/responses/EvidenceReadNotFound"
          },
          "409": {
            "$ref": "#/components/responses/EvidenceNotAvailable"
          },
          "410": {
            "$ref": "#/components/responses/EvidenceVerificationDeleted"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "$ref": "#/components/responses/EvidenceReadUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/evidence/content": {
      "get": {
        "operationId": "kycEvidenceContent",
        "tags": [
          "KYC Evidence"
        ],
        "summary": "Fetch an evidence image with a signed URL",
        "description": "Returns the bytes of one image. The signed URL that `kycVerificationEvidenceAccess` returned\nis the credential: send no `Authorization` header, and use the URL exactly as it was given.\n\n**The response.** `200` carries the image itself. `Content-Type` is the type of the stored\nimage (`image/jpeg`, `image/png` or `image/webp`), `Content-Disposition` is\n`inline; filename=\"evidence-<itemId>.<jpg|png|webp>\"` and `Content-Length` is the exact size.\nEvery response of this route, errors included, carries `Cache-Control: no-store, private`,\n`X-Content-Type-Options: nosniff` and `Referrer-Policy: no-referrer`: do not cache or store the\nresponse.\n\n**Checks on every fetch.** A URL that was altered, is malformed or has expired answers\n`401 invalid_or_expired_url`; the body is the same for every one of those causes. The same\n`401` follows when the key that asked for the URL has been revoked or no longer carries the\n`read:evidence` scope. The tenant administrator's enablement and evidence storage must still\nbe in place, otherwise the answer is `403 evidence_read_not_enabled` or\n`403 evidence_storage_not_armed`. An image that no longer exists, or that is not a readable\nimage of the verification the URL was granted for, answers the byte-identical `404`.\n\n**Audit.** Each fetch is recorded for your organisation before any byte is released, with the\nkey, the verification, the type of image, the time and, as Noxtica saw them, the caller\naddress and user agent. If the record cannot be written, nothing is released and the answer is\n`503 evidence_audit_unavailable`. A request that is refused before that point is not\nrecorded. The record is readable with\n`GET /v1/kyc/verifications/{verificationId}/evidence/access-log`.\n\n**Stored image check.** Noxtica checks that the bytes are an image of the type they were\nstored as before serving them. If they are not, nothing is served and the answer is\n`502 evidence_unreadable`.\n\n**Rate limit.** A per-key limit applies to fetches, separate from the one on the other\nevidence image routes; a limited request answers `429` with `Retry-After`.",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "description": "The signed token of the URL returned by `kycVerificationEvidenceAccess`. Use the URL as returned; do not build or change the token.",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "example": "v1.EXAMPLE_PAYLOAD.EXAMPLE_SIGNATURE"
          }
        ],
        "responses": {
          "200": {
            "description": "The image.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/EvidenceNoStore"
              },
              "Content-Disposition": {
                "$ref": "#/components/headers/EvidenceContentDisposition"
              },
              "Content-Length": {
                "description": "The exact size of the image in bytes.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-Content-Type-Options": {
                "description": "Always `nosniff`.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "nosniff"
                  ]
                }
              }
            },
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_or_expired_url` — the URL was altered, is malformed or has expired, or the key that asked for it is revoked or no longer carries the `read:evidence` scope. The body is the same for every cause.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/EvidenceNoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_or_expired_url",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "`evidence_read_not_enabled` — a tenant administrator has withdrawn (or never gave) the enablement for your organisation; or `evidence_storage_not_armed` — evidence storage is not on for your organisation. Nothing was released. A tenant administrator fixes either in the Backoffice, KYC → Invitations → Settings tab (cards \"Evidence image API\" and \"Evidence storage\").",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/EvidenceNoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "evidence_read_not_enabled",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/EvidenceReadNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "`evidence_unreadable` — the stored bytes are not an image of the type they were stored as, so nothing was served. The fetch record had already been written.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/EvidenceNoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "evidence_unreadable",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EvidenceReadUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verifications/{verificationId}/evidence/access-log": {
      "get": {
        "operationId": "kycVerificationEvidenceAccessLog",
        "tags": [
          "KYC Evidence"
        ],
        "summary": "List who opened the evidence images of a verification",
        "description": "Server-to-server. Lists the openings of the images of one of your own verifications, newest\nfirst: one row for each access grant and one for each fetch of a signed URL. Use it to answer\nwho looked at an image, from where and when. A row never holds image bytes, a signed URL or\nits token. The key needs the `read:evidence` scope.\n\n**After the enablement is withdrawn.** Unlike the other evidence image routes, this one does\nnot need the tenant administrator's enablement to be in place, nor evidence storage to be on,\nso you can still read the history after the enablement was withdrawn. It is not available\nwhile evidence image access is not available for your organisation (`404`).\n\n**Which id.** `verificationId` is accepted in the same forms as on\n`kycVerificationEvidenceList`; a malformed, unknown or foreign id gets the same\nbyte-identical `404`. A verification you deleted answers `410 verification_deleted`.\n\n**Rows.** `event` is `grant` (a signed URL was issued) or `fetch` (the image was served).\n`itemId` is the image, `apiKeyId` the key that asked for the URL, and `at` the time. `ip` and\n`userAgent` are what Noxtica saw on the request (the address the request came from and its\n`User-Agent` header, shortened to 256 characters) and are `null` when there was none.\n\n**Paging.** `limit` is 1 to 100 (default 50). When more rows exist, `nextBefore` is a cursor in\nUnix milliseconds: pass it unchanged as `before` to read the next page. It is `null` on the\nlast page. A `limit` or `before` that is repeated, not a whole number or out of range answers\n`400 invalid_request`.\n\n**Handling.** Every response of this route carries `Cache-Control: no-store, private`.\n\n**Rate limit.** The same per-key limit as `kycVerificationEvidenceList`.",
        "security": [
          {
            "ApiKeyAuth": [
              "read:evidence"
            ]
          }
        ],
        "parameters": [
          {
            "name": "verificationId",
            "in": "path",
            "required": true,
            "description": "The `verificationId` of the verification.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9:_-]{1,128}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "The most rows to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "A cursor in Unix milliseconds. Only rows older than it are returned. Use `nextBefore` of the previous page unchanged.",
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            },
            "example": 1793700000000
          }
        ],
        "responses": {
          "200": {
            "description": "The recorded openings of the images of the verification, newest first.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/EvidenceNoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycEvidenceAccessLog"
                },
                "example": {
                  "ok": true,
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "events": [
                    {
                      "id": "req-example-0002",
                      "at": "2026-11-05T10:11:40.000Z",
                      "event": "fetch",
                      "itemId": "evd_example_0001",
                      "apiKeyId": "key_example_01",
                      "ip": "203.0.113.7",
                      "userAgent": "ExampleBackend/1.0"
                    },
                    {
                      "id": "req-example-0001",
                      "at": "2026-11-05T10:11:12.000Z",
                      "event": "grant",
                      "itemId": "evd_example_0001",
                      "apiKeyId": "key_example_01",
                      "ip": "203.0.113.7",
                      "userAgent": "ExampleBackend/1.0"
                    }
                  ],
                  "nextBefore": null,
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/EvidenceInvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/EvidenceReadForbidden"
          },
          "404": {
            "$ref": "#/components/responses/EvidenceReadNotFound"
          },
          "410": {
            "$ref": "#/components/responses/EvidenceVerificationDeleted"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "$ref": "#/components/responses/EvidenceReadUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/sessions": {
      "post": {
        "operationId": "kycSessionMint",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Mint a Noxtica-hosted KYC verification-session link",
        "description": "Server-to-server. Creates a hosted verification session and returns a one-shot,\nopaque URL to hand to your end customer (redirect, email, SMS or QR). The URL embeds a\nbearer session token that is never returned again by any route: treat it as a secret,\ndo not log it and do not put it in an analytics URL. Minting is free; the verification is\ncounted when the customer submits.\n\n**Before you can mint.** (1) The identity verification module must be enabled for your organisation;\nuntil it is, this route answers the same byte-identical `404` as an unknown path. (2) Your\n`sk_` key needs the `write:identity` scope (a key created with the default scope is\nrefused with `403`). (3) A tenant administrator must have recorded an active legal-basis\nreceipt for hosted document capture in the Backoffice; without it the mint is refused with\n`403 kyc_legal_basis_receipt_required`. Noxtica never selects a legal basis for you. (4)\nYour organisation needs a hosted configuration (business name, privacy URL, supported\ndocuments) in the Backoffice, or the customer sees \"Verification isn't ready yet\"; the mint\nitself does not check this. (5) Your organisation's raw-evidence storage must be provisioned\nin the Backoffice: evidence storage armed and an evidence residency of `TR`, `EU` or `US`\nset. A link for an organisation that cannot store the customer's capture would fail after\nthe first photo, so the mint is refused before any session exists with\n`403 evidence_not_provisioned` and a `reason` (`raw_storage_not_armed` or\n`residency_unconfigured`). Retrying does not help until the organisation is provisioned.\n\n**Request.** `subjectRef` is required. The expiry field is `expiresInSeconds`: a key named\n`ttlSeconds` is not read and is silently ignored (the link then lasts the 24 hour default).\nUnknown keys are ignored. The snake_case spellings `subject_ref`, `return_url`,\n`id_type`, `expires_in_seconds` and `hosted_options` are also accepted; the camelCase names\nare the contract. Locale, customer name, email and phone are not request fields: the page\nlanguage follows the customer's browser and cannot be forced, and the webhook endpoint is\nregistered once for your organisation in the Backoffice, not per session.\n\n**Lifetime and expiry.** `expiresAt` is Unix time in **milliseconds** in every response.\nAt mint it is `now + expiresInSeconds`: 24 hours (86400 s) by default, or the 5\nminutes (300 s) to 7 days (604800 s) you ask for; a value outside that range is refused\nwith `400 invalid_request` and is never clamped. An unopened link keeps that whole\nvalidity. The first customer action the platform records on the hosted page (for example\naccepting the privacy notice) opens the session: `openedAt` is stamped and `expiresAt`\nbecomes `min(expiresAt, openedAt + 30 minutes)`, so the customer must finish capture\ninside that window. The deadline only ever moves earlier: a link minted for 10 minutes\nstill ends 10 minutes after mint, and a link minted for 24 hours ends 30 minutes after the\ncustomer first acts. It is never extended and there is no renewal call: when a customer\nneeds more time, mint a new session. Each timed capture step (document front or back,\nselfie, document near face, liveness, voice) also has its own 10 minute limit; a step that\nruns out restarts and the session carries on until its own deadline. When the deadline\npasses the session is `expired`: the platform records it on the next read or on its expiry\nsweep (every 5 minutes), the hosted page answers `410 session_expired`,\n`GET /v1/kyc/sessions/{sessionId}` keeps answering `200` with `status: \"expired\"`, a\nsession that was never submitted is never billed, and `kyc.session.completed` is never\nsent. A session in `review_pending` never expires: it waits for a Backoffice reviewer.\n\n**Using the link.** The hosted page cannot be embedded in an iframe (`X-Frame-Options:\nDENY`); open it in a new tab, or redirect the customer to it. The page never posts a\nmessage to the opener or parent window and never redirects on its own. If you minted with\na `returnUrl`, the page shows a plain \"Return to <business>\" link to exactly that URL\n(nothing is appended) on the completion and status screens only for a completed outcome,\nand on the screen shown when a step cannot continue. Sessions created from Backoffice\ninvitations carry no `returnUrl`. Correlate by the `sessionId` you stored at mint, and\nnever treat a visit to your return page as proof of completion.\n\n**Outcome.** Learn the outcome with the `kyc.session.completed` webhook or by polling\n`GET /v1/kyc/sessions/{sessionId}`. With the identity provider configured today every\nsuccessful capture ends in `review_pending`, not `completed`: the webhook fires only\nafter a Backoffice reviewer approves or overrides the case (usually within seconds,\nat most about 5 minutes after the decision), and a `review_pending` session never\nexpires. Read `verification` and `review` on the status read to tell an approval from an\noverride. See that route for the status table.\n\n**Email verification (optional step).** `hostedOptions.steps.emailVerification`\n(`off`, `optional` or `required`) adds an email-verification step to the hosted flow: the\ncustomer types an address, receives one confirmation link, and only a click on that link\nmarks the address verified. Omit the key to use your organisation's default (Hosted\nconfiguration); an explicit value for this link wins in both directions. The effective\nmode is resolved once, here, and is fixed for the session. This is available only once the\nplatform has enabled hosted email verification: until then an explicit `optional` or\n`required` is refused with `400 email_verification_unavailable` (an explicit `off` is\nalways accepted, and your organisation default is ignored), so a `required` session can\nnever silently run unverified.\n\n**Domains (optional).** One organisation can own several domains, and each domain can carry\nits own hosted configuration (business name, purpose, privacy and support pages, supported\ndocuments, approved return hosts, embed origins, email verification) that overrides your\norganisation default field by field; see `GET /v1/kyc/config`. The invitation is created\nfor one domain, chosen in this order: `domainId` in the body, then `siteKey` in the body,\nthen the request's `Origin` header when it exactly equals the origin of one of your active\ndomains (any other origin is ignored). With none of these the invitation uses your\norganisation default. A `domainId` and a `siteKey` that name different domains are refused\nwith `400 domain_mismatch`; an unknown or disabled domain, or one that belongs to another\norganisation, is refused with `404 domain_not_found` (the same answer in all three cases,\nso the route cannot be used to discover other organisations' domains). The domain is fixed\nat mint: the `returnUrl` check and the email-verification mode use that domain's\nconfiguration, and the hosted page always shows the configuration the domain has at that\nmoment (if you remove a domain's override later, its sessions fall back to your\norganisation default). The mint response and the status read carry the session's\n`domainId`, `null` for the organisation default. Until a domain has its own configuration,\nevery invitation behaves as before.\n\n**Countries (optional).** `hostedOptions.allowedIssuingCountries` narrows one invitation to\nsome issuing countries, for example the countries you expect this customer to come from,\nwithout changing your hosted configuration. Send any ISO 3166-1 alpha-2 codes\n(`CountryCodeList`: 1 to 249 distinct codes; all 249 is the same as sending none). Each\ndocument type your configuration accepts keeps only the issuing countries that are also in\nthe list, and a document type left with no country is dropped from the invitation. When no\ndocument type is left the mint is refused with `400 invalid_hosted_options` (`field`\n`allowedIssuingCountries`, `reason` `no_matching_document_country`) and no session is\ncreated. Omit the key to keep every configured country. A list outside your configuration\nis not an error as long as one document type remains. `hostedOptions` must be the complete\nobject when you send it, so send this key together with the other required keys. The list\nis fixed at mint: the hosted page shows your configuration at that moment narrowed by it,\nand the customer cannot choose a country outside it.\n\n**Idempotency.** `Idempotency-Key` is optional (8 to 128 characters). Without it every\ncall mints a fresh session. With it, a retry under the same key returns the SAME session\nwith `replayed: true` and WITHOUT the `url`: the one-shot URL of a lost response cannot be\nrecovered, so mint a new session under a new key instead. The replay matches on the key\nonly; a different body under the same key still returns the original session.\n\n**Rate limits.** A per-source-IP limit applies first, before authentication, then a per-key limit\nshared with the status read. See \"Rate limits\" above.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subjectRef"
                ],
                "properties": {
                  "subjectRef": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256,
                    "description": "Your own opaque reference for this end customer (trimmed, non-empty). It is returned to you as `subjectId` on the status read and is the identifier an erasure request targets. It is never sent in webhooks."
                  },
                  "returnUrl": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "pattern": "^https://",
                    "description": "Where the \"Return to <business>\" link on the hosted page points. `https` only, no user info, at most 2048 characters, and the host must exactly equal (case insensitive) an entry in your organisation's approved return hosts (`allowedReturnUrlHosts` in the Backoffice hosted configuration; at most 20 entries, host names only, no wildcards; port, path and query are not checked). An organisation with no approved hosts cannot send a `returnUrl`. Anything else is refused with `400 return_url_not_allowed`. The page appends nothing to it. For an invitation created for a domain, the list is that domain's own when it sets one, otherwise your organisation's."
                  },
                  "purpose": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Shown to the customer on the consent screen. Falls back to the default purpose of the invitation's domain, then to your organisation's default purpose. More than 500 characters is refused with `400 invalid_request`."
                  },
                  "idType": {
                    "type": "string",
                    "enum": [
                      "national_id",
                      "passport",
                      "driving_license",
                      "residence_permit"
                    ],
                    "description": "Document type hint; it must be one of `hostedOptions.allowedDocumentTypes` (default: the first allowed type, `national_id` when `hostedOptions` is omitted), else `400 invalid_id_type`. The customer still chooses the type and issuing country on the page from the allowed types your organisation configured."
                  },
                  "expiresInSeconds": {
                    "type": "integer",
                    "minimum": 300,
                    "maximum": 604800,
                    "default": 86400,
                    "description": "Lifetime of the UNOPENED link in seconds: 5 minutes to 7 days, default 24 hours. A value outside the range or a non-integer is refused with `400 invalid_request` (it is not clamped). After the customer opens the link the 30 minute capture window applies."
                  },
                  "hostedOptions": {
                    "$ref": "#/components/schemas/KycHostedSessionOptions"
                  },
                  "domainId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "Create the invitation for this domain of your organisation (the `d_...` id shown on the Backoffice Domains page), so that it uses that domain's hosted configuration. Optional. It is chosen before `siteKey` and the `Origin` header (see \"Domains\" above). An unknown, disabled or foreign domain is refused with `404 domain_not_found`."
                  },
                  "siteKey": {
                    "type": "string",
                    "minLength": 1,
                    "description": "The site key (`pk_...`) of one of your active domains, as an alternative to `domainId`. When both are sent they must name the same domain, otherwise the call is refused with `400 domain_mismatch`. Optional. An unknown or disabled site key is refused with `404 domain_not_found`."
                  }
                }
              },
              "example": {
                "subjectRef": "cust_4821",
                "returnUrl": "https://merchant.example.com/kyc/return",
                "purpose": "Open your brokerage account",
                "idType": "passport",
                "expiresInSeconds": 3600,
                "hostedOptions": {
                  "allowedDocumentTypes": [
                    "passport",
                    "national_id"
                  ],
                  "allowedIssuingCountries": [
                    "TR",
                    "GB",
                    "DE"
                  ],
                  "allowDeviceUpload": false,
                  "steps": {
                    "documentBack": "auto",
                    "documentNearFace": false,
                    "activeLiveness": true,
                    "speechAnalysis": false,
                    "ageGenderEstimation": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session minted, or (idempotent replay) the existing session returned without its URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "sessionId",
                    "expiresAt",
                    "domainId",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "sessionId": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The session id. Store it with your customer record; it is the correlation handle for status reads and webhooks."
                    },
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "One-shot hosted-flow URL; the ONLY place the bearer token is ever returned. Absent on an idempotent replay."
                    },
                    "expiresAt": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Unix time in milliseconds at which the unopened link expires."
                    },
                    "replayed": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Present (and `true`) only when an `Idempotency-Key` matched an existing session; `url` is then absent."
                    },
                    "hostedOptions": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/KycHostedSessionOptions"
                        }
                      ],
                      "description": "The effective hosted options stored with the session, including the resolved `steps.emailVerification` mode (your organisation default applied when the request did not set it). Present only when hosted email verification is enabled for the platform AND the session's effective mode is not `off`; absent otherwise (a session that does not use the step gets the pre-feature response)."
                    },
                    "domainId": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The domain the invitation was created for, or `null` when it uses your organisation default. Fixed at mint; the status read returns it too."
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "examples": {
                  "created": {
                    "summary": "Fresh mint",
                    "value": {
                      "ok": true,
                      "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                      "url": "https://verify.noxtica.com/s/kycs.tenant_42.q3JkT0vN8mYb1xW6cE4aZ7uP2sH9dLfR5gKiA0oXyUw",
                      "expiresAt": 1790765600000,
                      "domainId": null,
                      "requestId": "req-test"
                    }
                  },
                  "replayed": {
                    "summary": "Idempotent replay (no url)",
                    "value": {
                      "ok": true,
                      "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                      "expiresAt": 1790765600000,
                      "domainId": null,
                      "replayed": true,
                      "requestId": "req-test"
                    }
                  },
                  "createdForDomain": {
                    "summary": "Fresh mint for one domain",
                    "value": {
                      "ok": true,
                      "sessionId": "0b7f2d64-3c1a-4e58-9a6d-5f8e2c4b7a10",
                      "url": "https://verify.noxtica.com/s/kycs.tenant_42.Zk8wN2xQe5JrT1yB7vLc4mHs9aDfG3uP6oIiXq0RtWd",
                      "expiresAt": 1790765600000,
                      "domainId": "d_7Kp3Zx9QmW2aLr5T",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` (unreadable JSON, `Idempotency-Key` outside 8 to 128 characters, `subjectRef` missing or longer than 256, `expiresInSeconds` outside 300 to 604800 or not an integer, `purpose` longer than 500, a bad `payload`), `invalid_id_type`, `invalid_hosted_options` (`hostedOptions` is not a complete, well-formed object, `steps.emailVerification` is not one of `off`, `optional`, `required`, or `allowedIssuingCountries` is not a list of 1 to 249 distinct ISO 3166-1 alpha-2 codes, with `field` `allowedIssuingCountries`; also when none of your configured document types is offered in any listed country, with `reason` `no_matching_document_country`), `return_url_not_allowed`, `email_verification_unavailable` (an explicit `steps.emailVerification` of `optional` or `required` was requested but hosted email verification is not enabled for the platform), or `domain_mismatch` (`domainId` and `siteKey` name different domains).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycHostedOptionsError"
                },
                "examples": {
                  "invalidRequest": {
                    "value": {
                      "error": "invalid_request",
                      "requestId": "req-test"
                    }
                  },
                  "returnUrlNotAllowed": {
                    "value": {
                      "error": "return_url_not_allowed",
                      "requestId": "req-test"
                    }
                  },
                  "emailVerificationUnavailable": {
                    "value": {
                      "error": "email_verification_unavailable",
                      "requestId": "req-test"
                    }
                  },
                  "domainMismatch": {
                    "value": {
                      "error": "domain_mismatch",
                      "requestId": "req-test"
                    }
                  },
                  "invalidAllowedIssuingCountries": {
                    "value": {
                      "error": "invalid_hosted_options",
                      "field": "allowedIssuingCountries",
                      "requestId": "req-test"
                    }
                  },
                  "noMatchingDocumentCountry": {
                    "value": {
                      "error": "invalid_hosted_options",
                      "field": "allowedIssuingCountries",
                      "reason": "no_matching_document_country",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "description": "The key lacks the `write:identity` scope; or `kyc_legal_basis_receipt_required` (no active hosted-capture legal-basis receipt for your organisation); or `evidence_not_provisioned` (your organisation's raw-evidence storage is not armed, `reason` `raw_storage_not_armed`, or is armed without an evidence residency of `TR`, `EU` or `US`, `reason` `residency_unconfigured`; no session was created); or `age_gender_estimation_not_enabled` (`hostedOptions.steps.ageGenderEstimation` was `true` but the platform feature or your organisation's acknowledgement is missing; see `reasonCode`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "examples": {
                  "scope": {
                    "value": {
                      "error": "API key does not have write:identity scope",
                      "requestId": "req-test"
                    }
                  },
                  "legalBasisReceiptRequired": {
                    "value": {
                      "error": "kyc_legal_basis_receipt_required",
                      "requestId": "req-test"
                    }
                  },
                  "ageGenderNotEnabled": {
                    "value": {
                      "error": "age_gender_estimation_not_enabled",
                      "reasonCode": "age_gender_platform_disabled",
                      "requestId": "req-test"
                    }
                  },
                  "evidenceNotProvisioned": {
                    "value": {
                      "error": "evidence_not_provisioned",
                      "reason": "raw_storage_not_armed",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedOrDomainNotFound"
          },
          "413": {
            "description": "`body_too_large` — the request body exceeds 32768 bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "example": {
                  "error": "body_too_large",
                  "requestId": "req-test"
                }
              }
            }
          },
          "415": {
            "description": "The request is not `Content-Type: application/json` (body `invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "description": "Retryable unless noted. `tenant_module_gate_unavailable` (the module gate could not be answered; `Retry-After: 2`), `kyc_legal_basis_receipt_unavailable` (the receipt check faulted; `reason`, `Retry-After: 2`), `hosted_verify_not_configured` (the hosted deployment is not configured; contact support, no `Retry-After`), or `invalid_request` with a `reason` (a database or backend fault; `Retry-After: 2`). Retry a `503` with the SAME `Idempotency-Key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "examples": {
                  "receiptCheckUnavailable": {
                    "value": {
                      "error": "kyc_legal_basis_receipt_unavailable",
                      "message": "The legal-basis receipt required for this request could not be confirmed",
                      "reason": "backend_unavailable",
                      "requestId": "req-test"
                    }
                  },
                  "moduleGateUnavailable": {
                    "value": {
                      "error": "tenant_module_gate_unavailable",
                      "message": "The tenant module gate could not be confirmed; retry shortly",
                      "requestId": "req-test"
                    }
                  },
                  "hostedVerifyNotConfigured": {
                    "value": {
                      "error": "hosted_verify_not_configured",
                      "requestId": "req-test"
                    }
                  },
                  "backendFault": {
                    "value": {
                      "error": "invalid_request",
                      "reason": "pg_unavailable",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/sessions/{sessionId}": {
      "get": {
        "operationId": "kycSessionStatus",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Read a hosted session's current status (tenant backend read)",
        "description": "Server-to-server. The authoritative tenant-side read of a hosted session; use it to\nconfirm any webhook before you act on it, and to poll when you have no webhook endpoint.\nEverything is nested under `session`, and every timestamp is Unix time in\n**milliseconds**. A session that is past its deadline reads `200` with\n`status: \"expired\"` (never `410`; the `410` answers belong to the customer's browser\nroutes). A session id that does not exist, or belongs to another organisation, gets the\nsame byte-identical `404` as an unknown path.\n\n**Status lifecycle.**\n\n| `status` | Meaning | `kyc.session.completed` / `kyc.session.cancelled` |\n|---|---|---|\n| `pending` | Minted; the customer has not opened the link. | none |\n| `opened` | The customer reached the capture page. | none |\n| `verifying` | The customer submitted; the verification is running. | none |\n| `review_pending` | The verification finished and awaits a Backoffice review decision. With the identity provider configured today every successful hosted capture lands here. It never expires. | **none yet**; `kyc.session.completed` fires later, when a reviewer decides |\n| `completed` | A business decision exists: a reviewer approved or overrode the case (or, by design, a machine `verified`). `completed` does not mean `verified`: read `verification` and `review` below. | `kyc.session.completed` with `status: completed` |\n| `unverified` | Finished with a negative outcome and no manual-review code (for example capture attempts exhausted). | `kyc.session.completed` with `status: unverified` |\n| `error` | A provider or infrastructure fault, or the submit was refused at admission. | `kyc.session.completed` with `status: error` |\n| `expired` | Past the deadline without a decision (see \"Lifetime and expiry\" on `POST /v1/kyc/sessions`). | never |\n| `cancelled_by_user` | The customer cancelled on the hosted page (only when cancellation is enabled for your organisation). | `kyc.session.cancelled` |\n\n**`review_pending` to `completed`.** `kyc.session.completed` does not fire when a session\nlands in `review_pending`. It fires when a Backoffice reviewer approves or overrides the\ncase, usually within seconds and at most about 5 minutes after the decision. Until then\nthe only way to observe `review_pending` is this read. An override also moves the session\nto `completed`, so `completed` is not an approval. `decision.outcome` is derived from\n`status` alone (`completed` reads `approved`), so it reads `approved` after an override\ntoo: do not use it to tell the two apart. Use `verification` and `review`.\n\n**Verification outcome and review.** Once the session has a `verificationId` (the customer\nsubmitted), `session` carries two more blocks that state what the verification finally is;\nbefore that neither key is present.\n\n- `verification.status` is the outcome: `verified`, `unverified`, `failed`, `error` or\n  `pending` (the verification has no verdict yet). `verification.source` says who produced\n  it: `pipeline` is the automated verdict exactly as recorded, `review` means a reviewer's\n  `approve` decision was applied over it. Only `approve` ever turns the reported status\n  into `verified`; an `override` is reported in `review` but never upgrades the status.\n- `review` is `null` while no reviewer has acted, otherwise `{state, decision, version,\n  decidedAt}`. `state` is `in_review` (escalated, no decision yet), `approved` or\n  `overridden`; the vocabulary also holds `open` and `closed`, which no review action\n  writes today. `decision` is `approve`, `override`, or `null` while the state is\n  `in_review`. `version` is the number of review writes, starting at 1 and increasing by\n  one with every write. `decidedAt` is Unix ms of the last decision, or `null` while there\n  is none. No reviewer identity and no reviewer note is ever returned.\n\n`reasonCodes` keeps the automated reasons after a review: `provider_manual_review` stays\nin the list once a reviewer has approved the case, so it does not mean the case is still\nwaiting. Read `status`, `verification` and `review` instead.\n\n| Situation | `status` | `verification` | `review` |\n|---|---|---|---|\n| Waiting for a reviewer | `review_pending` | `unverified` / `pipeline` | `null` |\n| Escalated, undecided | `review_pending` | `unverified` / `pipeline` | `state: in_review`, `decision: null` |\n| Approved | `completed` | `verified` / `review` | `state: approved`, `decision: approve` |\n| Overridden | `completed` | the automated status / `pipeline` | `state: overridden`, `decision: override` |\n\nThere is no `reject` decision in the review vocabulary today, and a rejected end state\ncannot be expressed. `kyc.verification.completed` is sent once, when the automated\nverification ends (for a hosted session, `unverified` with `provider_manual_review`); it\nis never sent again after a review, so do not wait for it: read `review`.\n\n**Review decision and status events.** A reviewer's `approve` or `override` is announced by the `kyc.review.decided` event, for hosted sessions and for verifications you created through the API; for a hosted session that was `review_pending`, `kyc.session.completed` follows. An escalation (`in_review`) sends nothing. Each status a session enters is also announced by its own opt-in event: `kyc.session.created` (`pending`), `kyc.session.opened`, `kyc.session.verifying`, `kyc.session.review_pending`, `kyc.session.unverified`, `kyc.session.error` and `kyc.session.expired`; see the `webhooks` section.\n\n**Cancellation.** `status: cancelled_by_user` is reported here, but `cancelledAt` and\n`cancelReason` are NOT part of this response; they are delivered in the\n`kyc.session.cancelled` webhook. An expiry never sends `kyc.session.completed` or\n`kyc.session.cancelled`. A cancelled or expired session is never billed as a verification\n(the `kyc.verification` meter is recorded only at submit-time admission); only the\nrequest-level `endpoint.collect.request` counts.\n\n**Email verification result.** Only when hosted email verification is enabled for the\nplatform AND the session's effective mode is not `off` or an address is stored, the\n`session` object also carries `email` (the normalised address the customer entered, or\n`null`), `emailVerified` (`true` only after the customer clicked the confirmation link for\nthat exact address), `emailVerifiedAt` (Unix ms, or `null`) and `emailVerification` (the\neffective mode: `off`, `optional` or `required`). Treat `email` with `emailVerified: false`\nas an unverified, customer-typed value. Otherwise these four keys are absent.\n\n**Noxtica Verification ID.** Only when the Noxtica Verification ID (NVID) is enabled for\nthe platform, the `session` object also carries `nvid`: the session's NVID\n(`NVID-XXXX-XXXX-XXXX-XXXX`) or `null` (none was issued: an NVID is issued when the\nsubmit ends `completed` or `review_pending`). The key is absent while the feature is not\nenabled. The public status page of an NVID is `https://verify.<environment host>/v/<NVID>`;\nthe public lookup route behind it can only be called from the hosted site's browser\norigin, never from a tenant backend.\n\n**Check results.** The per-check results below are evidence for a reviewer and for your own\nrisk rules. Every number is a value the service uses today and may be retuned without a\nversion bump unless a row says otherwise; model, engine and vendor names are deliberately\nnot published.\n\n- **Nothing here rejects a person by itself.** Every hosted verification currently ends\n  `verification.status: unverified` with `reasonCodes: [provider_manual_review]`; a\n  reviewer's approval completes it (`verification.source: review`). A check result never\n  changes `verification.status` on its own.\n- **`fail` means a confident failure; `review` means no confident answer, a person should\n  look.** An advisory or unqualified signal can only read `review`. A `fail` on a person who\n  is clearly genuine is a false reject: report it with the `sessionId`.\n- **While presentation-attack detection (PAD) is advisory, `liveness` and `selfie` never read\n  `pass`.** Their best outcome is `review`, so `scorecard.overall` is at best `review` for\n  every session today. This is expected and is not a problem with the person's capture.\n\nWhat these readings mean for your integration, what to automate and what not to, and every\nPAD and liveness reason code are on the page\n[Liveness & PAD policy](https://noxtica.com/docs/identity-verification-liveness-pad-policy/).\n\n**Thresholds are platform-wide.** Every threshold named below (the selfie quality limits, the\nface-match bands, the liveness and blink limits, the document-authenticity bands and the PAD\noperating points) is one value for all customers. No request carries a tenant, profile or\nthreshold parameter, and neither your organisation default, a domain override nor an\ninvitation changes a threshold: there is no tenant-level or domain-level threshold setting\ntoday. What you can configure, per organisation, per domain (a domain overrides your\norganisation default field by field) and per invitation, is which steps and documents run\nand which identity fields must be read: in the hosted verification configuration\n(`GET /v1/kyc/config`) `documents[]` (`idType`, `issuingCountries`, `requiredIdentityFields`),\n`requireSelfie`, `requireVoice`, `allowDeviceUpload` and `emailVerification`; on an\ninvitation (`POST /v1/kyc/sessions`) `hostedOptions` (`allowedDocumentTypes`,\n`allowedIssuingCountries` and `steps`, for example `steps.activeLiveness`). None of these\nchanges how a check is judged. The verdict of a hosted verification (`unverified` with\n`provider_manual_review` until a reviewer approves) is not configurable either.\n\nWhere the results appear: `session.scorecard.<step>` for `document_front`, `document_back`,\n`selfie`, `liveness`, `voice`, `cross_validation` and `overall`\n(`{status, score 0..1, checks[{name, pass, score}], reasonCodes[]}`), and\n`session.stages.<stage>` (`{status, score, reasonCodes[]}`). The identity read\n(`GET /v1/kyc/verifications/{verificationId}/identity`, scope `read:identity_details`)\nreturns the verdicts only, as `checks.documentAuthenticity`, `checks.faceMatch`,\n`checks.liveness` and `checks.selfie`, without scores.\n\n| `checks.*` on the identity read | Built from | Scorecard step |\n|---|---|---|\n| `documentAuthenticity` | the document authenticity analysis | `document_front` |\n| `faceMatch` | document portrait against selfie (and the liveness frames, the worse of the two) | `selfie` |\n| `liveness` | the active-liveness challenge (five gestures) | `liveness` |\n| `selfie` | selfie quality and PAD, the worse of the two | `selfie` |\n\n**Reading a scorecard reason code.**\n\n| Pattern | Meaning |\n|---|---|\n| `<check>_failed` | that check (a name from the tables below) returned a confident failure. |\n| `<check>_inconclusive` | the analysis could not judge that check (for example a blink that could not be measured). Never a failure. |\n| `<capability>_pass`, `_fail`, `_inconclusive` | the analysis's own overall verdict, in public capability names: `document_authenticity`, `face_match`, `active_liveness`, `presentation_attack_detection`, `face_quality`, `document_recognition`, `document_text_reading`, `mrz_reading`, `barcode_reading`, `document_consistency`. For an advisory analysis `_fail` is that analysis's verdict only; the step still reads `review`. |\n| `awaiting_capture`, `awaiting_evaluation`, `not_required`, `not_evaluated` | the step has no result yet, is not required, or the analysis never produced one (gated or unavailable): `review`. |\n| `not_applicable` | the document type has nothing to measure for this stage (a driving licence has no machine-readable zone): the stage reads `skipped`. |\n| `additional_check_failed` | a check outside the public vocabulary failed; no detail is exposed. |\n\n**Document authenticity** (`checks.documentAuthenticity`, step `document_front`). Advisory:\nthe analysis is an unqualified classical image analysis whose own decision is always manual\nreview. A failed check reads `review` at most; only an overall `pass` reads `pass`. It is\nnever used to reject.\n\n| Check | What it measures | Failing means | Weight |\n|---|---|---|---|\n| `card_geometry` | The document's own outline has the shape of a card or booklet page. | The outline's long to short side ratio differs by more than 0.08 (relative to the reference ratio) from a bank-card (ID-1) card (1.586) or a passport page (1.389). A card smaller than 300 px on its short side, or not found, is `inconclusive`, never `fail`. | hard gate |\n| `moire_recapture` | Screen-photographed or re-photographed document patterns. | A recapture-like pattern was found. | hard gate |\n| `layout_conformance` | The expected elements and photo region of a card of this kind are present. | Too few elements were found (blank, cropped or wrong-side image). | scored |\n| `mrz_structure` | Structure of the machine-readable-zone band (back sides only). | The band is malformed. | scored |\n| `print_consistency` | The print noise pattern is uniform across the card. | Locally inconsistent print texture (possible overlay or edit). | scored |\n| `copy_move` | Duplicated regions inside the image. | Repeated image regions. Soft: never fails alone. | scored |\n| `ela` | Compression-history consistency (JPEG sources). | Locally different compression history. | scored |\n| `hologram_presence` | A light-variable (hologram-like) region is visible. | Not visible at this capture angle. Soft: never fails alone. | scored |\n| `image_similarity_replay` | The image is not a known replayed or duplicate capture. | Matches a known replay (only when a reference catalogue is configured). | scored |\n\nThe analysis is `fail` when a hard-gate check fails or the weighted score is below the fail\nband; `pass` only when every check passes, the score is in the pass band and `card_geometry`\nwas actually measured; everything else is `inconclusive`. The numeric bands and the forensic\nthresholds of `copy_move`, `ela`, `moire_recapture`, `print_consistency` and\n`hologram_presence` are not published. The advisory portrait-substitution observation\n(`doc_auth_portrait_substitution`) is computed after the verdict and is not part of the\nscore: it appears in `checks[]` when it did not pass, adds no `_failed` code and never\nlowers a `pass`. `card_geometry_failed` on a real card almost always\nmeans the document was not found or was held at a strong angle: ask for a flat, well-lit\ncapture with the whole card in view. A document other than the supported card layout (for\nexample a driving licence) is measured against the card profile, so its layout and hologram\nresults are weaker evidence. `session.scorecard.document_front` is wider than\n`checks.documentAuthenticity`: it also folds in the document recognition and text reading\nrows of the front. The text reading row is always `inconclusive` today\n(`document_text_reading_inconclusive`), so `document_front` reads `review` at best, even\nwhen `checks.documentAuthenticity` reads `pass`. Treat `review` as \"a person should look\".\n\n**Liveness** (`checks.liveness`, step `liveness`, stage `active_liveness`). Advisory evidence\nplus a capture gate in the page: the hosted page does not accept a gesture until the person\nhas moved by the capture thresholds below. The server then re-checks the captured frames\nindependently, and the browser's own measurements can only make a result worse, never rescue\na failed server check. Five gestures are captured in the order `turn_up`, `turn_down`,\n`turn_left`, `turn_right`, `blink`. Checks in the scorecard: `active_liveness` (the overall\nresult), `turn_left`, `turn_right`, `turn_up`, `turn_down`, `blink` and `pad`.\n\n| Check | `pass` | `review` (`<name>_inconclusive`) | `fail` (`<name>_failed`) |\n|---|---|---|---|\n| `turn_left`, `turn_right`, `turn_up`, `turn_down` | The head moved clearly in the requested direction, measured against the person's own starting pose. | No starting pose or head-pose measurement could be formed, or the browser's measurements were unavailable. | The head did not move enough, moved the wrong way, or the peak frame the browser claimed is not where the server saw it. A frame without exactly one face fails the gesture. |\n| `blink` | A complete open-closed-open eyelid cycle lasting 50 to 1500 ms was observed. | The blink could not be measured, or its duration was outside 50 to 1500 ms. | No credible eyelid closure. |\n| `pad` | not applicable (advisory) | always (`pad_unvalidated_model`) | never while advisory |\n| `active_liveness` | All five gestures pass and `pad` passes. Not reachable while PAD is advisory. | Any gesture is `review`, or PAD is advisory. | Any gesture failed, or there was no usable face. |\n\nPublishable capture thresholds (browser, per gesture, relative to the start pose): turn left\nor right at least 6 degrees; look up or down at least 4.5 degrees; at most 42 degrees sideways\nor 38 degrees up and down is accepted; blink between 50 and 1500 ms. The server-side gate, its\nnoise margins and the PAD operating points are not published.\n\n| Liveness reason code | Meaning | Status it causes |\n|---|---|---|\n| `challenge_direction_inconsistent` | Head movement did not match the requested direction. It is present when the gesture failed, or when the gesture reads `review` and no more specific cause from this table applies; a `review` that names its cause (for example `client_metrics_unavailable`) does not carry it. | gesture `fail` (or `review`) |\n| `landmark_peak_mismatch` | The browser's reported peak frame does not match the server's measurement. For up and down it is judged only when the head-pose measurement was available; without it the gesture reads `review` with `pose_landmarks_unavailable`, not `fail`. | gesture `fail` (turn left and right, and up and down with a head-pose measurement) |\n| `no_face_detected`, `multiple_faces` | A frame had no face or more than one face. | gesture `fail` |\n| `face_track_lost` | The face was lost between frames. | `review` or `fail` |\n| `client_metrics_unavailable`, `client_landmark_low_confidence` | The browser did not provide its own measurements, or with low confidence. A blink is judged by the server on its own frames and does not carry them. | caps at `review` |\n| `neutral_reference_unavailable` | No usable starting pose (fewer than three single-face frames in the blink burst). | `review`, never `pass` |\n| `pose_landmarks_unavailable` | The head-pose measurement was unavailable for a frame; up and down cannot be judged. | pitch gesture `review` |\n| `blink_inconsistent` | The blink could not be confirmed. When a blink-like cycle existed it comes with one of the three codes below. | blink `review` or `fail` |\n| `blink_closed_plateau` | A real eye closing and reopening was found, but the closed frame sits next to another almost identical near-closed frame, so a frozen or painted eyelid cannot be told apart from a very long closure. | blink `review` |\n| `blink_scene_changed` | The blink was found, but the nose region, which serves as a control for lighting and head movement, changed between the open frame and the reopened frame. | blink `review` |\n| `blink_cycle_partial` | Only part of the evidence for a blink was present (the eyelid shape or the eye-region change, not both), or the cycle was incomplete. | blink `review` |\n| `blink_duration_out_of_range` | The blink cycle lasted outside 50 to 1500 ms. | blink `review` |\n| `pad_unvalidated_model` | The PAD result is advisory by design. | `pad` `review` |\n| `pad_unavailable`, `pad_incomplete`, `pad_face_not_detected` | PAD could not run on every frame. | `pad` `review` |\n| `pad_review`, `pad_rejected` | Reserved for when PAD becomes decision-affecting; not emitted today. | not applicable |\n| `passive_temporal_evidence_unavailable`, `passive_temporal_inconsistent`, `motion_implausible`, `frame_duplicate_suspected` | Passive-hold variant only: not enough or implausible motion across the three frames, or repeated frames. `passive_temporal_inconsistent` is present when the result is `fail`, or when a `review` has no more specific cause. | `review` (inconsistent: `fail`) |\n| `evidence_missing`, `evidence_unavailable`, `evidence_wrong_content_type`, `invalid_evidence_package`, `evidence_lookup_failed`, `transport_error`, `unexpected_status`, `malformed_response`, `malformed_refusal_body`, `adapter_not_configured`, `adapter_config_invalid`, `not_configured`, `weights_ungated` | The liveness check could not run at all (capture package missing or invalid, or the verification service unavailable). Appears as the stage reason of `stages.active_liveness`. | `review`, never `fail` |\n\n`turn_*_failed` on a genuine person usually means the head barely moved, a very small face\nor poor light: ask the person to turn clearly past the on-screen mark. `blink_inconclusive`\nis common and means \"not measured\", not \"no blink\"; together with `blink_closed_plateau`,\n`blink_scene_changed` or `blink_cycle_partial` it means a blink-like movement was seen but\nnot confirmed, so a person should look at it. A server result stored for the session counts\nas proof that the challenge was captured: the `liveness` step is not `pending` once that result\nexists, even when the browser never reported a completed gesture.\n\n**Selfie quality** (`checks.selfie`, step `selfie`, stage `selfie_quality`). The hard checks\nare flow-affecting (the hosted page asks for a retake); the soft checks are advisory. Every check\nafter `face_count` needs exactly one detected face. A failed check is reported as\n`<check>_failed`, with the check name from the first column, in\n`scorecard.selfie.reasonCodes` and `session.stages.selfie_quality.reasonCodes`; the overall\nresult of the quality analysis adds `face_quality_fail` or `face_quality_inconclusive`. The\nquality analysis also has internal issue names (for example `eyes_not_open`,\n`image_too_blurry`, `exposure_out_of_range`); they are not reason codes of the scorecard and\nare not returned in these lists.\n\n| Check | Passes when | Reason code when it fails | Kind |\n|---|---|---|---|\n| `face_count` | exactly one face | `face_count_failed` | hard (`fail`) |\n| `face_area_ratio` | the face box covers 0.03 to 0.90 of the image area | `face_area_ratio_failed` | hard (`fail`) |\n| `face_centered` | the face centre is within 0.18 of the width and 0.20 of the height from the image centre | `face_centered_failed` | hard (`fail`) |\n| `pose_yaw`, `pose_pitch` | the landmark pose ratio of each axis is within 0.25 of frontal (a unitless ratio of facial landmark positions, not an angle) | `pose_yaw_failed`, `pose_pitch_failed` | soft (`review`) |\n| `eyes_open` | eye-region texture of at least 8 (luma standard deviation) | `eyes_open_failed` | hard (`fail`) |\n| `not_occluded` | nose and mouth-corner texture of at least 5 | `not_occluded_failed` | hard (`fail`) |\n| `sharpness` | Laplacian variance of the face box of at least 40 | `sharpness_failed` | soft (`review`) |\n| `exposure` | mean face luma between 35 and 220 (of 255) and at most 0.15 of the pixels clipped | `exposure_failed` | soft (`review`) |\n\nThe selfie analysis is `fail` (`face_quality_fail`) when any hard check fails,\n`inconclusive` (`face_quality_inconclusive`, reads `review`) when only soft checks fail or\nwhen every check passed but their mean score is below 0.6, otherwise `pass`. In the last\ncase no `<check>_failed` code is present. The check scores (0..1) are in\n`scorecard.selfie.checks`. The thresholds are calibrated for typical webcam selfies around\n640x480; `sharpness` is a pixel-scale measure and reads lower on higher-resolution captures\nof the same sharp face (a soft check: it can only produce `review`). `exposure` covers both\ntoo dark and too bright faces. A head that is clearly turned away can also make the hosted\npage ask the person to retake the selfie, within the retake budget of the session.\n`checks.selfie` also folds in PAD, so while PAD is advisory it is at best `review` even when\nevery check above passes; it reads `fail` only when a hard selfie check fails.\n\n**Presentation-attack detection (PAD).** Advisory, never decision-affecting. No operating\npoint has been qualified on real replay and print cohorts, so every PAD result is reported\nas `review` with the reason `pad_unvalidated_model` and the scorecard code\n`presentation_attack_detection_inconclusive`. The live-score is recorded for reviewers but\nis not interpreted as live or spoof; its thresholds and model details are not published.\nPromoting PAD to a decision requires a new qualification and a documented contract change;\nthe policy page linked above states what changes for your integration and what does not.\nWhen the PAD analysis does not find a face in the selfie although the face comparison did,\nthe verification does not end in an error: document, machine-readable-zone and barcode\nreading continue, the `selfie` and `liveness` steps read `review` with the same two codes,\nand the session ends like every other hosted verification (`unverified` with\n`provider_manual_review`).\n\n**Face match** (`checks.faceMatch`). Reviewer evidence: a low score can force manual review,\nit never rejects.\n\n| Check | Meaning |\n|---|---|\n| `similarity_accept_band` | Primary comparison of the document portrait with the selfie: `pass` at a similarity of 0.42 or higher, `review` from 0.30 up to 0.42, `fail` below 0.30. |\n| `face_match_calibrated_score` | The fused, calibrated score (0..1), advisory. |\n| `cross_check_1`, `cross_check_2`, ... | Independent second-opinion comparisons (anonymous ordinal ids). Each one agrees only when its own calibrated score reaches its own accept bound; one that does not agree reads `cross_check_N_failed` and makes `checks.faceMatch` and the `selfie` step `review` even when `similarity_accept_band` passes. A second opinion never makes either of them `fail`: a `fail` comes only from the primary comparison. |\n| `face_match_borderline` | The comparison sat near a band edge. |\n\nA missing result reads `null` on the identity read and `not_evaluated` on the scorecard: no\nface was found in one of the images, or the analysis was unavailable. Different lighting,\nangle or age lowers the score: ask for a frontal, well-lit selfie.\n\n**Machine-readable zone and cross-check** (stage `mrz_crosscheck`, steps `document_back` and\n`cross_validation`). A driving licence has no machine-readable zone. For a session whose\ndocument type is `driving_license`, `session.stages.mrz_crosscheck` reads\n`{status: skipped, score: null, reasonCodes: [not_applicable]}` and the machine-readable-zone\nrows are left out of `document_back` and `cross_validation`, so the missing zone is not a\nfailure; a zone that was read from that document anyway is shown as usual. For every other\ndocument type, a zone that was not found, or that cannot be compared with the printed text,\nreads `review` (a measurement gap) and never `fail`. `fail` is kept for a decoded zone whose\ncheck digits are wrong or whose content contradicts the printed text. A barcode that was not\nfound or could not be decoded on the back is a measurement gap as well: `document_back`\nreads `review` with `barcode_reading_inconclusive`, never `fail`; `fail` is kept for a\nbarcode that was decoded and is invalid. On a zone that was read, the structure checks that\nonly report which fields the band carries (for example `mrz_personal_number_present`, a\nfield that a passport or a foreign identity card does not have) are informational: they stay\nin `checks[]` and never turn `document_back` into `fail` unless the zone itself failed. A\n`driving_license` whose `cross_validation` compared nothing reads `skipped` with\n`not_applicable` there instead of `review`. An auxiliary check of `document_front` that\ncompares a secondary document classifier with the primary document type reads `review` at\nmost and never turns the step into `fail`; the two label sets are compared by document type\nand side, so a document named as the same type and side by both agrees.\n\n**How steps roll up** (`scorecard.overall`). `fail` dominates `review`, which dominates\n`pass`. `skipped` steps are ignored; a `pending` step turns an otherwise `pass` roll-up into\n`pending` but never hides a `fail` or a `review`.\n\nThe document recognition and reading codes that can appear next to these (`document_type_uncertain`,\n`document_type_not_identified`, `document_front_no_portrait`, `document_back_has_portrait`,\n`mrz_not_detected`, `mrz_invalid`, `mrz_checksum_invalid`, `front_field_missing`,\n`front_back_field_mismatch`) concern reading the document. Two informational codes concern the\nselection the person made: `document_type_mismatch_selection` (the classifier contradicts the\nselected document type) and `issuing_country_mismatch_selection` (the document shows another\ncountry than the selected one); see the extraction block of\n`GET /v1/kyc/verifications/{verificationId}/identity`.\n\n**Rate limit.** Shares the per-key limit (30 requests per 60 seconds) with the mint call,\nafter the per-source-IP limit that applies to every request.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "description": "The `sessionId` returned by the mint call.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9-]{1,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The session's current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycTenantSessionEnvelope"
                },
                "example": {
                  "ok": true,
                  "session": {
                    "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                    "domainId": null,
                    "status": "review_pending",
                    "idType": "passport",
                    "subjectId": "cust_4821",
                    "captureStep": "submit",
                    "createdAt": 1790762000000,
                    "openedAt": 1790762100000,
                    "expiresAt": 1790763900000,
                    "completedAt": 1790762445000,
                    "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "reasonCodes": [
                      "provider_manual_review"
                    ],
                    "purpose": "Open your brokerage account",
                    "returnUrl": "https://merchant.example.com/kyc/return",
                    "selectedIdType": "passport",
                    "selectedIssuingCountry": "GB",
                    "consentVersion": "2026-09-01",
                    "consentAcceptedAt": 1790762110000,
                    "speechProfilingConsentAcceptedAt": null,
                    "ageGenderConsentAcceptedAt": null,
                    "evidence": {
                      "front": "obj_9c1e4a",
                      "selfie": "obj_5b7d02"
                    },
                    "decision": {
                      "outcome": "review_pending",
                      "reasonCodes": [
                        "provider_manual_review"
                      ],
                      "decidedAt": 1790762445000
                    },
                    "verification": {
                      "status": "unverified",
                      "source": "pipeline"
                    },
                    "review": null,
                    "stages": {
                      "scene_inventory": {
                        "status": "pass",
                        "score": 0.97,
                        "reasonCodes": []
                      },
                      "document_type_id": {
                        "status": "pass",
                        "score": 0.94,
                        "reasonCodes": []
                      },
                      "ocr_back": {
                        "status": "skipped",
                        "score": null,
                        "reasonCodes": [
                          "not_required"
                        ]
                      },
                      "mrz_crosscheck": {
                        "status": "pass",
                        "score": 0.99,
                        "reasonCodes": []
                      },
                      "selfie_quality": {
                        "status": "pass",
                        "score": 0.91,
                        "reasonCodes": []
                      },
                      "document_near_face": {
                        "status": "skipped",
                        "score": null,
                        "reasonCodes": [
                          "not_required"
                        ]
                      },
                      "active_liveness": {
                        "status": "pass",
                        "score": 0.93,
                        "reasonCodes": []
                      },
                      "speech_profile": {
                        "status": "skipped",
                        "score": null,
                        "reasonCodes": [
                          "speech_profiling_not_requested"
                        ]
                      },
                      "speech_analysis": {
                        "status": "skipped",
                        "score": null,
                        "reasonCodes": [
                          "speech_analysis_not_requested"
                        ]
                      },
                      "age_gender": {
                        "status": "skipped",
                        "score": null,
                        "reasonCodes": [
                          "age_gender_not_requested"
                        ]
                      }
                    },
                    "scorecard": {
                      "document_front": {
                        "status": "pass",
                        "score": 0.94,
                        "checks": [],
                        "reasonCodes": []
                      },
                      "document_back": {
                        "status": "skipped",
                        "score": 0,
                        "checks": [],
                        "reasonCodes": [
                          "not_required"
                        ]
                      },
                      "selfie": {
                        "status": "review",
                        "score": 0.91,
                        "checks": [],
                        "reasonCodes": [
                          "presentation_attack_detection_inconclusive"
                        ]
                      },
                      "liveness": {
                        "status": "review",
                        "score": 0.93,
                        "checks": [],
                        "reasonCodes": [
                          "presentation_attack_detection_inconclusive"
                        ]
                      },
                      "voice": {
                        "status": "skipped",
                        "score": 0,
                        "checks": [],
                        "reasonCodes": [
                          "not_required"
                        ]
                      },
                      "cross_validation": {
                        "status": "pass",
                        "score": 0.99,
                        "checks": [],
                        "reasonCodes": []
                      },
                      "overall": {
                        "status": "review",
                        "score": 0.94,
                        "checks": [],
                        "reasonCodes": [
                          "presentation_attack_detection_inconclusive"
                        ]
                      }
                    },
                    "nvid": "NVID-7K3M-Q9XA-T2RW-4HZD"
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid sessionId` — the path segment is not valid percent-encoding.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "example": {
                  "error": "Invalid sessionId",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "description": "Retryable (`Retry-After: 2`). `tenant_module_gate_unavailable`, or `invalid_request` with a `reason` (`pg_unavailable` or `backend_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "examples": {
                  "moduleGateUnavailable": {
                    "value": {
                      "error": "tenant_module_gate_unavailable",
                      "message": "The tenant module gate could not be confirmed; retry shortly",
                      "requestId": "req-test"
                    }
                  },
                  "backendFault": {
                    "value": {
                      "error": "invalid_request",
                      "reason": "pg_unavailable",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "available",
        "x-noxtica-webhook-data": {
          "kyc.session.completed": {
            "$ref": "#/components/schemas/KycSessionCompletedWebhookData"
          },
          "kyc.session.cancelled": {
            "$ref": "#/components/schemas/KycSessionCancelledWebhookData"
          }
        }
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/config": {
      "get": {
        "operationId": "kycHostedConfigRead",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Read the hosted verification configuration, for your organisation or for one domain",
        "description": "Server-to-server, read-only. Returns the hosted verification configuration your invitations\nuse: with no `domainId`, your organisation default; with a `domainId`, the configuration\nthat applies to that domain.\n\n**Per-domain configuration.** One organisation can own several domains, and each domain can\noverride the hosted configuration (business name, purpose, privacy page, support page,\nsupported documents, approved return hosts, embed origins, email verification, selfie,\nvoice, device upload and liveness mode). Inheritance is **field by field**:\n\n- For every field the effective value is the domain's own value when it sets one, otherwise\n  your organisation default, otherwise the platform default. A field the domain leaves unset\n  inherits; nothing is merged inside a field.\n- The lists (`documents`, `allowedReturnUrlHosts`, `allowedEmbedOrigins`) are replaced as a\n  whole, never combined. An empty list is a real override (\"no return hosts\", \"cannot be\n  embedded\") and is not the same as an unset list.\n- `supportUrl`: unset inherits. A domain can explicitly have no support link, and the\n  effective value is then `null`.\n- The platform default applies only to a field neither the domain nor your organisation\n  sets: `requireSelfie` `true`, `requireVoice` `false`, `allowDeviceUpload` `false`,\n  `livenessMode` `passive`, `allowedReturnUrlHosts` `[]`, `allowedEmbedOrigins` `[]`,\n  `emailVerification` `off`, `supportUrl` `null`.\n- `businessName`, `purpose`, `privacyUrl`, `privacyVersion` and `documents` have no platform\n  default. When no layer sets one of them, `effective` is `null` and the hosted page shows\n  \"Verification isn't ready yet\". A domain can therefore stand alone without an\n  organisation default (for example when you operate several companies), or override only\n  a few fields of the default.\n- A domain that sets its own `privacyUrl` should also set its own `privacyVersion`: the\n  customer's consent receipt records the version.\n\n**What a session uses.** An invitation is created for one domain (see `POST\n/v1/kyc/sessions`; the mint response and the status read carry its `domainId`) and always\nshows that domain's configuration as it is at that moment: if you remove a domain's\noverride later, its sessions fall back to your organisation default. The approved return\nhosts and the email-verification mode are checked once, at mint, against the chosen domain\nand are then fixed for the session. Until a domain has an override, every invitation behaves\nas it did with your organisation default alone.\n\n**Countries.** `documents[].issuingCountries` accepts any ISO 3166-1 alpha-2 country\n(`CountryCodeList`: 1 to 249 distinct codes per document type), so a document type can be\noffered for every country. A single invitation can offer fewer countries without changing\nthis configuration: see `hostedOptions.allowedIssuingCountries` on `POST /v1/kyc/sessions`.\nThis read returns the configuration itself, not the narrowing of any invitation.\n\n**Required identity fields.** An entry of `documents` can carry `requiredIdentityFields`: which\nof the five identity fields (`surname`, `givenNames`, `dateOfBirth`, `personalNumber`,\n`documentNumber`) must be read from that document type for the extraction to count as complete\n(see `extraction` on `GET /v1/kyc/verifications/{verificationId}/identity`). While the member\nis absent the default applies: `surname`, `givenNames`, `dateOfBirth` and `documentNumber`,\nplus `personalNumber` on a Turkish national ID card or driving licence, and on a Turkish\ndocument whose T.C. Kimlik No was read and passes its check digits `documentNumber` is not\nrequired. A non-empty list is exactly the set that is required and replaces that default,\nincluding the Turkish rule; an empty list requires nothing. A domain override replaces the\nwhole `documents` list, like every other member of it. The member is edited in the Backoffice\nhosted verification settings (organisation default or one domain), not through this API, and\nthis read returns it where it is set. It only decides which fields count towards\n`extraction.status` and the informational reason code `identity_fields_unreadable`: it never\nchanges whether a session goes to review.\n\n**Response.** With `domainId`: `domainId` and `domain` identify the domain, `effective` is\nthe configuration its invitations use, `sources` names the layer each effective field comes\nfrom, and `tenantDefault` is your organisation default for comparison (`null` when you have\nnone). Without `domainId`: `domainId` and `domain` are `null`, `effective` equals\n`tenantDefault`, and `domains` lists the active domains that carry an override, with the\nfields each overrides (a domain without an override is not listed: it uses your\norganisation default). An unknown\ndomain, a disabled domain and a domain of another organisation all answer the same\n`404 domain_not_found`.\n\n**Read only.** The configuration is edited in the Backoffice hosted verification settings\n(a domain selector chooses the domain), not through the API: a change there needs a\nsigned-in administrator's fresh multi-factor confirmation, which an API key cannot give.\n\n**Before you can call.** The identity verification module must be enabled for your\norganisation (otherwise the same byte-identical `404` as an unknown path) and your `sk_` key\nneeds the `write:identity` scope. See \"Rate limits\" above.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "name": "domainId",
            "in": "query",
            "required": false,
            "description": "Return the configuration that applies to this domain (the `d_...` id of a domain of your organisation, shown on the Backoffice Domains page; `domains` lists the ones that carry an override when this parameter is omitted). Omit it to read your organisation default and that list. An empty value is refused with `400 invalid_request`.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycHostedConfigResponse"
                },
                "examples": {
                  "tenantDefault": {
                    "summary": "Without domainId — organisation default and the per-domain summary",
                    "value": {
                      "ok": true,
                      "domainId": null,
                      "domain": null,
                      "effective": {
                        "businessName": "Acme Brokerage Ltd",
                        "purpose": "Open your brokerage account",
                        "privacyUrl": "https://acme.example.com/privacy",
                        "supportUrl": "https://acme.example.com/support",
                        "privacyVersion": "2026-09-01",
                        "documents": [
                          {
                            "idType": "passport",
                            "issuingCountries": [
                              "GB",
                              "DE"
                            ],
                            "requireBack": false
                          },
                          {
                            "idType": "national_id",
                            "issuingCountries": [
                              "GB"
                            ],
                            "requireBack": true
                          }
                        ],
                        "requireSelfie": true,
                        "requireVoice": false,
                        "allowDeviceUpload": false,
                        "livenessMode": "passive",
                        "emailVerification": "off",
                        "allowedReturnUrlHosts": [
                          "acme.example.com"
                        ],
                        "allowedEmbedOrigins": [],
                        "updatedAt": 1790760000000
                      },
                      "sources": {
                        "businessName": "tenant",
                        "defaultPurpose": "tenant",
                        "privacyUrl": "tenant",
                        "supportUrl": "tenant",
                        "privacyVersion": "tenant",
                        "documents": "tenant",
                        "requireSelfie": "tenant",
                        "requireVoice": "tenant",
                        "allowDeviceUpload": "tenant",
                        "livenessMode": "tenant",
                        "allowedReturnUrlHosts": "tenant",
                        "allowedEmbedOrigins": "tenant",
                        "emailVerification": "tenant"
                      },
                      "tenantDefault": {
                        "businessName": "Acme Brokerage Ltd",
                        "purpose": "Open your brokerage account",
                        "privacyUrl": "https://acme.example.com/privacy",
                        "supportUrl": "https://acme.example.com/support",
                        "privacyVersion": "2026-09-01",
                        "documents": [
                          {
                            "idType": "passport",
                            "issuingCountries": [
                              "GB",
                              "DE"
                            ],
                            "requireBack": false
                          },
                          {
                            "idType": "national_id",
                            "issuingCountries": [
                              "GB"
                            ],
                            "requireBack": true
                          }
                        ],
                        "requireSelfie": true,
                        "requireVoice": false,
                        "allowDeviceUpload": false,
                        "livenessMode": "passive",
                        "emailVerification": "off",
                        "allowedReturnUrlHosts": [
                          "acme.example.com"
                        ],
                        "allowedEmbedOrigins": [],
                        "updatedAt": 1790760000000
                      },
                      "domains": [
                        {
                          "domainId": "d_7Kp3Zx9QmW2aLr5T",
                          "origin": "https://markets.example.com",
                          "overriddenFields": [
                            "businessName",
                            "privacyUrl",
                            "supportUrl",
                            "privacyVersion"
                          ]
                        },
                        {
                          "domainId": "d_Hf4Nc8Vb1Yt6Jw2R",
                          "origin": "https://shop.example.com",
                          "overriddenFields": [
                            "allowedReturnUrlHosts"
                          ]
                        }
                      ],
                      "requestId": "req-test"
                    }
                  },
                  "domain": {
                    "summary": "With domainId — a domain that overrides its business name and pages",
                    "value": {
                      "ok": true,
                      "domainId": "d_7Kp3Zx9QmW2aLr5T",
                      "domain": {
                        "id": "d_7Kp3Zx9QmW2aLr5T",
                        "origin": "https://markets.example.com"
                      },
                      "effective": {
                        "businessName": "Acme Markets Inc.",
                        "purpose": "Open your brokerage account",
                        "privacyUrl": "https://markets.example.com/privacy",
                        "supportUrl": "https://markets.example.com/help",
                        "privacyVersion": "2026-10-15",
                        "documents": [
                          {
                            "idType": "passport",
                            "issuingCountries": [
                              "GB",
                              "DE"
                            ],
                            "requireBack": false
                          },
                          {
                            "idType": "national_id",
                            "issuingCountries": [
                              "GB"
                            ],
                            "requireBack": true
                          }
                        ],
                        "requireSelfie": true,
                        "requireVoice": false,
                        "allowDeviceUpload": false,
                        "livenessMode": "passive",
                        "emailVerification": "off",
                        "allowedReturnUrlHosts": [
                          "acme.example.com"
                        ],
                        "allowedEmbedOrigins": [],
                        "updatedAt": 1790761000000
                      },
                      "sources": {
                        "businessName": "domain",
                        "defaultPurpose": "tenant",
                        "privacyUrl": "domain",
                        "supportUrl": "domain",
                        "privacyVersion": "domain",
                        "documents": "tenant",
                        "requireSelfie": "tenant",
                        "requireVoice": "tenant",
                        "allowDeviceUpload": "tenant",
                        "livenessMode": "tenant",
                        "allowedReturnUrlHosts": "tenant",
                        "allowedEmbedOrigins": "tenant",
                        "emailVerification": "tenant"
                      },
                      "tenantDefault": {
                        "businessName": "Acme Brokerage Ltd",
                        "purpose": "Open your brokerage account",
                        "privacyUrl": "https://acme.example.com/privacy",
                        "supportUrl": "https://acme.example.com/support",
                        "privacyVersion": "2026-09-01",
                        "documents": [
                          {
                            "idType": "passport",
                            "issuingCountries": [
                              "GB",
                              "DE"
                            ],
                            "requireBack": false
                          },
                          {
                            "idType": "national_id",
                            "issuingCountries": [
                              "GB"
                            ],
                            "requireBack": true
                          }
                        ],
                        "requireSelfie": true,
                        "requireVoice": false,
                        "allowDeviceUpload": false,
                        "livenessMode": "passive",
                        "emailVerification": "off",
                        "allowedReturnUrlHosts": [
                          "acme.example.com"
                        ],
                        "allowedEmbedOrigins": [],
                        "updatedAt": 1790760000000
                      },
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — `domainId` is present but empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedOrDomainNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "description": "Retryable. `tenant_module_gate_unavailable` (the module gate could not be answered), or `invalid_request` with a `reason` (`pg_unavailable` or `backend_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "examples": {
                  "moduleGateUnavailable": {
                    "value": {
                      "error": "tenant_module_gate_unavailable",
                      "message": "The tenant module gate could not be confirmed; retry shortly",
                      "requestId": "req-test"
                    }
                  },
                  "backendFault": {
                    "value": {
                      "error": "invalid_request",
                      "reason": "pg_unavailable",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-view": {
      "get": {
        "operationId": "kycSessionView",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Read the current hosted session (browser-facing)",
        "description": "Called by the hosted verification page itself, never by a tenant backend.\n**Noxtica Verification ID.** Only when the Noxtica Verification ID (NVID) is enabled for the platform, the `session` object carries `verificationIdentifier` (`KycVerificationIdentifier`) — the NVID with its derived status and an `emailOnFile` flag — or `null` when the session has no NVID. It lets the page show the ID again when a finished link is reloaded. The key is absent while the feature is not enabled; the email address is never returned.",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The session snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycHostedSessionEnvelope"
                },
                "example": {
                  "ok": true,
                  "session": {
                    "status": "opened",
                    "idType": "passport",
                    "captureStep": "consent",
                    "expiresAt": 1790763900000,
                    "openedAt": 1790762100000,
                    "steps": [
                      "consent",
                      "id_front",
                      "id_back",
                      "selfie",
                      "document_near_face",
                      "liveness",
                      "voice",
                      "review",
                      "submit"
                    ],
                    "configuration": {
                      "businessName": "Example Bank",
                      "purpose": "Open your brokerage account",
                      "privacyUrl": "https://merchant.example.com/privacy",
                      "supportUrl": "https://merchant.example.com/support",
                      "returnUrl": "https://merchant.example.com/kyc/return",
                      "privacyVersion": "2026-09-01",
                      "documents": [
                        {
                          "idType": "passport",
                          "issuingCountries": [
                            "GB"
                          ],
                          "requireBack": false
                        }
                      ],
                      "requireSelfie": true,
                      "requireVoice": false,
                      "livenessMode": "passive",
                      "options": {
                        "allowedDocumentTypes": [
                          "passport"
                        ],
                        "allowDeviceUpload": false,
                        "steps": {
                          "documentBack": "auto",
                          "documentNearFace": false,
                          "activeLiveness": true,
                          "speechAnalysis": false
                        }
                      }
                    },
                    "selection": null,
                    "consent": null,
                    "evidence": {},
                    "resume": {
                      "eligible": true
                    },
                    "failureBudget": {
                      "front": {
                        "attempts": 0,
                        "max": 10
                      },
                      "back": {
                        "attempts": 0,
                        "max": 10
                      },
                      "selfie": {
                        "attempts": 0,
                        "max": 10
                      },
                      "liveness": {
                        "attempts": 0,
                        "max": 3
                      },
                      "voice": {
                        "attempts": 0,
                        "max": 10
                      },
                      "upload": {
                        "attempts": 0,
                        "max": 3
                      }
                    },
                    "serverNow": 1790762150000,
                    "sessionExpiresAt": 1790763900000
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "Missing/disallowed Origin, or an invalid/expired session token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "410": {
            "$ref": "#/components/responses/SessionExpiredOrCancelled"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-progress": {
      "post": {
        "operationId": "kycSessionProgress",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Checkpoint hosted-flow progress (browser-facing)",
        "description": "Records consent, document/issuing-country selection, and step completion as the customer moves through the hosted flow.",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Progress-checkpoint payload (consent, selection, or step-completion — shape depends on the current step)."
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkpoint recorded; returns the updated snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycHostedSessionEnvelope"
                },
                "example": {
                  "ok": true,
                  "session": {
                    "status": "opened",
                    "idType": "passport",
                    "captureStep": "id_front",
                    "expiresAt": 1790763900000,
                    "openedAt": 1790762100000,
                    "steps": [
                      "consent",
                      "id_front",
                      "id_back",
                      "selfie",
                      "document_near_face",
                      "liveness",
                      "voice",
                      "review",
                      "submit"
                    ],
                    "configuration": {
                      "businessName": "Example Bank",
                      "purpose": "Open your brokerage account",
                      "privacyUrl": "https://merchant.example.com/privacy",
                      "supportUrl": "https://merchant.example.com/support",
                      "returnUrl": "https://merchant.example.com/kyc/return",
                      "privacyVersion": "2026-09-01",
                      "documents": [
                        {
                          "idType": "passport",
                          "issuingCountries": [
                            "GB"
                          ],
                          "requireBack": false
                        }
                      ],
                      "requireSelfie": true,
                      "requireVoice": false,
                      "livenessMode": "passive",
                      "options": {
                        "allowedDocumentTypes": [
                          "passport"
                        ],
                        "allowDeviceUpload": false,
                        "steps": {
                          "documentBack": "auto",
                          "documentNearFace": false,
                          "activeLiveness": true,
                          "speechAnalysis": false
                        }
                      }
                    },
                    "selection": {
                      "idType": "passport",
                      "issuingCountry": "GB"
                    },
                    "consent": {
                      "version": "2026-09-01",
                      "acceptedAt": 1790762110000
                    },
                    "evidence": {},
                    "resume": {
                      "eligible": true
                    },
                    "failureBudget": {
                      "front": {
                        "attempts": 0,
                        "max": 10
                      },
                      "back": {
                        "attempts": 0,
                        "max": 10
                      },
                      "selfie": {
                        "attempts": 0,
                        "max": 10
                      },
                      "liveness": {
                        "attempts": 0,
                        "max": 3
                      },
                      "voice": {
                        "attempts": 0,
                        "max": 10
                      },
                      "upload": {
                        "attempts": 0,
                        "max": 3
                      }
                    },
                    "serverNow": 1790762160000,
                    "sessionExpiresAt": 1790763900000
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "403": {
            "description": "Missing/disallowed Origin, or an invalid/expired session token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "409": {
            "description": "The session is already terminal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/SessionExpiredOrCancelled"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-cancel": {
      "post": {
        "operationId": "kycSessionCancel",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Cancel the hosted session (browser-facing)",
        "description": "Called by the hosted verification page when the end customer confirms they do not want to continue. Moves the session to the terminal status `cancelled_by_user`, recorded server-side (`cancelledAt`, optional closed-enum `cancelReason`); the cancel itself hard-deletes no evidence (evidence retention rules are unchanged).\n**Capability.** A collector that serves and has armed this route advertises it on every session snapshot (`GET /v1/kyc/session-view`, `POST /v1/kyc/session-progress`) as `session.capabilities.cancel: true`. When the field is absent (an older collector, or cancel disarmed) the hosted page never calls this route and shows a client-only end screen instead. An older or unarmed collector answers this route with the same byte-identical V1 `404` as any other gated route.\n**When it is allowed.** Cancel is accepted from any capture, consent, selection, or review step (session `pending` or `opened`). It is refused with `409 session_not_cancellable` once the session is `verifying` (the single-use submit gate is consumed and the verification is being paid for) or already `completed`, `unverified`, `error`, or `review_pending`. A session past its deadline answers `410 session_expired`.\n**Idempotency.** Repeating the call on an already-cancelled session returns `200` with the ORIGINAL `cancelledAt` and `cancelReason`.\n**After a cancel** every other customer route (`session-view`, `session-progress`, `session-submit`, `session-evidence/handle`, `session-evidence/finalize`) answers `410 session_cancelled` (`code: kyc_session_cancelled`).\n**Webhook.** The call that actually transitions the session emits ONE `kyc.session.cancelled` webhook to every enabled endpoint subscribed to that event type (subscription is opt-in per endpoint; nothing is sent to an endpoint that did not list it). The envelope `data` is `{schemaVersion: \"1\", sessionId, status: \"cancelled_by_user\", cancelReason (closed enum or `null`), cancelledAt (Unix ms), occurredAt (= cancelledAt), deliveryId}` — plus `email` and `emailVerified` when hosted email verification is enabled for the platform AND the session has a stored address (`KycSessionCancelledWebhookData`) — and carries no other subject data. An idempotent replay, a `409` and an expiry emit nothing. Delivery is at-least-once through the same signed outbox as `kyc.session.completed`; receivers MUST dedup by `deliveryId`. A webhook is a notification, not authority: `GET /v1/kyc/sessions/{sessionId}` confirms the status `cancelled_by_user` but does NOT return `cancelledAt` or `cancelReason`; those are delivered only in this webhook.\n**Billing.** A cancelled session is never billed as a verification: a cancel creates no verification request and no spend reservation, and never records the `kyc.verification` meter (that meter is recorded only at submit-time spend admission). Only the request-level `endpoint.collect.request` count applies, as for every hosted session call.\nRate limits: a per-IP limit, then a per-session limit — the same as `session-progress`.",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionCancelRequest"
              },
              "example": {
                "reason": "not_now"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The session is `cancelled_by_user` — either this call cancelled it or it was already cancelled (idempotent replay; the original `cancelledAt` and `cancelReason` are returned).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionCancelResponse"
                },
                "example": {
                  "ok": true,
                  "status": "cancelled_by_user",
                  "cancelledAt": 1768434600000,
                  "cancelReason": "not_now",
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — invalid body, or `reason` outside the closed enum.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_session` — the session token is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_session",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "`origin_not_allowed` — missing or disallowed `Origin` (same origin enforcement as every other hosted route).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "origin_not_allowed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "409": {
            "description": "`session_not_cancellable` — the session is already `verifying`, `completed`, `unverified`, `error`, or `review_pending`; `status` names which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionNotCancellableError"
                },
                "example": {
                  "error": "session_not_cancellable",
                  "status": "verifying",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/SessionExpired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`invalid_request` — an unexpected internal fault while handling the cancel (any uncaught error in the handler). The body is the same `{error: invalid_request, requestId}` envelope as a `400`, so clients MUST key off the status code, not the error string, to tell the two apart. The session is not reported as cancelled; it is safe to retry the call (cancel is idempotent).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-submit": {
      "post": {
        "operationId": "kycSessionSubmit",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Submit the hosted session for verification (browser-facing, one-shot)",
        "description": "Drives the SAME `handleVerifyAction` pipeline `POST /v1/kyc/verify` uses, under the session's own tenant, with a server-derived payload (evidence object ids resolved exclusively from this session's own finalized evidence — a browser can never substitute its own payload fields). Single-use: a lost single-use claim on a concurrent resubmit returns `verify_gate_consumed`, distinct from a stale replay (`session_not_submittable`). See the closed `SUBMIT REFUSAL CODES` table below — hardened 2026-09-13 (D8b) so every pre-verification failure returns a distinct, actionable code instead of a shared `invalid_request`.\n**Email verification.** When the session's effective email-verification mode is `required` and the customer has not verified an address, the submit is refused with `409 email_verification_required`; the session stays open and can be submitted once the address is verified. Only possible while hosted email verification is enabled for the platform.\n**Noxtica Verification ID.** When the Noxtica Verification ID (NVID) is enabled for the platform, a `200` response also carries `nvid` — the session's NVID when the outcome is positive (`completed` or `review_pending`), otherwise `null`. The key is absent while the feature is not enabled.",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional supplemental payload; the four pipeline-critical fields (operation, jurisdiction, evidenceObjectIds, subject, policy) are always server-derived and cannot be overridden by the caller."
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "A resolved verify attempt (verified, unverified, or review_pending).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycVerifyResponse"
                },
                "example": {
                  "ok": true,
                  "verdict": "verified",
                  "verificationLevel": "verified",
                  "reasonCodes": [
                    "provider_timeout"
                  ],
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "requestId": "req-test",
                  "legalBasisRef": "example-legalBasisRef",
                  "nfcAssurance": {},
                  "deviceTrust": {}
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_invalid` or `payload_invalid` — both new distinct codes as of 2026-09-13.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "Missing/disallowed Origin, an invalid/expired session token, or `evidence_refused` with `refusal` `evidence_not_provisioned` or `residency_unconfigured` (the organisation's raw-evidence storage is no longer provisioned; nothing was consumed and the session stays retryable).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionEvidenceError"
                },
                "examples": {
                  "originOrSession": {
                    "value": {
                      "error": "invalid_request",
                      "requestId": "req-test"
                    }
                  },
                  "evidenceNotProvisioned": {
                    "value": {
                      "error": "evidence_refused",
                      "refusal": "evidence_not_provisioned",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "409": {
            "description": "`session_not_submittable` (already terminal/mid-flight), `verify_gate_consumed` (NEW 2026-09-13 — lost the single-use race), or `email_verification_required` (NEW 2026-10-01 — the session's effective email-verification mode is `required` and no address has been verified yet).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/SessionExpiredOrCancelled"
          },
          "429": {
            "description": "`session_rate_limited` (the per-session budget, NEW 2026-09-13, distinct from the dispatcher-level 429 below) or the per-IP hosted-page limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "description": "`nonce_mint_unavailable` (NEW), `submit_internal_error` (NEW), `hosted_verify_not_configured`, or `kyc_legal_basis_receipt_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-verification-id/email": {
      "post": {
        "operationId": "kycSessionVerificationIdEmail",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Email the person their Noxtica Verification ID (browser-facing, optional, consented)",
        "description": "Called by the hosted verification page when the person, on the final screen, asks Noxtica to email them their Noxtica Verification ID (NVID) and its public status link. Optional and independent of the verification outcome. **The person's explicit consent is required** (`consent.version` `nvid-email-v1`, `consent.accepted` `true`; the hosted checkbox is unticked by default). Noxtica — not the tenant — is the controller for this address: it is sealed (AES-256-GCM) at rest, used only to send this email, kept only with the verification record, never returned by any API and never disclosed to the tenant.\nOnly available while the NVID feature is enabled for the platform; otherwise the byte-identical gated `404`. Requires an issued, non-revoked NVID on the session (`409 verification_id_unavailable` otherwise). Limits: per-session, per-IP and per-address limiters, at most 3 sends per session from the hosted page, at least 60 seconds between sends (`429 rate_limited`).",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycVerificationIdEmailRequest"
              },
              "example": {
                "email": "jane.doe@example.com",
                "locale": "en",
                "consent": {
                  "version": "nvid-email-v1",
                  "accepted": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The email was accepted for delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "400": {
            "description": "`invalid_email` or `consent_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "consent_required",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "Missing/disallowed Origin, or an invalid/expired session token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "origin_not_allowed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "409": {
            "description": "`verification_id_unavailable` — the session has no NVID, or it was revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "verification_id_unavailable",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/SessionExpiredOrCancelled"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "`email_send_failed` — the provider refused or failed; the send slot is released.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "email_send_failed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "description": "`email_unavailable` — the address cannot be sealed right now; nothing was stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "email_unavailable",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-email/send": {
      "post": {
        "operationId": "kycSessionEmailSend",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Send the email-verification link for the address the customer typed (browser-facing)",
        "description": "Called by the hosted verification page, never by a tenant backend. Available only while hosted email verification is enabled for the platform AND the session's effective `emailVerification` mode (fixed at mint) is `optional` or `required`; otherwise the byte-identical gated `404` (platform off) or `409 email_verification_not_enabled` (mode `off`). Sends one confirmation link to the address; the link is valid for 30 minutes or until the session ends, whichever is sooner. Opening the link changes nothing: the customer must press the button on the page it opens, because mail gateways prefetch links.\nLimits per session: at most 5 accepted sends, at least 60 seconds between sends (`429 cooldown` with `retryAfterSec`, `429 send_limit_reached`), no send with less than 60 seconds of session left (`409 session_expiring`). Per client IP 20 sends an hour and per address 5 sends an hour (`429 Rate limit exceeded`). A provider failure rolls the send back (`502 email_send_failed`) so an outage does not spend the customer's quota. The response carries the masked address only.",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 254,
                    "description": "The address to verify (normalised server-side)."
                  },
                  "locale": {
                    "type": "string",
                    "description": "One of the supported UI locales; the email is rendered in it (an unsupported value falls back to `en`)."
                  }
                }
              },
              "example": {
                "email": "jane.doe@example.com",
                "locale": "en"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycEmailVerificationStateEnvelope"
                },
                "example": {
                  "emailVerification": {
                    "mode": "required",
                    "email": "j***@example.com",
                    "verified": false,
                    "verifiedAt": null,
                    "expiresAt": 1790764000000,
                    "resendAvailableAt": 1790762260000,
                    "sendsRemaining": 4
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_email`, or `invalid_request` (unreadable body).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_email",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_session` — no session bearer, or one that does not resolve to a session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_session",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "Missing or disallowed `Origin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "origin_not_allowed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "409": {
            "description": "`email_verification_not_enabled` (effective mode `off`), `already_verified` (this address is already confirmed; the body carries the current `emailVerification` state), `session_expiring` (under 60 seconds left), or `session_not_editable` (the session is past capture; the body carries its `status`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "email_verification_not_enabled",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/SessionExpiredOrCancelled"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "429": {
            "description": "`cooldown` (with `retryAfterSec`), `send_limit_reached`, or `Rate limit exceeded` (per-session, per-IP or per-address bucket; `retryAfter`). `Retry-After` is sent for the cooldown and rate-limit cases.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "cooldown",
                  "retryAfterSec": 42,
                  "requestId": "req-test"
                }
              }
            }
          },
          "502": {
            "description": "`email_send_failed` — the mail provider refused or failed; the send was rolled back.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "email_send_failed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "description": "`hosted_verify_not_configured`, `session_backend_unavailable`, or `invalid_request` (a transient lock or database fault); retry after the `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-email/status": {
      "get": {
        "operationId": "kycSessionEmailStatus",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Poll the email-verification state (browser-facing)",
        "description": "Called by the hosted verification page while the customer is in their mail app; it builds no snapshot, just the `emailVerification` state (masked address). Same availability as `POST /v1/kyc/session-email/send`: the byte-identical gated `404` while the platform has the feature off, `409 email_verification_not_enabled` for a session whose mode is `off`.",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycEmailVerificationStateEnvelope"
                },
                "example": {
                  "emailVerification": {
                    "mode": "optional",
                    "email": null,
                    "verified": false,
                    "verifiedAt": null,
                    "expiresAt": null,
                    "resendAvailableAt": null,
                    "sendsRemaining": 5
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_session`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_session",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "Missing or disallowed `Origin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "origin_not_allowed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "409": {
            "description": "`email_verification_not_enabled` — the session's effective mode is `off`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "email_verification_not_enabled",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/SessionExpiredOrCancelled"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`session_backend_unavailable` or `invalid_request` — transient; retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/email-verification/confirm": {
      "post": {
        "operationId": "kycEmailVerificationConfirm",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Confirm the emailed link (browser-facing, link token is the credential)",
        "description": "Called by the page the emailed link opens, when the customer presses its one button. The link token (`kyev.<tenant>.<secret>`, 256-bit) is the only credential: no session bearer is read or accepted, and no session token is returned. Single use. A malformed, unknown, already used, expired, wrong-tenant or session-ended token all answer the identical `400 invalid_or_expired` (no oracle). The `Origin` must be on the hosted-verification allow-list. Per client IP 30 confirmations per 10 minutes. Available only while hosted email verification is enabled for the platform; otherwise the byte-identical gated `404`.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "maxLength": 512,
                    "description": "The link token, `kyev.<tenant>.<secret>`."
                  }
                }
              },
              "example": {
                "token": "kyev.tenant_42.q3JkT0vN8mYb1xW6cE4aZ7uP2sH9dLfR5gKiA0oXyUw"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The address is now verified for that session.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_or_expired` (every token problem), or `invalid_request` (unreadable body).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_or_expired",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "description": "Missing or disallowed `Origin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "origin_not_allowed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`temporarily_unavailable` — transient fault; retry after `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "temporarily_unavailable",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verification-status": {
      "get": {
        "operationId": "kycVerificationIdStatus",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Read the public status of a Noxtica Verification ID (public, no authentication)",
        "description": "Public lookup behind the status link `https://verify.<env>/v/<NVID>` that a person can share. **No bearer token.** Returns only the status of the ID, its dates and the display name of the business that requested the verification — never a name, document data, image, email address or tenant identifier. An NVID confirms *status for a business*, not identity, and is **not a credential**: it cannot be used to sign in or to skip a verification.\n`status` is `revoked` (a Backoffice revocation; wins over everything), `pending` (the verification awaits a review decision), `expired` (365 days after completion) or `verified`. An unknown, erased or malformed ID — including one whose check symbol is wrong — answers the **same** `404 not_found` (no existence oracle). The `Origin` must be on the hosted-verification allow-list (`403 origin_not_allowed`); per-IP limit 60 requests per 60 seconds (`429`). Responses are `Cache-Control: no-store`. Only available while the NVID feature is enabled for the platform; otherwise the byte-identical gated `404`.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "The NVID, canonical form `NVID-XXXX-XXXX-XXXX-XXXX` (the collector also accepts the lower-case, unhyphenated and `O`/`I`/`L`-confusable spellings).",
            "schema": {
              "type": "string",
              "example": "NVID-7K3M-Q9XA-T2RW-4HZD"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ID's public status.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycVerificationIdStatusResponse"
                },
                "example": {
                  "ok": true,
                  "verification": {
                    "id": "NVID-7K3M-Q9XA-T2RW-4HZD",
                    "status": "verified",
                    "verifiedAt": 1790762445000,
                    "expiresAt": 1822298445000,
                    "revokedAt": null,
                    "requestedBy": "Example Bank"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing or disallowed `Origin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "origin_not_allowed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — unknown, erased or malformed ID (identical bodies), or the feature is not enabled for the platform (the gated `404` shape).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "error": "not_found"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP limit exceeded (60 requests per 60 seconds). `Retry-After` carries the seconds.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error",
                    "retryAfter"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "rate_limited"
                      ]
                    },
                    "retryAfter": {
                      "type": "integer"
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "error": "rate_limited",
                  "retryAfter": 12
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable; retry after `Retry-After` seconds (2).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "temporarily_unavailable"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "error": "temporarily_unavailable"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-evidence/handle": {
      "post": {
        "operationId": "kycSessionEvidenceHandle",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Mint an evidence-upload handle (browser-facing)",
        "description": "Token-authenticated sibling of `POST /v1/kyc/evidence/handle`, delegating to the same evidence pipeline — no parallel evidence store.",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Same shape as the S2S evidence/handle body, minus verificationId/tenant fields (resolved from the session)."
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Handle minted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Same envelope as POST /v1/kyc/evidence/handle."
                },
                "example": {}
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "403": {
            "description": "Missing/disallowed Origin, an invalid/expired session token, `device_upload_disabled`, or `evidence_refused` with a closed `refusal`: `evidence_not_provisioned` (the organisation's raw-evidence storage was never armed), `residency_unconfigured` (armed, but no evidence residency of `TR`, `EU` or `US`), `residency_refused` (the residency route refused the data class and jurisdiction pair, the only true region refusal), `binding_unprovisioned`, `evidence_storage_disabled` or `verification_not_admitted`. Treat an unknown `refusal` as a generic unavailable answer; never as a regional restriction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionEvidenceError"
                },
                "examples": {
                  "originOrSession": {
                    "value": {
                      "error": "invalid_request",
                      "requestId": "req-test"
                    }
                  },
                  "evidenceNotProvisioned": {
                    "value": {
                      "error": "evidence_refused",
                      "refusal": "evidence_not_provisioned",
                      "requestId": "req-test"
                    }
                  },
                  "residencyUnconfigured": {
                    "value": {
                      "error": "evidence_refused",
                      "refusal": "residency_unconfigured",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "410": {
            "$ref": "#/components/responses/SessionExpiredOrCancelled"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/session-evidence/finalize": {
      "post": {
        "operationId": "kycSessionEvidenceFinalize",
        "tags": [
          "KYC Hosted Sessions"
        ],
        "summary": "Upload sealed ciphertext and finalize (browser-facing)",
        "description": "Token-authenticated sibling of `POST /v1/kyc/evidence/finalize`.",
        "security": [
          {
            "SessionTokenAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Same shape as the S2S evidence/finalize body."
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Terminal inspection outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Same envelope as POST /v1/kyc/evidence/finalize."
                },
                "example": {}
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "403": {
            "description": "Missing/disallowed Origin, an invalid/expired session token, or a failed handle verification.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "410": {
            "$ref": "#/components/responses/SessionExpiredOrCancelled"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/erasure/self-request": {
      "post": {
        "operationId": "kycErasureSelfRequest",
        "tags": [
          "KYC Compliance"
        ],
        "summary": "Create a right-to-erasure request for an attested subject",
        "description": "Device-callable FR-15 erasure intake. The subject is resolved server-side from an opaque `attestationId` — a caller can never supply a `subjectId` directly (no cross-subject erasure oracle). Always resolves 200; a resolution miss, an inactive subject, or an infra fault all collapse to the SAME `{ok:true, requested:false, status:'subject_not_attested'}` shape.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "attestationId"
                ],
                "properties": {
                  "attestationId": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "attestationId": "att_9c6b2f7e4a3d4c8e9f1a2b3c4d5e6f70"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200 (fail-closed discipline).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "requested": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "subject_not_attested",
                        "requested",
                        "erasure_request_failed"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "requested": true,
                  "status": "subject_not_attested",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/activate": {
      "post": {
        "operationId": "kycActivate",
        "tags": [
          "KYC Compliance"
        ],
        "summary": "Transactionally activate a proofed-unbound mobile capability",
        "description": "Composes a signed attestation projection, an atomic edge consent receipt, and a typed device-identity proof into one transactional activation. Two opaque references only (`attestationId`, `receiptId`) — never a `subjectId` or `tenantId` (both are resolved server-side). Available only when mobile activation is enabled for your organisation (a separate enablement from `/v1/kyc/verify`); otherwise the route answers the `GatedNotFound` 404.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "attestationId",
                  "receiptId"
                ],
                "properties": {
                  "attestationId": {
                    "type": "string"
                  },
                  "receiptId": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "attestationId": "att_9c6b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
                "receiptId": "receipt_2f6a8b3c9d4e5f60718293a4b5c6d7e8"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200 (fail-closed discipline) — a false `activated:true` is never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "activated": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "description": "Present when activated is false (e.g. an unresolvable/foreign/stale attestation or receipt)."
                    },
                    "capability": {
                      "type": "object",
                      "description": "Present only when activated is true.",
                      "properties": {
                        "proofStatus": {
                          "type": "string",
                          "enum": [
                            "proofed_unbound"
                          ]
                        },
                        "verificationLevel": {
                          "type": "string",
                          "enum": [
                            "verified",
                            "partial",
                            "unverified",
                            "inconclusive"
                          ]
                        },
                        "receiptId": {
                          "type": "string"
                        },
                        "activatedAt": {
                          "type": "integer"
                        }
                      }
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "activated": true,
                  "reason": "user_requested",
                  "capability": {
                    "proofStatus": "proofed_unbound",
                    "verificationLevel": "verified",
                    "receiptId": "receipt_2f6a8b3c9d4e5f60718293a4b5c6d7e8",
                    "activatedAt": 1
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verifications/{verificationId}/identity": {
      "get": {
        "operationId": "kycVerificationIdentity",
        "tags": [
          "Identity Details"
        ],
        "summary": "Read the identity details of a verification",
        "description": "Server-to-server. Returns the identity details Noxtica extracted from the document\ncaptured for one of your own verifications: the document type, number and expiry, the\nholder's names, date of birth, sex and nationality, the machine-readable-zone check\nresults, and a summary of the verification checks. It never returns images or\nbiometric data.\n\n**Opt-in.** The key needs the `read:identity_details` scope, and the route answers\n`403 identity_read_not_enabled` until a tenant administrator enables identity details\naccess for your organisation in the Backoffice (KYC settings). A key keeps its scopes\nuntil a tenant administrator changes them (Edit scopes on the key in the Backoffice, API\nConfiguration): add `read:identity_details` to the key, or create a new key that carries it.\n\n**Which id.** `verificationId` is the id returned by `POST /v1/kyc/verify` (a UUID) or the\n`verificationId` of a hosted session (`hosted:` followed by 64 lower-case hexadecimal\ncharacters); see `KycVerificationId` for the formats and the 128-character maximum. The id\nof a hosted session and the `evidenceGroupId` of a verification made from your own capture\nscreens are accepted too and resolve to the same verification. An id that is malformed,\nunknown, or belongs to another organisation gets the same byte-identical `404` as an\nunknown path.\n\n**Outcome and review.** `verification.status` is `verified`, `unverified`, `failed`,\n`error` or `pending`, and `verification.source` says who produced it: `pipeline` is the\nautomated verdict, `review` means a reviewer's `approve` decision was applied over it (the\nsame outcome the hosted session status read reports). `verification.review` is `null` while\nno reviewer has acted, otherwise `{state, decision, version, decidedAt}` (see\n`KycTenantReview`). `verification.level` is the automated verification level and a review\ndoes not change it. A verification you created through the API has no session, so this\nread is where you see what a reviewer decided about it.\n\n**Review decision event.** A reviewer's `approve` or `override` on a verification is announced by the `kyc.review.decided` event; read this route again after it arrives to see the resulting `verification` and `review`. A decision recorded by your own back office through `POST /v1/kyc/verifications/{verificationId}/review-decision` is announced in the same way.\n\n**Selected and detected document type.** `document.selectedType` is the document type the\nperson chose in the hosted flow (`passport`, `national_id`, `driving_license` or\n`residence_permit`), or `null` for a verification created through the API or a session\nthat recorded no choice. `document.type` is the document type the stored extraction is\nfor. Compare the two: they differ when the extraction ran against another document type\nthan the person presented.\n\n**Extraction: what was read, and why not.** `extraction` answers, field by field, whether the\nidentity fields a relying party needs were read from the document, so a `null` in\n`person` or `document.number` always comes with a reason instead of an empty value. It is\npresent on every `200`. Which of the five fields are required is yours to set (see \"Required\nidentity fields\" below); a field that is not required is still reported, with its reason.\n\n| Key | Values |\n|---|---|\n| `extraction.status` | Computed over the required fields only. `complete`: every required field was read (also when none is required). `partial`: some were. `unreadable`: none was. |\n| `extraction.fields[]` | Always five entries, in this order: `person.surname`, `person.givenNames`, `person.dateOfBirth`, `person.personalNumber`, `document.number`. Each has `status`, `reason`, `source` and `required`; `person.surname` and `person.givenNames` also carry `foldedValue` and, when they apply, `uncertainty`, `uncertainPositions`, `alternatives`, `script` and `latinForm`. |\n| `fields[].status` | `read`: the field has a value. It is released, or withheld by the masking rules below, so `read` does not mean the value is in this response. `unreadable`: no value, see `reason`. `not_expected`: this document does not carry the field and your configuration does not require it. `person.personalNumber` is expected on a Turkish (`TR`) national ID card or driving licence, where it is the T.C. Kimlik No. |\n| `fields[].required` | `true` when the field counts towards `extraction.status` and `identity_fields_unreadable`; `false` when it is reported but does not. An `unreadable` field with `required: false` keeps its `reason` and does not lower `extraction.status`. |\n| `fields[].reason` | Only with `unreadable`. `label_not_found`: the printed label or numbered marker was not found in the recognised text. `value_not_found`: the label was found, with no value beside or below it. `value_invalid`: a value was found that is not a valid name, date or number. `checksum_invalid`: the personal number was read but its check digits fail; the value is withheld and never returned. `layout_unrecognised`: the front of the document matched no known layout, or nothing was recognised. `low_confidence`: a date was read below the recognition confidence floor and is withheld. `not_recorded`: the record was written before reasons were kept. |\n| `fields[].source` | Where the value came from: `viz` (the printed text of the document), `mrz` or `barcode`. `null` unless `status` is `read`. |\n| `fields[].uncertainty` | Only on `person.surname` and `person.givenNames`, and only when it applies: the key is absent otherwise. A list of advisory caveats on a `read` value. `diacritic_uncertain`: the value is the best reading, but a letter in it may carry a mark that the reading did not show, or the reverse: a Turkish `Ş` shown as `S`, a Polish `Ł` as `L`. It is set when the reader saw the marked and the plain letter as close candidates, when two readers of the printed text differed only in a mark, or when the value comes from the machine-readable zone or a barcode, which cannot print marks. `mrz_transliteration_conflict`: the printed name and the name in the machine-readable zone fit together only if a plain letter of the printed name carries a mark (printed `MULLER`, zone `MUELLER`); it can come alone or together with `diacritic_uncertain`. Both are advisory: `status` stays `read`, and they change neither `extraction.status` nor the verdict. What to do with each is in \"Names, diacritics and alphabets\" below. |\n| `fields[].uncertainPositions` | Only with `diacritic_uncertain`. The 0-based Unicode code point positions in the name (`person.surname` or `person.givenNames`, spaces counted), ascending, of the letters that may carry a different mark. For a name read from the printed text these are the letters the reader had doubt about; for a name taken from the machine-readable zone or a barcode they are every letter that has a marked form in the alphabet of the issuing country (for `TR`: `C`, `G`, `I`, `O`, `S`, `U`). |\n| `fields[].alternatives` | Up to four complete spellings of the name, most likely first, made only by swapping the flagged letters for their marked or plain forms. None equals the value and none repeats. With `mrz_transliteration_conflict` the list includes the spelling the machine-readable zone points to. Absent when there is no reading-based doubt, in particular for a name taken from the machine-readable zone or a barcode: that source cannot show a mark, so there is nothing to rank. The value is never replaced by an alternative. |\n| `fields[].foldedValue` | On every `read` `person.surname` and `person.givenNames`, with or without an uncertainty: the value with its marks removed as ICAO Doc 9303 Part 3 section 6.A lists them (one base letter such as `Ş` to `S`, `İ` to `I`, `Ä` to `A`, `Ñ` to `N`; the expansions `ß` to `SS`, `Æ` to `AE`, `Œ` to `OE`, `Ø` to `OE`, `Þ` to `TH`, `Ĳ` to `IJ`). The case of the value is kept, and it equals the value when no letter folds. It is a key for tolerant comparison, not a spelling of the name to store. |\n| `fields[].script` | The writing system the name was read in, as an ISO 15924 code: `Latn`, `Cyrl`, `Grek`, `Arab`, `Hebr`, `Geor`, `Armn`, `Deva`, `Thai`, `Hani`, `Jpan` or `Kore`. Absent when it was not determined. |\n| `fields[].latinForm` | `{value, source}`: a Latin-letter form of the name when one is available. `source` is `viz` (printed on the document) or `mrz` (the name in the machine-readable zone, which uses the ICAO transliteration). Absent when there is none. |\n| `extraction.reasonCodes` | `identity_fields_unreadable` when a required field is unreadable. `document_type_mismatch_selection` when `documentType.contradictsSelection` is `true`. `issuing_country_mismatch_selection` when `issuingCountry.contradictsSelection` is `true`. `diacritic_uncertain` or `mrz_transliteration_conflict` when `person.surname` or `person.givenNames` carries that `uncertainty`. An empty array when none applies. The first three codes are also added to the `reasonCodes` of the verification and of the session; the two name codes appear only here. All are informational and change neither the verdict nor the retake prompts. |\n| `extraction.documentType` | `selected`: the type the person chose (the same value as `document.selectedType`). `classified`: the type the classifier named, only when it is confident (0.72 or higher), otherwise `null`. `contradictsSelection`: `true` when the classified type is a different identity document type than the selection, `false` when they agree, `null` when they cannot be compared. |\n| `extraction.issuingCountry` | `selected`: the country the person chose in the hosted flow (ISO 3166-1 alpha-2), or `null`. `detected`: the country the document shows, or `null` when nothing readable named one. `source`: `mrz` (the issuing state of the machine-readable zone, used only when none of its check digits failed) or `viz` (the printed text), `null` exactly when `detected` is `null`. `contradictsSelection`: `true` when both are known and differ, `false` when they are equal, `null` when they cannot be compared; an unknown is never reported as a contradiction. |\n\n**Required identity fields.** By default `person.surname`, `person.givenNames`,\n`person.dateOfBirth` and `document.number` are required, and so is `person.personalNumber`\non a Turkish national ID card or driving licence. A document counts as Turkish when it shows\nTürkiye (`document.issuingCountry`, see `extraction.issuingCountry`), or, when no country could be read\nfrom it, when the person selected Türkiye. For those two Turkish document types one\nrule relaxes this: when the T.C. Kimlik No was read and passes its check digits,\n`document.number` is not required, so an unreadable document number is still reported (with\nits `reason` and `required: false`) but no longer raises `identity_fields_unreadable`. You can set the list per document type\nwith `requiredIdentityFields` on `documents` in your hosted configuration (organisation\ndefault or one domain; see `GET /v1/kyc/config`). A non-empty list is exactly the set that is\nrequired, replaces the Turkish rule, and may name `personalNumber` for any country; an empty\nlist requires nothing. The list that applied is recorded with the verification. A verification\nmade before this was recorded is read with the default rules at read time, while its stored\nsession `reasonCodes` stay as they were recorded.\n\n`identity_fields_unreadable` is informational. It does not decide whether a session is reviewed:\nevery hosted verification currently ends `unverified` with `provider_manual_review` until a\nreviewer decides, whatever the extraction says.\n\n**Selected and detected issuing country.** `extraction.issuingCountry` compares the country\nthe person selected with the country the document shows. Only Türkiye can be recognised from\nprinted text (the Turkish country heading, or a Turkish layout with a T.C. Kimlik No that\npasses its check digits); for any other country `detected` is set only when the\nmachine-readable zone names it. The words of a document alone, such as \"SÜRÜCÜ BELGESİ\", are not\nevidence of a country. A dependent territory selected against its sovereign state (for example\n`GF` and `FR`) is not a contradiction. `document.issuingCountry` is the country the document shows\nwhen one was read, otherwise the country the person selected, otherwise `null`. The hosted page\nstarts with the first country of the list selected, so narrow `documents[].issuingCountries`\nin your hosted configuration (or `hostedOptions.allowedIssuingCountries` on the invitation) to\nthe countries you accept; an example of a contradiction is\n`{selected: AD, detected: TR, source: viz, contradictsSelection: true}`.\n\nA personal number is returned (when `fields` names `personalNumber`) only if it passes the\nofficial T.C. Kimlik No check-digit rule; a number that fails it is `null` and its field\nreads `unreadable` with `checksum_invalid`. Dates are validated against the calendar before\nthey are returned. A value that could not be validated is reported unreadable, never\nguessed.\n\n**Names, diacritics and alphabets.** `person.surname` and `person.givenNames` hold the\nname as the document prints it; on a document where the printed text and the\nmachine-readable zone name the same person, the printed spelling is used. A text reader can\nshow a letter with a mark as its plain form (a Turkish `Ş` as `S`, a German `Ü` as `U`) or\nthe other way round, a letter can occasionally be missing, and the machine-readable zone\ncannot print marks at all. The name fields therefore carry advisory data next to the value\nand never instead of it: the value is not changed to a letter the printed read did not\nproduce, and every other spelling is offered as an alternative for you to check.\n\n| You see | It means | What to do |\n|---|---|---|\n| No `uncertainty` | The reader found no doubt on this name. This is not a confirmation of each letter. | Compare tolerantly (below) and keep a review path for a mismatch. |\n| `diacritic_uncertain` with `alternatives` | The name was read from the printed text and the letters in `uncertainPositions` may carry a different mark. | Check the value against the registry first, then each entry of `alternatives` in order, then `foldedValue` against registries that compare folded names only. If none matches, ask for a new capture or send the case to manual review. |\n| `diacritic_uncertain` without `alternatives` | The name comes from the machine-readable zone or a barcode, which cannot show marks. `uncertainPositions` lists the letters that could have one; nothing says which. | Do not make up spellings. Compare `foldedValue` with registries that compare folded names, otherwise ask the person to confirm the spelling, ask for a capture of the printed side, or send the case to manual review. |\n| `foldedValue` | The value with marks removed. Present on every read name. | Compare the `foldedValue` of both sides when you want a tolerant match (below). Do not store it as the name. |\n| `mrz_transliteration_conflict` | The printed name and the machine-readable zone do not fit together unless a plain letter of the printed name carries a mark. `alternatives` holds that spelling. | Check the spelling from `alternatives` against the registry before `foldedValue`; if it matches, have the person or a reviewer confirm it. If nothing matches, ask for a new capture or send the case to manual review. |\n\n**What the machine-readable zone can and cannot show.** The zone holds the letters `A` to\n`Z`, digits and `<` only. ICAO Doc 9303 Part 3 section 6.A lists how an issuing state\nwrites other letters there: `Ä` as `AE` or `A`, `Ö` as `OE` or `O`, `Ü` as `UE`, `UXX` or\n`U`, `Å` as `AA` or `A`, `Ø` as `OE`, `Æ` as `AE`, `ß` as `SS`, `Ñ` as `N` or `NXX`, `Þ` as\n`TH`. Turkish letters have no such expansion: `Ş`, `Ç`, `Ğ`, `İ`, `Ö` and `Ü` always become\n`S`, `C`, `G`, `I`, `O` and `U`, so a Turkish zone cannot confirm which letter was printed\n(`SAHIN` fits `ŞAHİN`, `SAHİN` and `SAHIN`). A zone that uses an expansion can: printed\n`MÜLLER` with zone `MUELLER` agrees only when the printed letter is `Ü`, so that letter is\ndropped from `uncertainPositions`. Printed `MULLER` with zone `MUELLER` is a conflict: the\nzone spelling needs a mark that the printed reading lacks, and\n`mrz_transliteration_conflict` is set. An ordinary difference between the two names is not\na conflict and is not flagged here.\n\n**What a missing flag means, and how often a flag is right.** A name without `uncertainty`\nmeans that the reader found no doubt on it. It is not a confirmation of each letter, and\nthe name can still contain a wrong one: measured on synthetic test documents, many wrong\nletters are read with full confidence and carry no flag. For the printed name on Turkish\nnational ID card and driving licence fronts, the flag appears on roughly one in ten names.\nAbout one in three flagged names is actually wrong (the others are already right), and for\nmost wrong flagged names the true spelling is among `alternatives`. So treat a flag as a\nreason to try the alternatives, not as a verdict, and keep the tolerant comparison and the\nreview path for a mismatch whether the flag is present or not.\n\n**Check a name against a registry (Turkish national registry example).**\n\n1. Always send the `value` first, upper-cased with Turkish rules (`i` to `İ`, `ı` to `I`);\na plain upper-case turns `i` into `I` and breaks the match.\n2. On a mismatch while `diacritic_uncertain` is present, send each entry of `alternatives`\nin order, upper-cased the same way.\n3. If nothing matches, ask the person for a new capture or send the case to manual review.\nA registry that compares folded names can be sent `foldedValue` instead of steps 1 and 2.\n4. Do not write an alternative or the folded value into your records as the legal name\nwithout a confirmation by the person, a reviewer or the registry answer.\n\n**Tolerant matching.** To compare a name with a name you hold without a registry, fold both\nsides the same way and compare the folded strings: use `foldedValue` for the name read from\nthe document and apply the same ICAO folding (and one case convention, for example Turkish\nupper case) to your own value. A match of the folded strings says the two names agree up to\nmarks; it does not say which marks are right, so keep the review path for names that matter.\n\n**Scope.** The fields are optional and additive: a verification recorded before this\nrelease can lack `alternatives`, `uncertainPositions`, `script`, `latinForm` and\n`mrz_transliteration_conflict`, and a finished verification is not read again, so treat each\nof them as optional. Doubts about the printed read, and so `alternatives`, exist for the\nTurkish national ID card and driving licence layouts. Flags for a name taken from the\nmachine-readable zone or a barcode exist for the countries whose alphabet Noxtica lists,\nfor example `TR`, `DE`, `AT`, `LI` and `PL`; `foldedValue` is on every read name. For a name\nin a non-Latin script, `script` says which one and `latinForm` gives a Latin form when\nthere is one; the value stays as read.\n\n**Turkish driving licence number.** The document number of a Turkish driving licence is the\nvalue of field `5.` on the front: six digits, with leading zeros kept; it has no check digit\nthat could be verified. Spaces, dots and stray marks inside the number (`123 456`,\n`12|3456`) are dropped. A letter read in place of a look-alike digit (for example `O` for\n`0` or `I` for `1`) is corrected only when the value has six characters and at least five\nof them are digits; a value of any other shape (six to twelve letters and digits with at\nleast one digit, such as the older `A123456` form) is accepted exactly as read and is never\nrepaired. A number that is not printed, or that cannot be recognised, stays `unreadable`\n(`value_not_found` or `value_invalid`) and is never guessed. Unless you list `documentNumber`\nin `requiredIdentityFields`, an unreadable licence number does not raise\n`identity_fields_unreadable` while the T.C. Kimlik No of the licence was read.\n\n**Check results.** `checks.documentAuthenticity`, `checks.faceMatch`, `checks.liveness` and\n`checks.selfie` are verdicts (`pass`, `review`, `fail`, `skipped`, `pending`, or `null` when\nno automated result was recorded for that check). They are evidence for review and for your\nown risk rules, not a decision. What each check measures, the reason codes behind it and the\npublishable thresholds are described under \"Check results\" on\n`GET /v1/kyc/sessions/{sessionId}`. Why `liveness` and `selfie` read at best `review` is\nexplained on [Liveness & PAD policy](https://noxtica.com/docs/identity-verification-liveness-pad-policy/).\n\n**Masked by default.** Names, date of birth, expiry date, nationality, sex, issuing\ncountry, ID type and document type are returned in full. The document number is returned\nmasked (its last characters only), and the personal number and the raw\nmachine-readable-zone lines are left out. Name them in `fields` (comma separated) to\nreceive them: `documentNumber`, `personalNumber`, `mrzLines`. Every masked or missing\nitem is listed in `redactions` with the `fields` value that returns it; data Noxtica does\nnot extract (the address today) is listed with the reason `not_extracted`. A `person.surname`,\n`person.givenNames` or `person.dateOfBirth` that is `null` is listed with the reason\n`not_available`. An unknown `fields` value is a `400 invalid_fields` that lists the accepted\nvalues.\n\n**Availability.** Details exist once a verification has finished and its fields were\nextracted. A verification without stored details answers `409 identity_not_available`\nwith `reason` `not_completed` (not finished yet) or `not_extracted` (nothing was\nextracted or stored: evidence storage is not enabled for your organisation, or the\nverification predates extraction). A verification you deleted answers\n`410 verification_deleted`.\n\n**Audit and handling.** Each successful read is recorded for your organisation with the\nkey that made it and the names of the fields returned, never their values. Every response\nof this route carries `Cache-Control: no-store, private`: do not cache or log the body.\nIdentity details are never sent in webhooks.\n\n**Rate limit.** A per-key limit applies to the identity details routes together; a\nlimited request answers `429` with `Retry-After`.",
        "security": [
          {
            "ApiKeyAuth": [
              "read:identity_details"
            ]
          }
        ],
        "parameters": [
          {
            "name": "verificationId",
            "in": "path",
            "required": true,
            "description": "The `verificationId` of the verification.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9:_-]{1,128}$"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": false,
            "description": "Comma-separated list of withheld values to include: `documentNumber` (the full document number), `personalNumber`, `mrzLines` (the raw machine-readable-zone lines). Omit it for the masked default.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "documentNumber",
                  "personalNumber",
                  "mrzLines"
                ]
              }
            },
            "example": [
              "documentNumber",
              "personalNumber"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The identity details of the verification.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycVerificationIdentity"
                },
                "example": {
                  "ok": true,
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "sourceEvidenceGroupId": null,
                  "extractedAt": "2026-10-28T14:22:11.000Z",
                  "verification": {
                    "status": "verified",
                    "source": "pipeline",
                    "level": "verified",
                    "review": null
                  },
                  "document": {
                    "type": "national_id",
                    "selectedType": null,
                    "side": "front",
                    "issuingCountry": "GB",
                    "number": "••••4821",
                    "expiryDate": "2031-05-17",
                    "idType": "national_id"
                  },
                  "person": {
                    "fullName": "ADA EXAMPLE",
                    "surname": "EXAMPLE",
                    "givenNames": "ADA",
                    "dateOfBirth": "1990-01-31",
                    "sex": "F",
                    "nationality": "GB",
                    "personalNumber": null
                  },
                  "address": null,
                  "mrz": {
                    "format": "TD1",
                    "fieldChecks": [
                      {
                        "field": "document_number",
                        "checkDigitValid": true
                      },
                      {
                        "field": "birth_date",
                        "checkDigitValid": true
                      },
                      {
                        "field": "expiry_date",
                        "checkDigitValid": true
                      }
                    ]
                  },
                  "checks": {
                    "documentAuthenticity": "pass",
                    "faceMatch": "pass",
                    "liveness": "review",
                    "selfie": "review",
                    "crossValidation": {
                      "overall": "match",
                      "fields": [
                        {
                          "field": "dateOfBirth",
                          "agreement": "match"
                        },
                        {
                          "field": "documentNumber",
                          "agreement": "match"
                        }
                      ]
                    }
                  },
                  "extraction": {
                    "status": "complete",
                    "reasonCodes": [],
                    "fields": [
                      {
                        "field": "person.surname",
                        "status": "read",
                        "reason": null,
                        "source": "mrz",
                        "required": true,
                        "foldedValue": "EXAMPLE",
                        "script": "Latn"
                      },
                      {
                        "field": "person.givenNames",
                        "status": "read",
                        "reason": null,
                        "source": "mrz",
                        "required": true,
                        "foldedValue": "ADA",
                        "script": "Latn"
                      },
                      {
                        "field": "person.dateOfBirth",
                        "status": "read",
                        "reason": null,
                        "source": "mrz",
                        "required": true
                      },
                      {
                        "field": "person.personalNumber",
                        "status": "not_expected",
                        "reason": null,
                        "source": null,
                        "required": false
                      },
                      {
                        "field": "document.number",
                        "status": "read",
                        "reason": null,
                        "source": "mrz",
                        "required": true
                      }
                    ],
                    "documentType": {
                      "selected": null,
                      "classified": "national_id",
                      "contradictsSelection": null
                    },
                    "issuingCountry": {
                      "selected": null,
                      "detected": "GB",
                      "source": "mrz",
                      "contradictsSelection": null
                    }
                  },
                  "redactions": [
                    {
                      "field": "document.number",
                      "reason": "masked_by_default",
                      "include": "documentNumber"
                    },
                    {
                      "field": "person.personalNumber",
                      "reason": "omitted_by_default",
                      "include": "personalNumber"
                    },
                    {
                      "field": "mrz.lines",
                      "reason": "omitted_by_default",
                      "include": "mrzLines"
                    },
                    {
                      "field": "address",
                      "reason": "not_extracted"
                    }
                  ],
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/IdentityInvalidFields"
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/IdentityReadForbidden"
          },
          "404": {
            "$ref": "#/components/responses/IdentityNotFound"
          },
          "409": {
            "description": "`identity_not_available` — the verification exists but has no stored identity details. `reason` is `not_completed` (it has not finished) or `not_extracted` (nothing was extracted or stored for it).",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityNotAvailableError"
                },
                "example": {
                  "error": "identity_not_available",
                  "reason": "not_completed",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "description": "`verification_deleted` — you deleted this verification; its details no longer exist.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "verification_deleted",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "$ref": "#/components/responses/IdentityUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/verifications/{verificationId}/review-decision": {
      "post": {
        "operationId": "kycVerificationReviewDecision",
        "tags": [
          "Review Decisions"
        ],
        "summary": "Record a review decision for a verification",
        "description": "Server-to-server. Lets your own back office record the decision on a verification that is waiting for review: the same decision a Backoffice reviewer records in the review panel, written by the same decision service, with the same effect on the status Noxtica reports, the same `kyc.review.decided` event and the same audit trail. Nothing else changes: the automated verdict of the verification is never rewritten, and a decision is never made for you.\n\n**Opt-in at two levels.** The route is off for every organisation until Noxtica turns the review decision API on for the platform **and** a tenant administrator of your organisation enables it in the Backoffice (KYC → Invitations → Settings tab, card \"Review decision API\", confirmed with step-up multi-factor authentication; it is not on the Settings page of your own account). While the platform has it off, a request answers the byte-identical `404` of an unknown path; while your organisation has not enabled it, `403 review_decision_not_enabled`. The key needs the `write:kyc_review_decision` scope: no other scope reaches this route, and `write:identity` does not imply it. A key keeps its scopes until a tenant administrator changes them (Edit scopes on the key in the Backoffice, API Configuration): add the scope to the key, or create a new key that carries it. The tenant administrator can switch the route off again at any time; the decisions already written stay.\n\n**Which id.** `verificationId` is the id returned by `POST /v1/kyc/verify` (a UUID) or the `verificationId` of a hosted session (`hosted:` followed by 64 lower-case hexadecimal characters); see `KycVerificationId` for the formats and the 128-character maximum. An id that is malformed, unknown, or belongs to another organisation gets the same byte-identical `404` as an unknown path, so the route cannot be used to find out whether a verification exists.\n\n**Decisions.** There are two, the two affirmative decisions of the review panel, and no other:\n\n| `decision` | Meaning | Reported status afterwards | Allowed `reasonCode` |\n|---|---|---|---|\n| `approve` | Your reviewer affirmed the verification. | `verified`, with `verification.source` `review` (see `GET /v1/kyc/verifications/{verificationId}/identity` and `GET /v1/kyc/sessions/{sessionId}`). `approve` is the only decision that reports a verification as `verified`. | `identity_confirmed`, `registry_crosscheck_passed`, `name_variant_accepted`, `manual_document_check_passed`, `other` |\n| `override` | Your reviewer replaced the automated verdict. It is not an approval. | The automated verdict is reported as it is; `override` never makes the status `verified`. | `identity_mismatch`, `registry_crosscheck_failed`, `document_invalid`, `suspected_fraud`, `liveness_not_accepted`, `other` |\n\nThere is no `reject` decision: the review panel has none, and `override` is the action that replaces the automated verdict. Escalating a case is a state of the review, not a decision, and is not offered here. A `reasonCode` that does not belong to the `decision` of the same request is a `400 invalid_reason_code`. `other` means none of the listed reasons; keep your own reason text in your own system, keyed by `reviewer` and the delivery id of the `kyc.review.decided` event.\n\n**Who decided.** `reviewer` is your own identifier of the person who took the decision (for example the id of the reviewer in your back office): 1 to 128 visible characters, no surrounding spaces and no control characters. Noxtica stores it with the decision, next to the API key that sent the request, and returns it in this response. Use an internal id, not a name or an email address. The optional `note` (at most 2000 characters) is stored with the decision. Neither the reviewer, the key nor the note is ever sent in a webhook or returned by a read.\n\n**Pending reviews only.** A decision is accepted while the review of the verification is still pending: no reviewer has acted on it, or a reviewer escalated it without deciding. A verification that already holds a decision, from the panel or from this route, answers `409 review_not_pending` and nothing is written; use the Backoffice to change a decision that was already recorded. As for the panel, the verification must have finished and have an active legal basis recorded for it (`409 legal_basis_missing` otherwise). A review that was closed answers `409 review_closed`.\n\n**What a decision does.** The decision is written together with its audit row (action `kyc_review_decision_recorded`, source `tenant_api`) in one transaction. A hosted session that was `review_pending` is completed by it, exactly as a panel decision completes it, and the `kyc.review.decided` event is sent to the endpoints that subscribe to it with `data.actor.type` `api` and the closed `data.reviewReasonCode`. Hosted verifications keep ending `unverified` with the reason `provider_manual_review` until a decision is recorded: the automated outcome never approves, and a decision recorded here is the only thing that changes what is reported. Read `verification` and `review` on `GET /v1/kyc/verifications/{verificationId}/identity` (scope `read:identity_details`) or `GET /v1/kyc/sessions/{sessionId}` afterwards. The response of this route already carries the resulting `state`, `version` and `decidedAt`.\n\n**Idempotency.** `Idempotency-Key` is required: 8 to 128 characters (a UUID is fine), a new value for every new decision. Send the same key again with the same body (a retry after a timeout) and you get the first answer back: `200` with the header `Idempotent-Replayed: true` and `replayed: true`. Nothing is written a second time, so no second event is sent. The same key with a different verification or a different body answers `422 idempotency_key_reuse`. A key is scoped to your organisation. A `409 conflict` means another decision for the same verification was written at the same moment and yours was not: read the verification, and send a new request only if it is still pending.\n\n**Audit and handling.** Every decision is recorded for your organisation with the key that made it and the `reviewer` you sent. Every response of this route carries `Cache-Control: no-store, private`.\n\n**Rate limit.** 30 requests per 60 seconds for each key; a limited request answers `429` with `Retry-After`. The route is available only while the KYC module is enabled for your organisation; otherwise it answers the `GatedNotFound` 404.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:kyc_review_decision"
            ]
          }
        ],
        "parameters": [
          {
            "name": "verificationId",
            "in": "path",
            "required": true,
            "description": "The `verificationId` of the verification to decide.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9:_-]{1,128}$"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycReviewDecisionRequest"
              },
              "examples": {
                "approve": {
                  "summary": "Approve after a registry check",
                  "value": {
                    "decision": "approve",
                    "reasonCode": "registry_crosscheck_passed",
                    "reviewer": "reviewer-17",
                    "note": "Name matches the registry answer."
                  }
                },
                "override": {
                  "summary": "Override, the document is not valid",
                  "value": {
                    "decision": "override",
                    "reasonCode": "document_invalid",
                    "reviewer": "reviewer-17"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decision is recorded and now stands (`replayed: false`), or the same request was already recorded and its first answer is returned (`replayed: true`, header `Idempotent-Replayed: true`).",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              },
              "Idempotent-Replayed": {
                "description": "`true` only when the answer is the stored answer of an earlier request with the same `Idempotency-Key`. Absent otherwise.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycReviewDecisionResult"
                },
                "example": {
                  "requestId": "req-test",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "decision": "approve",
                  "reasonCode": "registry_crosscheck_passed",
                  "reviewer": "reviewer-17",
                  "state": "approved",
                  "version": 1,
                  "decidedAt": 1790762445000,
                  "replayed": false
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid; nothing was written. `error` is `idempotency_key_invalid` (the `Idempotency-Key` header is missing or not 8 to 128 characters), `invalid_body` (not a JSON object, or it has a member this route does not accept), `invalid_decision`, `invalid_reason_code` (not in the list of the decision), `invalid_reviewer` or `invalid_note`. `message` says what to fix and never repeats your values.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycReviewDecisionError"
                },
                "examples": {
                  "idempotencyKeyInvalid": {
                    "value": {
                      "error": "idempotency_key_invalid",
                      "message": "Idempotency-Key must be 8 to 128 characters",
                      "requestId": "req-test"
                    }
                  },
                  "invalidReasonCode": {
                    "value": {
                      "error": "invalid_reason_code",
                      "message": "reasonCode for approve must be one of: identity_confirmed, registry_crosscheck_passed, name_variant_accepted, manual_document_check_passed, other",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "description": "`review_decision_not_enabled` — a tenant administrator has not enabled the review decision API for your organisation, so nothing was written; or the key lacks the `write:kyc_review_decision` scope (`error` is `API key does not have write:kyc_review_decision scope`). A tenant administrator enables the API in the Backoffice, KYC → Invitations → Settings tab, card \"Review decision API\".",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycReviewDecisionError"
                },
                "examples": {
                  "notEnabled": {
                    "summary": "The review decision API is not enabled for the organisation",
                    "value": {
                      "error": "review_decision_not_enabled",
                      "message": "A tenant administrator must enable the review decision API in Backoffice Settings",
                      "requestId": "req-test"
                    }
                  },
                  "scopeMissing": {
                    "summary": "The key does not carry the scope",
                    "value": {
                      "error": "API key does not have write:kyc_review_decision scope",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The byte-identical \"V1 endpoint not found\" response, returned for a malformed id, an id that does not exist, an id that belongs to another organisation, or a route that is not available (the review decision API is off for the platform, or the KYC module is not enabled for your organisation) — indistinguishable from an unknown path by design.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "V1 endpoint not found"
                      ]
                    },
                    "path": {
                      "type": "string"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "V1 endpoint not found",
                  "path": "/v1/kyc/verifications/3fa85f64-5717-4562-b3fc-2c963f66afa6/review-decision",
                  "requestId": "req-test"
                }
              }
            }
          },
          "409": {
            "description": "Nothing was written. `review_not_pending` — the verification already holds a decision. `review_closed` — the review was closed. `legal_basis_missing` — the verification has not finished, or no active legal basis is recorded for it. `conflict` — another decision for this verification was written at the same moment; read the verification and send a new request only if it is still pending.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycReviewDecisionError"
                },
                "examples": {
                  "reviewNotPending": {
                    "value": {
                      "error": "review_not_pending",
                      "requestId": "req-test"
                    }
                  },
                  "reviewClosed": {
                    "value": {
                      "error": "review_closed",
                      "requestId": "req-test"
                    }
                  },
                  "legalBasisMissing": {
                    "value": {
                      "error": "legal_basis_missing",
                      "requestId": "req-test"
                    }
                  },
                  "conflict": {
                    "value": {
                      "error": "conflict",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`invalid_body` — the body is larger than 16 KiB. Nothing was written.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycReviewDecisionError"
                },
                "example": {
                  "error": "invalid_body",
                  "message": "The body is too large",
                  "requestId": "req-test"
                }
              }
            }
          },
          "415": {
            "description": "`invalid_body` — the `Content-Type` is not `application/json`. Nothing was written.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycReviewDecisionError"
                },
                "example": {
                  "error": "invalid_body",
                  "message": "The Content-Type must be application/json",
                  "requestId": "req-test"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse` — the `Idempotency-Key` was already used for a different verification or a different body. Nothing was written. Use a new key for a new decision.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycReviewDecisionError"
                },
                "example": {
                  "error": "idempotency_key_reuse",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "description": "Retryable. Retry with the same `Idempotency-Key`: you get the decision written by an earlier try as a replay, or the decision is written now. `review_decision_unavailable` — the decision, or the setting that enables the route, could not be read or written right now. `tenant_module_gate_unavailable` — the module check could not be confirmed. `api_key_auth_unavailable` — the key could not be checked. Every one of them carries `Retry-After`.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/IdentityCacheControl"
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycReviewDecisionError"
                },
                "examples": {
                  "unavailable": {
                    "value": {
                      "error": "review_decision_unavailable",
                      "reason": "backend_unavailable",
                      "requestId": "req-test"
                    }
                  },
                  "moduleGateUnavailable": {
                    "value": {
                      "error": "tenant_module_gate_unavailable",
                      "message": "The tenant module gate could not be confirmed; retry shortly",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kyc/load": {
      "get": {
        "operationId": "getKycLoadStatus",
        "tags": [
          "KYC Capacity"
        ],
        "summary": "Read the current verification capacity and a start / wait recommendation",
        "description": "Server-to-server. Tells your backend how busy Noxtica's verification capacity is right now\nand whether this is a good moment to start a new verification. Call it before you send a\ncustomer into a capture flow or submit a verification, when you would rather queue or delay\nthe customer than let them wait on a busy platform. The answer is **advisory**: it never\nreserves, admits or blocks anything, a verification started while the answer is `defer` is\nstill processed, and every verification route keeps its own limits.\n\n**What the numbers mean.** `capacity` is counted in concurrent inference **slots**, not in\nsessions. One slot runs one inference job at a time, and one verification uses a slot for\neach of its steps (a step typically takes 25 ms to 3 s; a verification has about three to\nfive steps). `capacity: 8` therefore means eight inference jobs can run at the same moment,\nnot that eight customers can be verified at the same moment. `inUse` is the number of busy\nslots, `queueDepth` the number of jobs waiting for a slot, `estimatedWaitMs` how long a\njob that arrives now would wait before it starts, and `processingMs` the p50 and p95 duration\nof recently completed jobs. Everything except `yours` is a platform-wide aggregate shared by\nall customers; `yours.inFlight` is the number of verifications your own organisation has\nrunning right now.\n\n**Recommendation.**\n\n| `recommendation` | Meaning | Suggested action |\n|---|---|---|\n| `accept` | Capacity is available and nothing is queued. | Start the verification. |\n| `degraded` | Verifications are still accepted and will finish, but expect them to be slower than usual, or the margin is thin. | Start it if your flow tolerates extra latency; otherwise show a \"taking longer than usual\" message. |\n| `defer` | The platform is saturated or has no ready capacity; a new job is likely to wait several seconds or be refused with an overload answer. | Queue the customer, show a wait message, and ask again later with backoff, using `estimatedWaitMs` as a hint. |\n\n`recommendation` is derived from `reasons`: any reason from the first group below gives\n`defer`, otherwise any reason from the second group gives `degraded`, otherwise `accept`.\n`reasons` is ordered (the `defer` reasons first) and never repeats a value.\n\n| `reasons` value | Group | Present when |\n|---|---|---|\n| `no_ready_instance` | `defer` | No inference capacity is ready to take work (`capacity` is `0`). |\n| `queue_wait_high` | `defer` | `estimatedWaitMs` is at least 6000 ms, or at least the platform's own queue-wait limit when that is lower. |\n| `queue_full` | `defer` | `queueDepth` is greater than or equal to `capacity`. |\n| `recent_overload` | `defer` | A request was refused for overload, queue timeout or memory pressure within the last 30 seconds. |\n| `high_utilization` | `degraded` | `inUse / capacity` is at least 0.75. |\n| `queue_building` | `degraded` | `queueDepth` is greater than 0. |\n| `wait_elevated` | `degraded` | `estimatedWaitMs` is at least 1500 ms. |\n| `reduced_capacity` | `degraded` | Part of the platform's inference capacity is out of service, so `capacity` is lower than normal. |\n| `escalation_unavailable` | `degraded` | The escalation stage that re-examines difficult cases is unavailable; verifications continue without it. |\n| `escalation_saturated` | `degraded` | The escalation stage is running at full capacity, so difficult cases may wait. |\n| `vram_pressure` | `degraded` | Accelerator memory is low on part of the capacity, which slows or refuses new jobs. |\n| `slow_processing` | `degraded` | `processingMs.p95` is at least 10000 ms over at least 20 samples. |\n| `load_unavailable` | `degraded` | `status` is `unknown`: the load could not be read. |\n\n**`status: unknown`.** If Noxtica cannot read its own load (a timeout, an unreachable\nservice, an invalid answer), the route still answers `200`, never a `5xx`, with\n`status: unknown`, `recommendation: degraded`, `reasons: [\"load_unavailable\"]` and `null` in\n`capacity`, `inUse`, `queueDepth`, `estimatedWaitMs` and `processingMs`. It is `degraded`,\nnot `defer`, on purpose: a monitoring glitch must not make you stop every verification. Keep\nstarting verifications; if the verification service is really down, the verification call\nitself reports it. An `unknown` answer is cached for 5 seconds, then the load is read again.\n\n**Caching and polling.** The platform part of the answer is computed once and shared by all\ncustomers; `ttlSeconds` is how long that snapshot is kept (8 seconds, or 5 for `unknown`),\nand the response carries `Cache-Control: private, max-age=5`. Polling faster than every 10\nseconds only returns the same snapshot. Poll from your backend on a timer (or when a customer\nis about to start), cache the answer on your server and share it between your users; do not\ncall this route once per end-user page view. `yours.inFlight` is computed on each request.\nThe route is rate limited per API key (30 requests per 60 seconds); a limited call answers\n`429` with `Retry-After`.\n\n**Authentication.** `Authorization: Bearer sk_...` with the `write:identity` scope, the same\nscope as `GET /v1/kyc/sessions/{sessionId}`; there is no separate read scope. A key without\nit gets `403`. The route is available only when the KYC module is enabled for your\norganisation; otherwise it answers the `GatedNotFound` 404, indistinguishable from an unknown path.\n\n**Privacy.** The answer carries aggregated platform numbers and your own in-flight count only.\nIt contains no personal data, no other customer's data, and nothing about how or where the\ncapacity is hosted.",
        "security": [
          {
            "ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The capacity snapshot. Also the answer, with `status: unknown`, when the platform could not read its own load; this route does not answer `5xx` for that.",
            "headers": {
              "Cache-Control": {
                "description": "The answer may be reused by the calling backend for up to 5 seconds.",
                "schema": {
                  "type": "string",
                  "example": "private, max-age=5"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycLoadStatus"
                },
                "examples": {
                  "accept": {
                    "summary": "Capacity is available",
                    "value": {
                      "ok": true,
                      "status": "ok",
                      "recommendation": "accept",
                      "reasons": [],
                      "capacity": 8,
                      "inUse": 5,
                      "queueDepth": 0,
                      "estimatedWaitMs": 0,
                      "processingMs": {
                        "p50": 400,
                        "p95": 1500,
                        "samples": 256
                      },
                      "yours": {
                        "inFlight": 1
                      },
                      "asOf": "2026-10-28T17:31:08.000Z",
                      "ttlSeconds": 8,
                      "requestId": "req-test"
                    }
                  },
                  "degraded": {
                    "summary": "Busy; verifications are accepted but slower than usual",
                    "value": {
                      "ok": true,
                      "status": "ok",
                      "recommendation": "degraded",
                      "reasons": [
                        "high_utilization",
                        "queue_building",
                        "wait_elevated"
                      ],
                      "capacity": 8,
                      "inUse": 7,
                      "queueDepth": 2,
                      "estimatedWaitMs": 1800,
                      "processingMs": {
                        "p50": 800,
                        "p95": 3000,
                        "samples": 256
                      },
                      "yours": {
                        "inFlight": 3
                      },
                      "asOf": "2026-10-28T17:31:16.000Z",
                      "ttlSeconds": 8,
                      "requestId": "req-test"
                    }
                  },
                  "defer": {
                    "summary": "Saturated; hold new verifications",
                    "value": {
                      "ok": true,
                      "status": "ok",
                      "recommendation": "defer",
                      "reasons": [
                        "queue_wait_high",
                        "queue_full",
                        "high_utilization",
                        "queue_building",
                        "wait_elevated"
                      ],
                      "capacity": 8,
                      "inUse": 8,
                      "queueDepth": 9,
                      "estimatedWaitMs": 7200,
                      "processingMs": {
                        "p50": 1500,
                        "p95": 6000,
                        "samples": 256
                      },
                      "yours": {
                        "inFlight": 4
                      },
                      "asOf": "2026-10-28T17:31:24.000Z",
                      "ttlSeconds": 8,
                      "requestId": "req-test"
                    }
                  },
                  "unknown": {
                    "summary": "The load could not be read (still HTTP 200)",
                    "value": {
                      "ok": true,
                      "status": "unknown",
                      "recommendation": "degraded",
                      "reasons": [
                        "load_unavailable"
                      ],
                      "capacity": null,
                      "inUse": null,
                      "queueDepth": null,
                      "estimatedWaitMs": null,
                      "processingMs": null,
                      "yours": {
                        "inFlight": 1
                      },
                      "asOf": "2026-10-28T17:31:32.000Z",
                      "ttlSeconds": 5,
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerMisconfigured"
          },
          "503": {
            "description": "Retryable (`Retry-After: 2`). `tenant_module_gate_unavailable`: whether the KYC module is enabled for your organisation could not be confirmed. Retry shortly.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KycSessionError"
                },
                "example": {
                  "error": "tenant_module_gate_unavailable",
                  "message": "The tenant module gate could not be confirmed; retry shortly",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/kym/transactions": {
      "post": {
        "operationId": "kymTransactionsReport",
        "tags": [
          "KYM"
        ],
        "summary": "Report one observed merchant transaction signal",
        "description": "Notify-only. Records exactly one transaction signal that feeds scheduled alert-rule evaluation; this endpoint never calls a payment or execution API. The target `caseId` must already exist under KYM monitoring (created in the Backoffice KYB/KYM case surface).",
        "security": [
          {
            "ApiKeyAuth": [
              "write:kym"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "caseId",
                  "idempotencyKey",
                  "amountMinor",
                  "currency",
                  "occurredAt"
                ],
                "properties": {
                  "caseId": {
                    "type": "string",
                    "maxLength": 512
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "amountMinor": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Amount in the currency's minor unit (e.g. cents)."
                  },
                  "currency": {
                    "type": "string",
                    "pattern": "^[A-Z]{3}$",
                    "example": "USD"
                  },
                  "counterpartyRef": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "occurredAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              "example": {
                "caseId": "example-caseId",
                "idempotencyKey": "example-idempotencyKey",
                "amountMinor": 0,
                "currency": "USD",
                "counterpartyRef": "example-counterpartyRef",
                "occurredAt": "2026-09-13T12:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A replayed idempotencyKey — the original row (deduplicated).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KymTransactionResponse"
                },
                "example": {
                  "ok": true,
                  "eventId": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
                  "deduplicated": false,
                  "requestId": "req-test"
                }
              }
            }
          },
          "201": {
            "description": "A new transaction-event row was recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KymTransactionResponse"
                },
                "example": {
                  "ok": true,
                  "eventId": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
                  "deduplicated": false,
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_case_id` / `invalid_idempotency_key` / `invalid_amount` / `invalid_currency` / `invalid_occurred_at`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/ScopeForbidden"
          },
          "404": {
            "description": "The byte-identical gated-route 404, OR `case_not_found` (the caseId does not exist for this tenant).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`write_failed` — a persistence fault.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/api/mcp": {
      "post": {
        "operationId": "mcpJsonRpc",
        "tags": [
          "MCP Server"
        ],
        "summary": "Send one MCP JSON-RPC request",
        "description": "Sends one JSON-RPC 2.0 request and returns one JSON-RPC 2.0 response. Set\n`Content-Type: application/json`. Notifications and batches are not supported: send one\nrequest object with a `method` and, for calls that expect an answer, an `id`.\n\nThe HTTP status is `200` for every JSON-RPC outcome, including errors, except for the\ntransport failures `401`, `404`, `429` and `500`.",
        "security": [
          {
            "McpTokenAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "toolsList": {
                  "summary": "List the tools this token may call",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list"
                  }
                },
                "toolsCall": {
                  "summary": "Call a tool",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "list_alerts",
                      "arguments": {
                        "limit": 25
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response: `result` on success or `error` on failure (the HTTP status is `200` in both cases). With streamable transport negotiated and `Accept: text/event-stream`, the same object is sent as one `text/event-stream` `message` event.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Bucket": {
                "$ref": "#/components/headers/RateLimitBucket"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                },
                "examples": {
                  "toolsCallResult": {
                    "summary": "A successful tool call",
                    "value": {
                      "jsonrpc": "2.0",
                      "id": 2,
                      "result": {
                        "content": [
                          {
                            "type": "text",
                            "text": "{\"tool\":\"list_alerts\",\"data\":{\"entries\":[],\"count\":0}}"
                          }
                        ],
                        "isError": false
                      }
                    }
                  },
                  "scopeError": {
                    "summary": "The token's scopes do not allow the tool",
                    "value": {
                      "jsonrpc": "2.0",
                      "id": 2,
                      "error": {
                        "code": -32001,
                        "message": "Insufficient scope for this tool",
                        "data": {
                          "requiredScope": "alerts.read"
                        }
                      }
                    }
                  },
                  "parseError": {
                    "summary": "The body is not valid JSON",
                    "value": {
                      "jsonrpc": "2.0",
                      "id": null,
                      "error": {
                        "code": -32700,
                        "message": "Parse error"
                      }
                    }
                  }
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "One `message` event whose data is the JSON-RPC response."
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized` — the `Authorization` header is missing or the token is unknown, revoked or expired. The body is identical for every cause.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpHttpError"
                },
                "example": {
                  "error": "unauthorized"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — the MCP server is not enabled for this deployment; the body is the same as for a path that does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpHttpError"
                },
                "example": {
                  "error": "not_found"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited` — the token exceeded its request limit. Wait `Retry-After` seconds.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the token may call again."
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Bucket": {
                "$ref": "#/components/headers/RateLimitBucket"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpHttpError"
                },
                "example": {
                  "error": "rate_limited"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — an unexpected server error. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpHttpError"
                },
                "example": {
                  "error": "internal_error"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://backoffice.noxtica.com",
          "description": "Production"
        }
      ]
    },
    "/pulse/v1/tenant/subjects/{subject}": {
      "get": {
        "tags": [
          "Tenant"
        ],
        "operationId": "pulseTenantSubjectStatus",
        "summary": "Status of one player (pseudonymous subject) of the calling tenant",
        "description": "Server-to-server, scope `read:pulse`. `subject` is a `psub_…` enrolment pseudonym, the pairwise link subject `plnk_…` or the tenant's own `externalUserId` (both resolve ONLY through an ACTIVE link: after an unlink they answer `404 subject_not_found`; an `externalUserId` linked in several applications is `409 ambiguous_subject` unless `?appId=` names one). A subject resolved through a link shows only the scopes the player consented to (`status`: `enrolled`/`revokedAt`/`reasonCode`; `attestation_level`: `deviceAttestationLevel`; `last_seen`: `lastSeen`; `open_signals`: `openSignals`) and echoes them in `scopes`. The tenant is always the key's tenant: unknown subjects and other tenants' subjects are the same `404 subject_not_found`. The answer carries no account PII and no enrolment, device or key identifier. A revoked subject answers `enrolled: false` with `revokedAt` and `reasonCode`. `openSignals` are the subject's open detections (`open`, `under_review`), newest first, at most 50, with tenant-safe fields only; `[]` when there are none. `Cache-Control: no-store`. Times are epoch milliseconds.",
        "security": [
          {
            "tenantKey": [
              "read:pulse"
            ]
          }
        ],
        "parameters": [
          {
            "name": "subject",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            },
            "example": "psub_01M4GW6CR553W8AAWXEG17RDJW"
          },
          {
            "$ref": "#/components/parameters/TenantAppId"
          }
        ],
        "responses": {
          "200": {
            "description": "The subject's status.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantSubjectStatus"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_subject` (undecodable, control characters or longer than 256).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "`missing_api_key` or `invalid_api_key` (malformed, unknown, revoked, or a device token). `WWW-Authenticate: Bearer realm=\"pulse-tenant\"`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope` (the key lacks `read:pulse`) or `tenant_not_armed` (the tenant is not active and Pulse-enabled).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`subject_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`ambiguous_subject` — the `externalUserId` is linked in several applications: pass `?appId=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pulse_RateLimited"
          },
          "503": {
            "description": "`tenant_auth_unavailable` or `rate_limiter_unavailable` (fail closed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "servers": [
        {
          "url": "https://pulse.noxtica.com",
          "description": "Production (not yet deployed)"
        }
      ]
    },
    "/pulse/v1/tenant/decisions": {
      "get": {
        "tags": [
          "Tenant"
        ],
        "operationId": "pulseTenantListDecisions",
        "summary": "List the tenant's response decisions (audit), newest first",
        "description": "Server-to-server, scope `read:pulse`. Every response action the device executed, suppressed or dry-ran under the tenant's signed profile, with its reason code, the outcome of record, the linked detection's review status and appeal state, and the hint state. Keyset-paged (`cursor` from `nextCursor`). Pseudonyms (`psub`, `deviceRef`) and closed codes only: no account id, e-mail, enrolment id, evidence, profile hash or staff identity. `subject` resolves like the subject status; for a linked subject the player must have granted `open_signals` (`403 scope_not_granted`). `Cache-Control: no-store`.",
        "security": [
          {
            "tenantKey": [
              "read:pulse"
            ]
          }
        ],
        "parameters": [
          {
            "name": "app_id",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
            }
          },
          {
            "name": "device_ref",
            "in": "query",
            "description": "The `pdrf_` pseudonym of the device (server-side filter: the page and `nextCursor` only walk this device's decisions).",
            "schema": {
              "type": "string",
              "pattern": "^pdrf_[0-9A-HJKMNP-TV-Z]{26}$"
            }
          },
          {
            "name": "severity",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "low",
                "medium",
                "high",
                "critical"
              ]
            }
          },
          {
            "name": "subject",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          },
          {
            "name": "outcome",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ResponseOutcome"
            }
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ResponseAction"
            }
          },
          {
            "name": "reason_code",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{3}-[0-9]{3}$"
            }
          },
          {
            "name": "rule_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "hint_state",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "expired",
                "reversed",
                "none"
              ]
            }
          },
          {
            "name": "detection_id",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^pdet_[0-9A-HJKMNP-TV-Z]{26}$"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Epoch ms (inclusive) of the device decision.",
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Epoch ms (exclusive).",
            "schema": {
              "type": "integer",
              "format": "int64",
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 96
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of decisions.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantDecisionList"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` (a malformed filter) or `invalid_cursor`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "`missing_api_key` or `invalid_api_key` (a device token is never accepted).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope`, `tenant_not_armed` or `scope_not_granted`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`subject_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pulse_RateLimited"
          }
        }
      },
      "servers": [
        {
          "url": "https://pulse.noxtica.com",
          "description": "Production (not yet deployed)"
        }
      ]
    },
    "/pulse/v1/tenant/decisions/{id}": {
      "get": {
        "tags": [
          "Tenant"
        ],
        "operationId": "pulseTenantGetDecision",
        "summary": "One response decision",
        "description": "Scope `read:pulse`. `404 decision_not_found` for an unknown or another tenant's id (indistinguishable).",
        "security": [
          {
            "tenantKey": [
              "read:pulse"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^pact_[0-7][0-9A-HJKMNP-TV-Z]{25}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The decision.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantDecision"
                }
              }
            }
          },
          "401": {
            "description": "`missing_api_key` or `invalid_api_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope` or `tenant_not_armed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`decision_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pulse_RateLimited"
          }
        }
      },
      "servers": [
        {
          "url": "https://pulse.noxtica.com",
          "description": "Production (not yet deployed)"
        }
      ]
    },
    "/pulse/v1/tenant/decisions/{id}/reverse": {
      "post": {
        "tags": [
          "Tenant"
        ],
        "operationId": "pulseTenantReverseDecision",
        "summary": "Withdraw an active response hint",
        "description": "Scope `write:pulse`. The tenant back end marks the decision's ACTIVE hint as handled or withdrawn, with a closed `reasonCode`. The reversal is an append-only audit event (actor = the API key id) and is announced by the `pulse.response.v1` webhook (transition `reversed`). Idempotent: an already reversed hint answers `200` and writes nothing. A decision without an active hint (a dry run, a suppressed action, an expired hint) is `409 hint_not_active`.",
        "security": [
          {
            "tenantKey": [
              "write:pulse"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^pact_[0-7][0-9A-HJKMNP-TV-Z]{25}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "reasonCode"
                ],
                "properties": {
                  "reasonCode": {
                    "type": "string",
                    "enum": [
                      "handled",
                      "false_positive",
                      "account_verified",
                      "policy_exception",
                      "other"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decision with `hint.state` = `reversed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantDecision"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "`missing_api_key` or `invalid_api_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "`insufficient_scope` (needs `write:pulse`) or `tenant_not_armed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "`decision_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`hint_not_active`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "415": {
            "$ref": "#/components/responses/ApiError"
          },
          "429": {
            "$ref": "#/components/responses/pulse_RateLimited"
          }
        }
      },
      "servers": [
        {
          "url": "https://pulse.noxtica.com",
          "description": "Production (not yet deployed)"
        }
      ]
    },
    "/pulse/v1/tenant/link-codes": {
      "post": {
        "tags": [
          "Tenant"
        ],
        "operationId": "pulseTenantCreateLinkCode",
        "summary": "Issue a one-time link code for one of the tenant's own users",
        "description": "Server-to-server, scope `write:pulse`. The tenant backend (a launcher, a game server) asks for a code for ITS user `externalUserId` in one of its applications and hands the returned `url` to that person. The code is shown once, valid 10 minutes, single use; only its SHA-256 is stored. The person opens the URL signed in to their Pulse account, sees the tenant, the application, the masked user id and the data the tenant will see, and approves (the link is the consent receipt) or denies. At most 5 unconsumed codes exist per (application, `externalUserId`) (`429 too_many_outstanding_codes`, `Retry-After: 600`); 30 codes per key per minute and 600 per tenant per hour (`429 rate_limited`). A user id that already has an active link in that application is `409 external_user_already_linked` (unlink it first). `Cache-Control: no-store`.",
        "security": [
          {
            "tenantKey": [
              "write:pulse"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenantLinkCodeRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The code (shown ONCE).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantLinkCode"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_parameter` / `missing_parameter` (empty, padded, control-character or over-long `externalUserId`; malformed `appId` or `locale`), or a non-JSON body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/TenantUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/TenantForbidden"
          },
          "404": {
            "description": "`application_not_found` — not an active application of this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`external_user_already_linked`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "415": {
            "$ref": "#/components/responses/ApiError"
          },
          "429": {
            "$ref": "#/components/responses/pulse_RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/TenantUnavailable"
          }
        }
      },
      "servers": [
        {
          "url": "https://pulse.noxtica.com",
          "description": "Production (not yet deployed)"
        }
      ]
    },
    "/pulse/v1/tenant/links/{subject}": {
      "get": {
        "tags": [
          "Tenant"
        ],
        "operationId": "pulseTenantGetLink",
        "summary": "Read one link of the calling tenant",
        "description": "Scope `read:pulse`. `subject` is the pairwise `plnk_…` (resolves in EVERY state, so an unlink is observable as `state: unlinked` with `unlinkedBy`) or the tenant's own `externalUserId` (resolves only through its ACTIVE link; `409 ambiguous_subject` when it is linked in several applications unless `?appId=` names one). Another tenant's link, an unknown subject and an unlinked `externalUserId` are the same `404 subject_not_found`. The answer never carries an account id, e-mail, psub or device fact. `Cache-Control: no-store`.",
        "security": [
          {
            "tenantKey": [
              "read:pulse"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenantLinkSubject"
          },
          {
            "$ref": "#/components/parameters/TenantAppId"
          }
        ],
        "responses": {
          "200": {
            "description": "The link.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantLink"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_subject` or `invalid_parameter` (malformed `appId`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/TenantUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/TenantForbidden"
          },
          "404": {
            "description": "`subject_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`ambiguous_subject`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pulse_RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/TenantUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Tenant"
        ],
        "operationId": "pulseTenantUnlink",
        "summary": "Unlink a player (tenant side)",
        "description": "Scope `write:pulse`. Ends the link (`unlinkedBy: tenant`): the player's enrolments of that application lose the binding, the link is audited, and a `pulse.enrolment.linked.v1` webhook (`transition: unlinked`) is queued in the same transaction. Idempotent: an already unlinked link answers `200` with `changed: false` and writes nothing. Same subject forms, `?appId=` rule and `404 subject_not_found` as `GET`. The player can link again with a new code; that creates a NEW `plnk_…` subject.",
        "security": [
          {
            "tenantKey": [
              "write:pulse"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenantLinkSubject"
          },
          {
            "$ref": "#/components/parameters/TenantAppId"
          }
        ],
        "responses": {
          "200": {
            "description": "The link after the call.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantLinkUnlinked"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_subject` or `invalid_parameter`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/TenantUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/TenantForbidden"
          },
          "404": {
            "description": "`subject_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "`ambiguous_subject`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pulse_RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/TenantUnavailable"
          }
        }
      },
      "servers": [
        {
          "url": "https://pulse.noxtica.com",
          "description": "Production (not yet deployed)"
        }
      ]
    },
    "/v1/device/{deviceId}": {
      "get": {
        "operationId": "v1GetDevice",
        "tags": [
          "Devices"
        ],
        "summary": "Look up a device by ID",
        "description": "Returns the current score and risk projection of one device — the `fingerprintId` your browser or mobile integration received — scoped to the calling organisation.\n\nThe route answers `404` with `{\"error\":\"Device not found\",\"deviceId\":\"...\",\"requestId\":\"...\"}` when the device is unknown, belongs to another organisation, or has not been seen within your organisation's configured retention window.\n\n- `reasonCodes` is the published, non-personal reason-code projection of the internal flags. It is an empty array unless reason-code output is enabled for your organisation, independently of the raw `flags` field.\n- `gpuAttestationTier` is masked: a stored `hardware` or `software` tier is reported as `attested` unless publication of the hardware attestation level is enabled for your organisation. `gpuVerified` is not masked and never asserts `hardware`.\n- `subscores` and `details` are the scorer's per-domain breakdown when present, otherwise `null`. `softwareSubfp`, `hardwareSubfp` and `environmentSubfp` are server-derived observational sub-fingerprints; they are `null` for devices recorded before those fields existed.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The `device_id` to look up (URL-decoded from the path segment)."
          }
        ],
        "responses": {
          "200": {
            "description": "The device's current score/risk projection.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "device"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "device": {
                      "type": "object",
                      "properties": {
                        "deviceId": {
                          "type": "string"
                        },
                        "domainId": {
                          "type": "string"
                        },
                        "score": {
                          "type": "number"
                        },
                        "riskLevel": {
                          "type": "string"
                        },
                        "confidence": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "flags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "reasonCodes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Empty unless reason-code output is enabled for your organisation."
                        },
                        "subscores": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": true
                        },
                        "details": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": true
                        },
                        "country": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "city": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updatedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "lastSubmittedAt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "lastSeenAt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "gpuVerified": {
                          "type": "boolean"
                        },
                        "gpuAttestationTier": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "attested",
                            "suspicious",
                            "unavailable",
                            "failed",
                            "hardware",
                            "software"
                          ],
                          "description": "Masked unless publication of the hardware attestation level is enabled for your organisation; see description above."
                        },
                        "softwareSubfp": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "hardwareSubfp": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "environmentSubfp": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "device": {
                    "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                    "domainId": "dom_3f2a8c91d6e5f470",
                    "score": 1,
                    "riskLevel": "low",
                    "confidence": 1,
                    "flags": [
                      "example-flags"
                    ],
                    "reasonCodes": [
                      "example-reasonCodes"
                    ],
                    "subscores": {},
                    "details": {},
                    "country": "US",
                    "city": "Istanbul",
                    "createdAt": "2026-09-13T12:00:00.000Z",
                    "updatedAt": "2026-09-13T12:00:00.000Z",
                    "lastSubmittedAt": "2026-09-13T12:00:00.000Z",
                    "lastSeenAt": "2026-09-13T12:00:00.000Z",
                    "gpuVerified": true,
                    "gpuAttestationTier": "attested",
                    "softwareSubfp": "example-softwareSubfp",
                    "hardwareSubfp": "example-hardwareSubfp",
                    "environmentSubfp": "example-environmentSubfp"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing/empty `deviceId`, or the path segment failed URL-decoding.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "`{ error: 'Device not found', deviceId, requestId }` — tenant projection inactive/missing OR the row is absent/outside the retention window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "Device not found"
                      ]
                    },
                    "deviceId": {
                      "type": "string"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "Device not found",
                  "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/devices": {
      "get": {
        "operationId": "v1ListDevices",
        "tags": [
          "Devices"
        ],
        "summary": "List devices",
        "description": "Lists devices, most recently updated first, with filters and keyset pagination. `from` and `to` bound the range and may span at most 400 days. `source` selects real devices, synthetic devices (see the generator operations) or both; `all` merges both result sets by `(updated_at, device_id)` descending.\n\n`cursor` is opaque: pass the `nextCursor` of the previous page back unchanged and never construct one. `limit` defaults to 50 and is capped at 200.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:fingerprints"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque keyset cursor from a prior response's `nextCursor`."
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Defaults to 400 days before now."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Defaults to now."
          },
          {
            "name": "domain_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "risk_level",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by risk level; the allowed set is deps-injected (`riskLevelFilterValues`)."
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "real",
                "synthetic",
                "all"
              ],
              "default": "real"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of devices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "devices",
                    "hasMore",
                    "nextCursor"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "devices": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "deviceId": {
                            "type": "string"
                          },
                          "domainId": {
                            "type": "string"
                          },
                          "score": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "riskLevel": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "country": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "isSynthetic": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "devices": [
                    {
                      "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                      "domainId": "dom_3f2a8c91d6e5f470",
                      "score": 1,
                      "riskLevel": "low",
                      "flags": [
                        "example-flags"
                      ],
                      "country": "US",
                      "createdAt": "2026-09-13T12:00:00.000Z",
                      "updatedAt": "2026-09-13T12:00:00.000Z",
                      "isSynthetic": true
                    }
                  ],
                  "hasMore": true,
                  "nextCursor": "cursor_000123"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid source`, `Invalid risk_level`, or `Invalid date range` (bad/inverted/over-400-day `from`/`to`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "description": "`{ error: 'Failed to query devices', code: 'devices_list_query_failed' }` on a warehouse fault, or a generic internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/accounts/{accountHash}/devices": {
      "get": {
        "operationId": "v1ListAccountDevices",
        "tags": [
          "Devices"
        ],
        "summary": "List devices linked to an account",
        "description": "Lists the devices and agents linked to one of your end-user accounts, with their current score and risk only. `accountHash` must be the keyed 16-character lowercase-hex pseudonym your organisation already receives elsewhere (for example the `account_hash` filter of `GET /v1/events`); a raw account id is never accepted on this path.\n\nPagination is a keyset cursor over `(last_seen_at, id)` descending, with the same opaque cursor shape as `GET /v1/events`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:fingerprints"
            ]
          }
        ],
        "parameters": [
          {
            "name": "accountHash",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{16}$"
            },
            "description": "The keyed 16-hex-lowercase account pseudonym."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The account's linked device/agent history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "accountHash",
                    "links",
                    "hasMore",
                    "nextCursor"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "accountHash": {
                      "type": "string"
                    },
                    "links": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "agentKeyid": {
                            "type": "string"
                          },
                          "agentHost": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "deviceId": {
                            "type": "string"
                          },
                          "domainId": {
                            "type": "string"
                          },
                          "firstSeenAt": {
                            "type": "integer",
                            "description": "Epoch ms."
                          },
                          "lastSeenAt": {
                            "type": "integer",
                            "description": "Epoch ms."
                          },
                          "seenCount": {
                            "type": "integer"
                          },
                          "deviceScore": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "deviceRiskLevel": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "deviceUpdatedAt": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "accountHash": "a1b2c3d4e5f60718293a4b5c6d7e8f901a2b3c4d5e6f708192a3b4c5d6e7f89",
                  "links": [
                    {
                      "agentKeyid": "example-agentKeyid",
                      "agentHost": "example-agentHost",
                      "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                      "domainId": "dom_3f2a8c91d6e5f470",
                      "firstSeenAt": 1,
                      "lastSeenAt": 1,
                      "seenCount": 1,
                      "deviceScore": 1,
                      "deviceRiskLevel": "example-deviceRiskLevel",
                      "deviceUpdatedAt": "example-deviceUpdatedAt"
                    }
                  ],
                  "hasMore": true,
                  "nextCursor": "cursor_000123"
                }
              }
            }
          },
          "400": {
            "description": "`accountHash` fails the 16-hex-lowercase shape check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "description": "`{ error: 'Failed to query account devices', code: 'account_devices_query_failed' }` or a generic internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/events": {
      "get": {
        "operationId": "v1ListEvents",
        "tags": [
          "Events & Visits"
        ],
        "summary": "List scoring events",
        "description": "Lists scored events, newest first, with a keyset cursor over `(created_at, id)`. The optional `account_hash` filter (a keyed 16-character lowercase-hex pseudonym) applies to `source=real` only: synthetic events and the merged `all` view carry no `account_hash`.\n\n`reasonCodes` is the published reason-code projection of the event's internal flags; it is empty unless reason-code output is enabled for your organisation.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:events"
            ]
          }
        ],
        "parameters": [
          {
            "name": "device_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "domain_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account_hash",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{16}$"
            },
            "description": "Applied only when `source` resolves to `real`."
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "real",
                "synthetic",
                "all"
              ],
              "default": "real"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "events",
                    "hasMore",
                    "nextCursor"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "deviceId": {
                            "type": "string"
                          },
                          "domainId": {
                            "type": "string"
                          },
                          "score": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "riskLevel": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "confidence": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "flags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "reasonCodes": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "country": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "payloadUnavailable": {
                            "type": [
                              "boolean",
                              "null"
                            ]
                          },
                          "isSynthetic": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "events": [
                    {
                      "id": "example-id",
                      "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                      "domainId": "dom_3f2a8c91d6e5f470",
                      "score": 1,
                      "riskLevel": "low",
                      "confidence": 1,
                      "flags": [
                        "example-flags"
                      ],
                      "reasonCodes": [
                        "example-reasonCodes"
                      ],
                      "country": "US",
                      "createdAt": "2026-09-13T12:00:00.000Z",
                      "payloadUnavailable": true,
                      "isSynthetic": true
                    }
                  ],
                  "hasMore": true,
                  "nextCursor": "cursor_000123"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid source`, or `account_hash` fails the 16-hex-lowercase shape check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/visits": {
      "get": {
        "operationId": "v1ListVisits",
        "tags": [
          "Events & Visits"
        ],
        "summary": "List visits",
        "description": "Lists visit records, newest first, with a keyset cursor over `(created_at, id)`. Visits have no synthetic partition, so this route has no `source` filter. `meta` is the visit's own parsed JSON metadata and includes `external_user_id` when the submitting page set it.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:visits"
            ]
          }
        ],
        "parameters": [
          {
            "name": "device_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "domain_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_user_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of visits.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "visits",
                    "hasMore",
                    "nextCursor"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "visits": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "deviceId": {
                            "type": "string"
                          },
                          "domainId": {
                            "type": "string"
                          },
                          "country": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "city": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "meta": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "The row's own parsed JSON `meta` blob (e.g. `external_user_id`); shape is submitter-controlled and not locally validated."
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "visits": [
                    {
                      "id": "example-id",
                      "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                      "domainId": "dom_3f2a8c91d6e5f470",
                      "country": "US",
                      "city": "Istanbul",
                      "meta": {},
                      "createdAt": "2026-09-13T12:00:00.000Z"
                    }
                  ],
                  "hasMore": true,
                  "nextCursor": "cursor_000123"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/exports/fingerprints": {
      "get": {
        "operationId": "v1ExportFingerprints",
        "tags": [
          "Exports"
        ],
        "summary": "Export fingerprints as CSV or JSON",
        "description": "Bulk-exports device fingerprint records for a date range. `from` and `to` are required and are validated for both shape and calendar (a malformed string or an impossible date such as `2026-02-30` is rejected) before being echoed into the CSV `Content-Disposition` filename.\n\nEach request returns at most a fixed maximum number of rows. A capped page sets `truncated: true` (JSON) or the `X-Noxtica-Export-Truncated: true` header (CSV) and an opaque keyset `next_cursor` (JSON) or `X-Noxtica-Export-Next-Cursor` header (CSV) over `(created_at, id)` descending; request the next page with that cursor.\n\n`reason_codes` is the published reason-code projection (empty unless reason-code output is enabled for your organisation) and is the last CSV column, so fixed-position consumers are unaffected.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:export"
            ]
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "REQUIRED. `YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS`, or ISO 8601 (optional ms/offset)."
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "domain_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "real",
                "synthetic",
                "all"
              ],
              "default": "real"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque keyset cursor from a prior response's `next_cursor`. Omit for page 1."
          }
        ],
        "responses": {
          "200": {
            "description": "The exported page, as a JSON envelope or a raw CSV body depending on `format`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "count",
                    "limit",
                    "truncated",
                    "from",
                    "to",
                    "fingerprints"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean"
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    },
                    "next_cursor": {
                      "type": "string"
                    },
                    "fingerprints": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "device_id": {
                            "type": "string"
                          },
                          "domain_id": {
                            "type": "string"
                          },
                          "score": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "risk_level": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "flags": {
                            "type": "string",
                            "description": "Semicolon-joined."
                          },
                          "reason_codes": {
                            "type": "string",
                            "description": "Semicolon-joined; empty unless reason-code output is enabled for your organisation."
                          },
                          "country": {
                            "type": "string"
                          },
                          "city": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "is_synthetic": {
                            "type": "string",
                            "enum": [
                              "true",
                              "false"
                            ]
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "count": 1,
                  "limit": 1,
                  "truncated": true,
                  "from": "example-from",
                  "to": "example-to",
                  "next_cursor": "cursor_000123",
                  "fingerprints": [
                    {
                      "device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                      "domain_id": "dom_3f2a8c91d6e5f470",
                      "score": 1,
                      "risk_level": "low",
                      "flags": "example-flags",
                      "reason_codes": "example-reason_codes",
                      "country": "US",
                      "city": "Istanbul",
                      "created_at": "2026-09-13T12:00:00.000Z",
                      "updated_at": "2026-09-13T12:00:00.000Z",
                      "is_synthetic": "true"
                    }
                  ]
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "example-v1ExportFingerprints"
              }
            },
            "headers": {
              "X-Noxtica-Export-Truncated": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              },
              "X-Noxtica-Export-Next-Cursor": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid source`/`Invalid format`, missing `from`/`to`, or `from and to must be YYYY-MM-DD or ISO 8601 datetime values`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/exports/events": {
      "get": {
        "operationId": "v1ExportEvents",
        "tags": [
          "Exports"
        ],
        "summary": "Export events as CSV or JSON",
        "description": "Bulk-exports scored events for a date range. The contract is identical to `GET /v1/exports/fingerprints`: the same date validation, per-request row cap, and `truncated` / `next_cursor` pagination.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:export"
            ]
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "domain_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "real",
                "synthetic",
                "all"
              ],
              "default": "real"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The exported page, as a JSON envelope or a raw CSV body depending on `format`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "count",
                    "limit",
                    "truncated",
                    "from",
                    "to",
                    "events"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean"
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    },
                    "next_cursor": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "device_id": {
                            "type": "string"
                          },
                          "domain_id": {
                            "type": "string"
                          },
                          "score": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "risk_level": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "reason_codes": {
                            "type": "string",
                            "description": "Semicolon-joined; empty unless reason-code output is enabled for your organisation."
                          },
                          "country": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "is_synthetic": {
                            "type": "string",
                            "enum": [
                              "true",
                              "false"
                            ]
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "count": 1,
                  "limit": 1,
                  "truncated": true,
                  "from": "example-from",
                  "to": "example-to",
                  "next_cursor": "cursor_000123",
                  "events": [
                    {
                      "id": "example-id",
                      "device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                      "domain_id": "dom_3f2a8c91d6e5f470",
                      "score": 1,
                      "risk_level": "low",
                      "reason_codes": "example-reason_codes",
                      "country": "US",
                      "created_at": "2026-09-13T12:00:00.000Z",
                      "is_synthetic": "true"
                    }
                  ]
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "example-v1ExportEvents"
              }
            },
            "headers": {
              "X-Noxtica-Export-Truncated": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              },
              "X-Noxtica-Export-Next-Cursor": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid source`/`Invalid format`, missing `from`/`to`, or invalid date shape/calendar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/exports/visits": {
      "get": {
        "operationId": "v1ExportVisits",
        "tags": [
          "Exports"
        ],
        "summary": "Export visits as CSV or JSON",
        "description": "Bulk-exports visit records for a date range, with the same date validation, per-request row cap and pagination as the other exports. Visits have no synthetic partition, so there is no `source` parameter.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:export"
            ]
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "domain_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The exported page, as a JSON envelope or a raw CSV body depending on `format`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "count",
                    "limit",
                    "truncated",
                    "from",
                    "to",
                    "visits"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean"
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    },
                    "next_cursor": {
                      "type": "string"
                    },
                    "visits": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "device_id": {
                            "type": "string"
                          },
                          "domain_id": {
                            "type": "string"
                          },
                          "country": {
                            "type": "string"
                          },
                          "city": {
                            "type": "string"
                          },
                          "external_user_id": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "count": 1,
                  "limit": 1,
                  "truncated": true,
                  "from": "example-from",
                  "to": "example-to",
                  "next_cursor": "cursor_000123",
                  "visits": [
                    {
                      "id": "example-id",
                      "device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                      "domain_id": "dom_3f2a8c91d6e5f470",
                      "country": "US",
                      "city": "Istanbul",
                      "external_user_id": "example-external_user_id",
                      "created_at": "2026-09-13T12:00:00.000Z"
                    }
                  ]
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "example-v1ExportVisits"
              }
            },
            "headers": {
              "X-Noxtica-Export-Truncated": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              },
              "X-Noxtica-Export-Next-Cursor": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid format`, missing `from`/`to`, or invalid date shape/calendar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/exports/scored-events": {
      "get": {
        "operationId": "v1ExportScoredEvents",
        "tags": [
          "Exports"
        ],
        "summary": "Stream scored events as JSON Lines",
        "description": "Streams scored events as newline-delimited JSON, newest first: one scored-event object per line, followed by a trailing `_summary` line. The stream is capped at a fixed maximum number of rows per request.\n\n- `tier` is coarser than `gpuAttestationTier` on `GET /v1/device/{deviceId}`: only a hardware-attested device maps to `attested`; everything else is `suspicious`.\n- `band` is one of `critical`, `high`, `medium`, `low`, `minimal`; an unrecognised stored value is reported as `minimal`.\n- `reason_codes` holds at most 64 entries and is empty unless reason-code output is enabled for your organisation.\n\nThis route validates the shape of `from` and `to` only, not calendar validity, unlike the JSON/CSV exports.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:export"
            ]
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "REQUIRED. `YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS`, or ISO 8601."
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "domain_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque keyset cursor from a prior response's `next_cursor` (in the trailing `_summary` line). No cursor param on page 1."
          }
        ],
        "responses": {
          "200": {
            "description": "JSONL body: one scored-event object per line, then a trailing `_summary` line.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "type": "string",
                  "description": "Newline-delimited JSON. Each event line: `{id, device_id, domain_id, score, tier, band, confidence, reason_codes, created_at}`. The final line is always `{_summary: true, count, from, to, explainabilityEnabled, limit, truncated, next_cursor?}`."
                },
                "example": "example-v1ExportScoredEvents"
              }
            },
            "headers": {
              "X-Noxtica-Export-Truncated": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              },
              "X-Noxtica-Export-Next-Cursor": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Missing `from`/`to`, or a value not matching the accepted date shape (this route validates SHAPE only, not calendar validity, unlike the sibling JSON/CSV exports).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/emerging-threats": {
      "get": {
        "operationId": "v1GetEmergingThreats",
        "tags": [
          "Exports"
        ],
        "summary": "Read reason-code firing counts",
        "description": "Returns per-reason-code firing counts and rates over a bounded lookback window, aggregated from the published reason-code catalogue. It reuses the `read:export` scope.\n\nThe report is disabled unless it is enabled for the deployment. When it is, the route does no warehouse work and answers a neutral `200` with `{\"available\":false,\"reason\":\"feature_unavailable\",\"signals\":[]}`, so you can always probe it safely. A warehouse read fault degrades to an empty `signals` array and `window_sessions: 0`; this route never answers `500` for it.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:export"
            ]
          }
        ],
        "parameters": [
          {
            "name": "window",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                1,
                6,
                24,
                168,
                720
              ],
              "default": 24
            },
            "description": "Lookback hours; any other value silently falls back to 24."
          }
        ],
        "responses": {
          "200": {
            "description": "The firing-count report, or the feature-disabled neutral empty state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "available",
                    "window_hours",
                    "signals",
                    "count"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "available": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "feature_unavailable"
                      ]
                    },
                    "window_hours": {
                      "type": "integer"
                    },
                    "window_sessions": {
                      "type": "integer",
                      "description": "Present only when available is true."
                    },
                    "signals": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "reason_code": {
                            "type": "string"
                          },
                          "domain": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "firing_count": {
                            "type": "integer"
                          },
                          "firing_rate": {
                            "type": [
                              "number",
                              "null"
                            ],
                            "description": "`null` when `window_sessions` is below the 20-session display floor."
                          }
                        }
                      }
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "available": true,
                  "reason": "feature_unavailable",
                  "window_hours": 1,
                  "window_sessions": 1,
                  "signals": [
                    {
                      "reason_code": "example-reason_code",
                      "domain": "app.example.com",
                      "label": "example-label",
                      "firing_count": 1,
                      "firing_rate": 1
                    }
                  ],
                  "count": 1
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/sealed-results/key": {
      "get": {
        "operationId": "v1GetSealedResultsKey",
        "tags": [
          "Sealed Results"
        ],
        "summary": "Fetch the key that opens Sealed Client Results",
        "description": "Returns your organisation's symmetric key for opening Sealed Client Results envelopes. The key is derived on demand and never stored.\n\n`keyVersion` is optional. Each sealed envelope carries the cleartext `kv` (key version) it was sealed under, and this route serves any past version that has not been revoked, so rotation never orphans an archived envelope. `expiresAt` is present only when a key lifetime is configured.\n\n- `404 sealed_key_version_unavailable` — `keyVersion` is not a positive integer or is newer than the current version. No key material is returned.\n- `403` — the key lacks `read:sealed_results`, or the key is revoked for this organisation and version.\n- `500 server_misconfigured` — sealed results are not provisioned for this deployment; this is distinct from an authentication failure.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:sealed_results"
            ]
          }
        ],
        "parameters": [
          {
            "name": "keyVersion",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]{0,8}$"
            },
            "description": "Key version to fetch — the `kv` a Sealed Client Result envelope carries. Omitted = the current version (the response is unchanged). A past version is served from the server's master ring until it is REVOKED for the tenant (`403`, as for the current version). A malformed or future version answers `404 sealed_key_version_unavailable`."
          }
        ],
        "responses": {
          "200": {
            "description": "The tenant's sealed-results key material.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "keyVersion",
                    "key",
                    "alg",
                    "envelope"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "keyVersion": {
                      "type": "string"
                    },
                    "key": {
                      "type": "string",
                      "description": "base64-encoded raw derived key."
                    },
                    "alg": {
                      "type": "string"
                    },
                    "envelope": {
                      "type": "string",
                      "enum": [
                        "noxtica-sealed-v1"
                      ]
                    },
                    "expiresAt": {
                      "type": "integer",
                      "description": "Absolute epoch-ms expiry hint; present only when a TTL is configured."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "keyVersion": "example-keyVersion",
                  "key": "example-key",
                  "alg": "example-alg",
                  "envelope": "noxtica-sealed-v1",
                  "expiresAt": 1768435200
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "description": "`API key does not have read:sealed_results scope`, OR the sealed-results key is revoked for this tenant/version (`sealed-results key revoked for this tenant/version`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "description": "`sealed_key_version_unavailable` — the requested `keyVersion` is not a positive integer, or is newer than the current key version. Explicit (never a 500); no key material is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "sealed_key_version_unavailable"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "sealed-results key version unavailable",
                  "code": "sealed_key_version_unavailable",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/labels": {
      "get": {
        "operationId": "v1ListLabels",
        "tags": [
          "Labels"
        ],
        "summary": "List your organisation's ground-truth labels",
        "description": "Lists the labels your organisation has written with `POST /v1/labels`, with optional `device_id` and `label` filters and a keyset cursor over `(created_at, id)` (the same opaque cursor shape as `GET /v1/events`). It reuses the `read:device` scope; there is no separate read-labels scope.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "parameters": [
          {
            "name": "device_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "label",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "bot",
                "human",
                "fraud",
                "legit",
                "unknown"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of labels.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "labels",
                    "hasMore",
                    "nextCursor"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "labels": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "deviceId": {
                            "type": "string"
                          },
                          "eventId": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "label": {
                            "type": "string",
                            "enum": [
                              "bot",
                              "human",
                              "fraud",
                              "legit",
                              "unknown"
                            ]
                          },
                          "source": {
                            "type": "string"
                          },
                          "note": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "labels": [
                    {
                      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                      "eventId": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
                      "label": "bot",
                      "source": "example-source",
                      "note": "created via API",
                      "createdAt": "2026-09-13T12:00:00.000Z",
                      "updatedAt": "2026-09-13T12:00:00.000Z"
                    }
                  ],
                  "hasMore": true,
                  "nextCursor": "cursor_000123"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid label. Expected one of: bot, human, fraud, legit, unknown`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "post": {
        "operationId": "v1WriteLabel",
        "tags": [
          "Labels"
        ],
        "summary": "Write a ground-truth label for a device",
        "description": "Records a confirmed fraud, legitimate or other label for a device, optionally scoped to one event, to calibrate scoring. The write is an upsert: repeating the same `(device_id, event_id)` updates `label`, `source`, `note` and `updated_at` on the existing row instead of creating a duplicate.\n\nThe body accepts only `device_id`, `label`, `event_id`, `note` and `source`; any other key is rejected with `400 Unknown field`. `label` must be one of `bot`, `human`, `fraud`, `legit`, `unknown`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:labels"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "device_id",
                  "label"
                ],
                "additionalProperties": false,
                "properties": {
                  "device_id": {
                    "type": "string",
                    "maxLength": 256
                  },
                  "label": {
                    "type": "string",
                    "enum": [
                      "bot",
                      "human",
                      "fraud",
                      "legit",
                      "unknown"
                    ]
                  },
                  "event_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 256,
                    "description": "Optional; blank/whitespace-only normalizes to NULL (collapses onto the device-level idempotency slot)."
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1024
                  },
                  "source": {
                    "type": "string",
                    "maxLength": 64,
                    "default": "api",
                    "description": "Truncated to 64 chars; defaults to 'api' when omitted/blank."
                  }
                }
              },
              "example": {
                "device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                "label": "bot",
                "event_id": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
                "note": "created via API",
                "source": "api"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The upserted (inserted or updated) label row.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "label"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "label": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "deviceId": {
                          "type": "string"
                        },
                        "eventId": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "label": {
                          "type": "string",
                          "enum": [
                            "bot",
                            "human",
                            "fraud",
                            "legit",
                            "unknown"
                          ]
                        },
                        "source": {
                          "type": "string"
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updatedAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "label": {
                    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                    "eventId": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
                    "label": "bot",
                    "source": "example-source",
                    "note": "created via API",
                    "createdAt": "2026-09-13T12:00:00.000Z",
                    "updatedAt": "2026-09-13T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown body field, missing/oversized `device_id`, invalid `label`, or an oversized `event_id`/`note`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/generator/fingerprints": {
      "post": {
        "operationId": "v1GenerateFingerprintBatch",
        "tags": [
          "Generator"
        ],
        "summary": "Generate a batch of synthetic devices",
        "description": "Generates `count` synthetic devices, each with `eventsPerDevice` events, derived deterministically from real template fingerprints, for testing and load. Read them back with `source=synthetic`.\n\nThe call is idempotent on `(domain_id, seed)`: a repeated identical request returns the same existing batch with `isExisting: true` and HTTP `200`; a new batch answers `201`. `domain_id` must be an active domain of your organisation, otherwise the route answers `404`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:generate"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "domain_id",
                  "seed"
                ],
                "properties": {
                  "domain_id": {
                    "type": "string"
                  },
                  "seed": {
                    "type": "string",
                    "description": "Idempotency key for the whole batch."
                  },
                  "count": {
                    "type": "integer",
                    "default": 10,
                    "minimum": 1,
                    "maximum": 100,
                    "description": "Coerced via Number(); non-positive/non-finite falls back to 10."
                  },
                  "eventsPerDevice": {
                    "type": "integer",
                    "default": 3,
                    "minimum": 1,
                    "maximum": 10
                  },
                  "sourceFrom": {
                    "type": "string"
                  },
                  "sourceTo": {
                    "type": "string"
                  },
                  "sourceLimit": {
                    "type": "integer",
                    "default": 200,
                    "maximum": 500
                  },
                  "scrubIPs": {
                    "type": "boolean",
                    "default": true,
                    "description": "Any value other than the literal `false` is treated as true."
                  }
                }
              },
              "example": {
                "domain_id": "dom_3f2a8c91d6e5f470",
                "seed": "seed-01",
                "count": 10,
                "eventsPerDevice": 3,
                "sourceFrom": "example-sourceFrom",
                "sourceTo": "example-sourceTo",
                "sourceLimit": 200,
                "scrubIPs": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The existing batch, when `seed` already matches a committed batch for this `(tenant, domain)`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "isExisting",
                    "batchId",
                    "seed",
                    "tenantId",
                    "domainId",
                    "deviceIds",
                    "totalDevices",
                    "totalEvents",
                    "createdAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "isExisting": {
                      "type": "boolean"
                    },
                    "batchId": {
                      "type": "string"
                    },
                    "seed": {
                      "type": "string"
                    },
                    "tenantId": {
                      "type": "string"
                    },
                    "domainId": {
                      "type": "string"
                    },
                    "deviceIds": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "totalDevices": {
                      "type": "integer"
                    },
                    "totalEvents": {
                      "type": "integer"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "isExisting": true,
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "seed": "seed-01",
                  "tenantId": "tenant_42",
                  "domainId": "dom_3f2a8c91d6e5f470",
                  "deviceIds": [
                    "example-deviceIds"
                  ],
                  "totalDevices": 1,
                  "totalEvents": 1,
                  "createdAt": "2026-09-13T12:00:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "A newly-committed batch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "isExisting",
                    "batchId",
                    "seed",
                    "tenantId",
                    "domainId",
                    "deviceIds",
                    "totalDevices",
                    "totalEvents",
                    "createdAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "isExisting": {
                      "type": "boolean"
                    },
                    "batchId": {
                      "type": "string"
                    },
                    "seed": {
                      "type": "string"
                    },
                    "tenantId": {
                      "type": "string"
                    },
                    "domainId": {
                      "type": "string"
                    },
                    "deviceIds": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "totalDevices": {
                      "type": "integer"
                    },
                    "totalEvents": {
                      "type": "integer"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "isExisting": true,
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "seed": "seed-01",
                  "tenantId": "tenant_42",
                  "domainId": "dom_3f2a8c91d6e5f470",
                  "deviceIds": [
                    "example-deviceIds"
                  ],
                  "totalDevices": 1,
                  "totalEvents": 1,
                  "createdAt": "2026-09-13T12:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "`domain_id and seed are required`, or `No template fingerprints found in the specified range`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "`Domain not found or does not belong to tenant`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/generator/fingerprint": {
      "post": {
        "operationId": "v1GenerateSingleFingerprint",
        "tags": [
          "Generator"
        ],
        "summary": "Generate one seeded synthetic device",
        "description": "Generates one synthetic device from a filtered selection of real template fingerprints. It is idempotent on the derived seed key: a repeated request returns the existing device unchanged with `isExisting: true` and HTTP `200`; a new device answers `201`.\n\n`json_filters`, when present, constrain the candidate templates by JSONPath-style equality against the stored fingerprint, score and collector fields. Domain ownership, scope and body rules are the same as for `POST /v1/generator/fingerprints`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:generate"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "domain_id",
                  "seed"
                ],
                "properties": {
                  "domain_id": {
                    "type": "string"
                  },
                  "seed": {
                    "type": "string"
                  },
                  "platform": {
                    "type": "string"
                  },
                  "risk_level": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string"
                  },
                  "score_min": {
                    "type": "number"
                  },
                  "score_max": {
                    "type": "number"
                  },
                  "json_filters": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "source",
                        "path"
                      ],
                      "properties": {
                        "source": {
                          "type": "string",
                          "enum": [
                            "fingerprint",
                            "score",
                            "cf"
                          ]
                        },
                        "path": {
                          "type": "string",
                          "description": "A JSONPath-style equality filter, e.g. $.signals.webgl.vendor."
                        },
                        "value": {}
                      }
                    },
                    "description": "Normalized/hashed into the deterministic seed key alongside `seed`."
                  },
                  "sourceFrom": {
                    "type": "string"
                  },
                  "sourceTo": {
                    "type": "string"
                  },
                  "sourceLimit": {
                    "type": "integer",
                    "default": 200,
                    "maximum": 500
                  },
                  "scrubIPs": {
                    "type": "boolean",
                    "default": true
                  }
                }
              },
              "example": {
                "domain_id": "dom_3f2a8c91d6e5f470",
                "seed": "seed-01",
                "platform": "example-platform",
                "risk_level": "low",
                "country": "US",
                "language": "example-language",
                "timezone": "example-timezone",
                "score_min": 1,
                "score_max": 1,
                "json_filters": [
                  {
                    "source": "fingerprint",
                    "path": "/v1/kyc/verify",
                    "value": "example-value"
                  }
                ],
                "sourceFrom": "example-sourceFrom",
                "sourceTo": "example-sourceTo",
                "sourceLimit": 200,
                "scrubIPs": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The existing seeded fingerprint (returned unchanged, `Cache-Control: no-store`).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "isExisting",
                    "seed",
                    "filters",
                    "tenantId",
                    "domainId",
                    "batchId",
                    "deviceId",
                    "templateEventId",
                    "fingerprint",
                    "score",
                    "cf",
                    "createdAt",
                    "updatedAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "isExisting": {
                      "type": "boolean"
                    },
                    "seed": {
                      "type": "string"
                    },
                    "filters": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The normalized template filters echoed back."
                    },
                    "tenantId": {
                      "type": "string"
                    },
                    "domainId": {
                      "type": "string"
                    },
                    "batchId": {
                      "type": "string"
                    },
                    "deviceId": {
                      "type": "string"
                    },
                    "templateEventId": {
                      "type": "string"
                    },
                    "fingerprint": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The stored synthetic fingerprint blob (template-derived; shape not locally schema-validated)."
                    },
                    "score": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The stored score blob copied verbatim from the selected template."
                    },
                    "cf": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The stored Cloudflare-geo blob, IP-scrubbed when scrubIPs is true."
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "isExisting": true,
                  "seed": "seed-01",
                  "filters": {},
                  "tenantId": "tenant_42",
                  "domainId": "dom_3f2a8c91d6e5f470",
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                  "templateEventId": "example-templateEventId",
                  "fingerprint": {},
                  "score": {},
                  "cf": {},
                  "createdAt": "2026-09-13T12:00:00.000Z",
                  "updatedAt": "2026-09-13T12:00:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "A newly-committed seeded fingerprint.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "isExisting",
                    "seed",
                    "filters",
                    "tenantId",
                    "domainId",
                    "batchId",
                    "deviceId",
                    "templateEventId",
                    "fingerprint",
                    "score",
                    "cf",
                    "createdAt",
                    "updatedAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "isExisting": {
                      "type": "boolean"
                    },
                    "seed": {
                      "type": "string"
                    },
                    "filters": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The normalized template filters echoed back."
                    },
                    "tenantId": {
                      "type": "string"
                    },
                    "domainId": {
                      "type": "string"
                    },
                    "batchId": {
                      "type": "string"
                    },
                    "deviceId": {
                      "type": "string"
                    },
                    "templateEventId": {
                      "type": "string"
                    },
                    "fingerprint": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The stored synthetic fingerprint blob (template-derived; shape not locally schema-validated)."
                    },
                    "score": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The stored score blob copied verbatim from the selected template."
                    },
                    "cf": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The stored Cloudflare-geo blob, IP-scrubbed when scrubIPs is true."
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "isExisting": true,
                  "seed": "seed-01",
                  "filters": {},
                  "tenantId": "tenant_42",
                  "domainId": "dom_3f2a8c91d6e5f470",
                  "batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
                  "deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
                  "templateEventId": "example-templateEventId",
                  "fingerprint": {},
                  "score": {},
                  "cf": {},
                  "createdAt": "2026-09-13T12:00:00.000Z",
                  "updatedAt": "2026-09-13T12:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "`domain_id and seed are required`, or `No template fingerprints found matching the specified filters`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "`Domain not found or does not belong to tenant`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/verify/reputation": {
      "post": {
        "operationId": "v1VerifyReputation",
        "tags": [
          "Reputation"
        ],
        "summary": "Check email, website and phone reputation",
        "description": "Evaluates the reputation of an email address, a website and/or a phone number supplied by your backend and returns a fused verdict. At least one of `email`, `website` or `phone` is required. The checks are bounded and fail open: a check that cannot complete contributes no signal instead of failing the call.\n\nThis operation is not enabled by default. When the reputation module is not enabled for your organisation the route answers the `GatedNotFound` 404, and it does so before the API key is checked. A served call counts toward your organisation's reputation usage. Raw email and website values are not logged.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:reputation"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "At least one of `email`, `website`, `phone` is required.",
                "properties": {
                  "email": {
                    "type": "string"
                  },
                  "website": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "email": "example-email",
                "website": "example-website",
                "phone": "example-phone"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The fused reputation verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "verdict",
                    "signals",
                    "confidence"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "verdict": {
                      "type": "string"
                    },
                    "signals": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "Per-signal sub-scores from `evaluateReputation`; the engine's internal signal-shape union is not locally re-validated by this handler."
                    },
                    "confidence": {
                      "type": "number"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "verdict": "example-verdict",
                  "signals": {},
                  "confidence": 1
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/v1_InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/crawl/vouchers": {
      "post": {
        "operationId": "v1MintCrawlVoucher",
        "tags": [
          "Crawl Vouchers"
        ],
        "summary": "Mint a crawler-access voucher",
        "description": "Mints a signed, stateless voucher that a verified crawler (identified by `agentKey`) presents to your Noxtica edge to satisfy a `charge` pricing rule. The edge verifies only the signature and binding. The response's `header` field names the request header that carries the voucher.\n\nThe voucher is always scoped to the organisation that owns the API key; a tenant id in the body is never used. The route is fail-closed: it answers `503` when no signing key is available and never emits an unsigned voucher.\n\nPayment capture is not part of this operation. It does not verify that a payment was taken; it only checks that a payment reference (`paymentRef` in the body, or the payment-reference request header) is present, and only requires one when your deployment is configured to. Outstanding vouchers are capped per month, and the cap is claimed only after the voucher has been signed, so a signing failure never consumes it.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:crawl_voucher"
            ]
          }
        ],
        "parameters": [
          {
            "name": "X-Nox-Payment-Ref",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Alternative to the body's `paymentRef`; operator-attested, not independently verified this iteration."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "agentKey"
                ],
                "properties": {
                  "agentKey": {
                    "type": "string",
                    "maxLength": 256,
                    "description": "The crawler identity this voucher pays for ('*' = the catch-all pricing rule)."
                  },
                  "ttlSeconds": {
                    "type": "integer",
                    "default": 3600,
                    "minimum": 60,
                    "maximum": 86400
                  },
                  "paymentRef": {
                    "type": "string",
                    "description": "Operator-attested payment reference; presence-only checked."
                  }
                }
              },
              "example": {
                "agentKey": "example-agentKey",
                "ttlSeconds": 3600,
                "paymentRef": "example-paymentRef"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The minted voucher.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "voucher",
                    "expiresAt",
                    "header"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "voucher": {
                      "type": "string",
                      "description": "`<base64url(payload)>.<base64url(hmac)>`, or the HMAC-signing-trust envelope form when that feature is armed."
                    },
                    "expiresAt": {
                      "type": "integer",
                      "description": "Epoch seconds."
                    },
                    "header": {
                      "type": "string",
                      "enum": [
                        "X-Nox-Crawl-Voucher"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "voucher": "example-voucher",
                  "expiresAt": 1768435200,
                  "header": "X-Nox-Crawl-Voucher"
                }
              }
            }
          },
          "400": {
            "description": "`agentKey (non-empty string) is required`, or `Invalid JSON body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "402": {
            "description": "`Payment context required` — returned only when the deployment requires a payment reference and neither `paymentRef` nor the payment-reference header was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "`Crawl voucher minting is not configured` (no signing key is available) or `Voucher quota authority unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/behavioral/subjects/{subjectRef}": {
      "get": {
        "operationId": "v1GetBehavioralSubject",
        "tags": [
          "Behavioral Baseline"
        ],
        "summary": "Read a subject's behavioral baseline",
        "description": "Reads a subject's current behavioral-baseline enrollment and its latest drift snapshot. `subjectRef` is your raw end-user account id: it is never stored, logged or echoed, only keyed-hashed server-side before any lookup.\n\nNot enabled by default: the whole `/v1/behavioral/*` family answers the `GatedNotFound` 404 unless behavioral baselines are enabled for the deployment, and only then is the `read:behavioral` scope checked.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:behavioral"
            ]
          }
        ],
        "parameters": [
          {
            "name": "subjectRef",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 256
            },
            "description": "The tenant's raw end-user account id (hashed server-side; never stored as given)."
          }
        ],
        "responses": {
          "200": {
            "description": "The subject's baseline enrollment + latest drift.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "subject"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "subject": {
                      "type": "object",
                      "properties": {
                        "enrollmentStatus": {
                          "type": "string",
                          "enum": [
                            "learning",
                            "active"
                          ],
                          "description": "A persisted value outside the closed set clamps to `learning`."
                        },
                        "sampleCount": {
                          "type": "integer"
                        },
                        "attestationProvenance": {
                          "type": "object",
                          "properties": {
                            "gpu_attested": {
                              "type": "integer"
                            },
                            "sealed": {
                              "type": "integer"
                            },
                            "unattested": {
                              "type": "integer"
                            }
                          }
                        },
                        "deviation": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "families": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "pointer": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "keystroke": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "cadence": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          }
                        },
                        "lastDriftAt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "subject": {
                    "enrollmentStatus": "learning",
                    "sampleCount": 1,
                    "attestationProvenance": {
                      "gpu_attested": 1,
                      "sealed": 1,
                      "unattested": 1
                    },
                    "deviation": 1,
                    "families": {
                      "pointer": 1,
                      "keystroke": 1,
                      "cadence": 1
                    },
                    "lastDriftAt": "2026-09-13T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`Invalid subjectRef` — empty or over 256 chars.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "`Subject not found` (no current snapshot exists), or `V1 endpoint not found` when behavioral baselines are not enabled for the deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "delete": {
        "operationId": "v1EraseBehavioralSubject",
        "tags": [
          "Behavioral Baseline"
        ],
        "summary": "Erase a subject's behavioral baseline",
        "description": "Erases a subject's behavioral baseline, for data-subject erasure requests. Live profile state is erased first, then the stored snapshots and drift projections are hard-deleted. If the live-state erase fails the route answers `502` with `{\"ok\":false,\"profileErased\":false,...}` instead of a false `200`, because a surviving profile could otherwise re-flush the erased snapshot; the call is safe to retry. Erasing an unknown or already-erased subject is idempotent and answers `200 {\"deleted\":{\"snapshots\":0,\"driftEvents\":0}}`.\n\nThis erases account-scoped (`acct:`) subjects only. Device-scoped baselines and the raw behavioral tracks need the separate per-device erasure path in the Backoffice, so a subject with both needs both paths. Availability is the same as for `GET /v1/behavioral/subjects/{subjectRef}`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "erase:behavioral"
            ]
          }
        ],
        "parameters": [
          {
            "name": "subjectRef",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Erasure completed (or was already a no-op for an unknown subject).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "profileErased",
                    "deleted"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "profileErased": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "deleted": {
                      "type": "object",
                      "properties": {
                        "snapshots": {
                          "type": "integer"
                        },
                        "driftEvents": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "profileErased": true,
                  "deleted": {
                    "snapshots": 1,
                    "driftEvents": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "`Invalid subjectRef` — empty or over 256 chars.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "`Subject not found`, or `V1 endpoint not found` when behavioral baselines are not enabled for the deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "`{ ok:false, profileErased:false, deleted, error: 'behavioral profile durable-object erase failed — retry (idempotent)' }` — the erase call is safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "profileErased": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "deleted": {
                      "type": "object",
                      "properties": {
                        "snapshots": {
                          "type": "integer"
                        },
                        "driftEvents": {
                          "type": "integer"
                        }
                      }
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "profileErased": false,
                  "deleted": {
                    "snapshots": 1,
                    "driftEvents": 1
                  },
                  "error": "invalid_request"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/behavioral/subjects/{subjectRef}/timeline": {
      "get": {
        "operationId": "v1GetBehavioralSubjectTimeline",
        "tags": [
          "Behavioral Baseline"
        ],
        "summary": "Read a subject's drift-event history",
        "description": "Reads a subject's drift events, newest first. The row cap is clamped to 1–100 and there is no pagination cursor on this route. `subjectRef` handling and availability are the same as for `GET /v1/behavioral/subjects/{subjectRef}`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:behavioral"
            ]
          }
        ],
        "parameters": [
          {
            "name": "subjectRef",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The subject's drift-event timeline.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "events",
                    "count"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "deviation": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "families": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "pointer": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "keystroke": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "cadence": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              }
                            }
                          },
                          "attestation": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "channel": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "events": [
                    {
                      "deviation": 1,
                      "families": {
                        "pointer": 1,
                        "keystroke": 1,
                        "cadence": 1
                      },
                      "attestation": "example-attestation",
                      "channel": "example-channel",
                      "createdAt": "2026-09-13T12:00:00.000Z"
                    }
                  ],
                  "count": 1
                }
              }
            }
          },
          "400": {
            "description": "`Invalid subjectRef` — empty or over 256 chars.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "`Subject not found`, or `V1 endpoint not found` when behavioral baselines are not enabled for the deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/agent/passport": {
      "get": {
        "operationId": "getAgentPassport",
        "tags": [
          "Agent Trust"
        ],
        "summary": "Issue a signed Agent Passport (GET)",
        "description": "Runs the same Web Bot Auth verdict as `POST /v1/agent/verify` (RFC 9421 signature verification plus your organisation's agent policy registry) and returns one ES256-signed, offline-verifiable envelope. Verify the signature against the published JWKS (`GET /.well-known/noxtica/agent-passport-jwks.json`, falling back to `/.well-known/noxtica/jwks.json`) by `kid`; no round trip to Noxtica is needed.\n\nThe GET form reads only the three Web Bot Auth headers of the incoming request (`signature-input`, `signature`, `signature-agent`) and an optional forwarded attestation from the query parameters `tier`, `anchored`, `riskBand` and `jti`. `sub` is set only for a cryptographically `verified` verdict; every other verdict carries `sub: null`. An unverified `allow` is withheld or downgraded before signing.\n\n**Availability.** Agent Passports are not enabled by default. When they are not enabled for the deployment the route answers `404 {\"error\":\"Not found\"}`, a different body from the usual `V1 endpoint not found`; when your organisation has not opted into agent verification it answers `403 {\"error\":\"feature disabled\"}`. The route shares its rate-limit bucket with `POST /v1/agent/verify`, and a verified issuance counts toward your agent-verification usage.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "The signed target URI `@authority` is reconstructed from; degrades to the request's own URL when absent/malformed."
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "hardware",
                "software",
                "suspicious",
                "unavailable",
                "failed"
              ]
            },
            "description": "Optional caller-forwarded attestation tier; a non-member value is silently dropped (fail-open)."
          },
          {
            "name": "anchored",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "Optional caller-forwarded \"server-anchored single-use claim succeeded\" flag; only the literal string `true` is honoured."
          },
          {
            "name": "riskBand",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "critical",
                "high",
                "medium",
                "low",
                "minimal"
              ]
            }
          },
          {
            "name": "jti",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "OPTIONAL caller-forwarded device-correlation id, echoed back as the passport's untrusted `cid` (never the server-minted replay `jti`)."
          }
        ],
        "responses": {
          "200": {
            "description": "The ES256-signed Agent Passport envelope (`SignedEnvelope<AgentPassportPayload>`). A tenant backend verifies this OFFLINE against the published JWKS by `kid` — no round-trip to Noxtica is required.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "alg",
                    "kid",
                    "iat",
                    "payload",
                    "sig"
                  ],
                  "properties": {
                    "alg": {
                      "type": "string",
                      "enum": [
                        "ES256"
                      ]
                    },
                    "kid": {
                      "type": "string"
                    },
                    "iat": {
                      "type": "integer",
                      "description": "Epoch seconds."
                    },
                    "exp": {
                      "type": "integer",
                      "description": "Epoch seconds; the signed expiry window (== the payload `ttl`)."
                    },
                    "kv": {
                      "type": "integer",
                      "description": "OPTIONAL signing key-version; present only when the trust-lifecycle signer is in use."
                    },
                    "spv": {
                      "type": "integer",
                      "description": "OPTIONAL signing-policy-version; same presence contract as `kv`."
                    },
                    "sig": {
                      "type": "string",
                      "description": "base64url ES256 signature over the payload."
                    },
                    "payload": {
                      "type": "object",
                      "required": [
                        "typ",
                        "iss",
                        "aud",
                        "pv",
                        "verdict",
                        "attestation",
                        "riskBand",
                        "policyAction",
                        "jti",
                        "sub",
                        "tenantId",
                        "cid",
                        "disclosure",
                        "issuedAt",
                        "ttl"
                      ],
                      "properties": {
                        "typ": {
                          "type": "string",
                          "enum": [
                            "agent-passport"
                          ]
                        },
                        "iss": {
                          "type": "string",
                          "enum": [
                            "noxtica"
                          ]
                        },
                        "aud": {
                          "type": "string",
                          "description": "The authenticated tenant id (audience binding)."
                        },
                        "pv": {
                          "type": "integer",
                          "enum": [
                            1,
                            2
                          ],
                          "description": "Passport schema version: 1 = legacy claims, 2 = explicit `callerAsserted` trust marker (when enabled for the deployment)."
                        },
                        "verdict": {
                          "type": "string",
                          "enum": [
                            "verified",
                            "unsigned",
                            "invalid_signature",
                            "expired",
                            "replay"
                          ]
                        },
                        "attestation": {
                          "type": "object",
                          "properties": {
                            "tier": {
                              "type": "string",
                              "enum": [
                                "hardware",
                                "software",
                                "suspicious",
                                "unavailable",
                                "failed"
                              ]
                            },
                            "anchored": {
                              "type": "boolean"
                            },
                            "provenance": {
                              "type": "string",
                              "enum": [
                                "caller_attested"
                              ]
                            }
                          }
                        },
                        "callerAsserted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ],
                          "description": "Present only when `pv:2`."
                        },
                        "riskBand": {
                          "type": "string",
                          "enum": [
                            "critical",
                            "high",
                            "medium",
                            "low",
                            "minimal"
                          ]
                        },
                        "policyAction": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "allow",
                            "deny"
                          ]
                        },
                        "graduatedAction": {
                          "type": "string",
                          "enum": [
                            "allow",
                            "observe",
                            "challenge",
                            "deny"
                          ],
                          "description": "Present only when graduated agent policy is enabled for your organisation."
                        },
                        "jti": {
                          "type": "string",
                          "format": "uuid",
                          "description": "SERVER-MINTED per-issuance id — the consumer MUST reject a repeated `jti` (replay)."
                        },
                        "sub": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The verified subject (keyid, else the signature-agent host); null unless verdict is 'verified'."
                        },
                        "tenantId": {
                          "type": "string"
                        },
                        "cid": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "OPTIONAL untrusted caller-forwarded correlation id — never the replay id."
                        },
                        "disclosure": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "required": {
                              "type": "boolean",
                              "enum": [
                                true
                              ]
                            },
                            "regulation": {
                              "type": "string",
                              "enum": [
                                "eu_ai_act_art_50"
                              ]
                            },
                            "obligation": {
                              "type": "string"
                            },
                            "agentVerified": {
                              "type": "boolean"
                            }
                          }
                        },
                        "issuedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "ttl": {
                          "type": "integer",
                          "description": "Seconds; the signed envelope `exp` window."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "alg": "ES256",
                  "kid": "kid-2026-09",
                  "iat": 1,
                  "exp": 1,
                  "kv": 1,
                  "spv": 1,
                  "sig": "example-sig",
                  "payload": {
                    "typ": "agent-passport",
                    "iss": "noxtica",
                    "aud": "example-aud",
                    "pv": 1,
                    "verdict": "verified",
                    "attestation": {
                      "tier": "hardware",
                      "anchored": true,
                      "provenance": "caller_attested"
                    },
                    "callerAsserted": true,
                    "riskBand": "critical",
                    "policyAction": "allow",
                    "graduatedAction": "allow",
                    "jti": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "sub": "example-sub",
                    "tenantId": "tenant_42",
                    "cid": "example-cid",
                    "disclosure": {
                      "required": true,
                      "regulation": "eu_ai_act_art_50",
                      "obligation": "example-obligation",
                      "agentVerified": true
                    },
                    "issuedAt": "2026-09-13T12:00:00.000Z",
                    "ttl": 1
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "description": "Your organisation has not opted into agent verification (`feature disabled`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "feature disabled"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "feature disabled",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "description": "Agent Passports are not enabled for this deployment. The body is `{\"error\":\"Not found\"}`, returned before the API key is checked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "Not found"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "Not found",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "description": "Unexpected fault; the handler otherwise never throws.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "description": "The signing key is unavailable or invalid (fail-closed: there is no silent fallback to another key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "post": {
        "operationId": "postAgentPassport",
        "tags": [
          "Agent Trust"
        ],
        "summary": "Issue a signed Agent Passport (POST)",
        "description": "Same verdict and signing pipeline as `GET /v1/agent/passport`; the POST form lets your backend forward the signature material in the JSON body instead of request headers, like `POST /v1/agent/verify`. The same availability rules, error responses, rate-limit bucket and usage counting apply.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "headers": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "The forwarded Web Bot Auth signature headers (`signature-input`, `signature`, `signature-agent`, lower-cased keys); non-string values are dropped."
                  },
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "The signed target URI; a malformed value degrades to the request's own URL."
                  },
                  "method": {
                    "type": "string",
                    "description": "The signed HTTP method the signature covers; defaults to `GET` when omitted/blank."
                  },
                  "attestation": {
                    "type": "object",
                    "properties": {
                      "tier": {
                        "type": "string",
                        "enum": [
                          "hardware",
                          "software",
                          "suspicious",
                          "unavailable",
                          "failed"
                        ]
                      },
                      "anchored": {
                        "type": "boolean"
                      }
                    }
                  },
                  "riskBand": {
                    "type": "string",
                    "enum": [
                      "critical",
                      "high",
                      "medium",
                      "low",
                      "minimal"
                    ]
                  },
                  "jti": {
                    "type": "string",
                    "description": "OPTIONAL caller-forwarded device-correlation id, echoed back as the passport's untrusted `cid`."
                  }
                }
              },
              "example": {
                "headers": {
                  "key": "example-headers"
                },
                "url": "https://verify.noxtica.com/s/kycs.tenant_42.9f3a8b7c6d5e4f3a2b1c0d9e8f7a6b5c",
                "method": "example-method",
                "attestation": {
                  "tier": "hardware",
                  "anchored": true
                },
                "riskBand": "critical",
                "jti": "example-jti"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The ES256-signed Agent Passport envelope (`SignedEnvelope<AgentPassportPayload>`). A tenant backend verifies this OFFLINE against the published JWKS by `kid` — no round-trip to Noxtica is required.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "alg",
                    "kid",
                    "iat",
                    "payload",
                    "sig"
                  ],
                  "properties": {
                    "alg": {
                      "type": "string",
                      "enum": [
                        "ES256"
                      ]
                    },
                    "kid": {
                      "type": "string"
                    },
                    "iat": {
                      "type": "integer",
                      "description": "Epoch seconds."
                    },
                    "exp": {
                      "type": "integer",
                      "description": "Epoch seconds; the signed expiry window (== the payload `ttl`)."
                    },
                    "kv": {
                      "type": "integer",
                      "description": "OPTIONAL signing key-version; present only when the trust-lifecycle signer is in use."
                    },
                    "spv": {
                      "type": "integer",
                      "description": "OPTIONAL signing-policy-version; same presence contract as `kv`."
                    },
                    "sig": {
                      "type": "string",
                      "description": "base64url ES256 signature over the payload."
                    },
                    "payload": {
                      "type": "object",
                      "required": [
                        "typ",
                        "iss",
                        "aud",
                        "pv",
                        "verdict",
                        "attestation",
                        "riskBand",
                        "policyAction",
                        "jti",
                        "sub",
                        "tenantId",
                        "cid",
                        "disclosure",
                        "issuedAt",
                        "ttl"
                      ],
                      "properties": {
                        "typ": {
                          "type": "string",
                          "enum": [
                            "agent-passport"
                          ]
                        },
                        "iss": {
                          "type": "string",
                          "enum": [
                            "noxtica"
                          ]
                        },
                        "aud": {
                          "type": "string",
                          "description": "The authenticated tenant id (audience binding)."
                        },
                        "pv": {
                          "type": "integer",
                          "enum": [
                            1,
                            2
                          ],
                          "description": "Passport schema version: 1 = legacy claims, 2 = explicit `callerAsserted` trust marker (when enabled for the deployment)."
                        },
                        "verdict": {
                          "type": "string",
                          "enum": [
                            "verified",
                            "unsigned",
                            "invalid_signature",
                            "expired",
                            "replay"
                          ]
                        },
                        "attestation": {
                          "type": "object",
                          "properties": {
                            "tier": {
                              "type": "string",
                              "enum": [
                                "hardware",
                                "software",
                                "suspicious",
                                "unavailable",
                                "failed"
                              ]
                            },
                            "anchored": {
                              "type": "boolean"
                            },
                            "provenance": {
                              "type": "string",
                              "enum": [
                                "caller_attested"
                              ]
                            }
                          }
                        },
                        "callerAsserted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ],
                          "description": "Present only when `pv:2`."
                        },
                        "riskBand": {
                          "type": "string",
                          "enum": [
                            "critical",
                            "high",
                            "medium",
                            "low",
                            "minimal"
                          ]
                        },
                        "policyAction": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "allow",
                            "deny"
                          ]
                        },
                        "graduatedAction": {
                          "type": "string",
                          "enum": [
                            "allow",
                            "observe",
                            "challenge",
                            "deny"
                          ],
                          "description": "Present only when graduated agent policy is enabled for your organisation."
                        },
                        "jti": {
                          "type": "string",
                          "format": "uuid",
                          "description": "SERVER-MINTED per-issuance id — the consumer MUST reject a repeated `jti` (replay)."
                        },
                        "sub": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The verified subject (keyid, else the signature-agent host); null unless verdict is 'verified'."
                        },
                        "tenantId": {
                          "type": "string"
                        },
                        "cid": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "OPTIONAL untrusted caller-forwarded correlation id — never the replay id."
                        },
                        "disclosure": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "required": {
                              "type": "boolean",
                              "enum": [
                                true
                              ]
                            },
                            "regulation": {
                              "type": "string",
                              "enum": [
                                "eu_ai_act_art_50"
                              ]
                            },
                            "obligation": {
                              "type": "string"
                            },
                            "agentVerified": {
                              "type": "boolean"
                            }
                          }
                        },
                        "issuedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "ttl": {
                          "type": "integer",
                          "description": "Seconds; the signed envelope `exp` window."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "alg": "ES256",
                  "kid": "kid-2026-09",
                  "iat": 1,
                  "exp": 1,
                  "kv": 1,
                  "spv": 1,
                  "sig": "example-sig",
                  "payload": {
                    "typ": "agent-passport",
                    "iss": "noxtica",
                    "aud": "example-aud",
                    "pv": 1,
                    "verdict": "verified",
                    "attestation": {
                      "tier": "hardware",
                      "anchored": true,
                      "provenance": "caller_attested"
                    },
                    "callerAsserted": true,
                    "riskBand": "critical",
                    "policyAction": "allow",
                    "graduatedAction": "allow",
                    "jti": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "sub": "example-sub",
                    "tenantId": "tenant_42",
                    "cid": "example-cid",
                    "disclosure": {
                      "required": true,
                      "regulation": "eu_ai_act_art_50",
                      "obligation": "example-obligation",
                      "agentVerified": true
                    },
                    "issuedAt": "2026-09-13T12:00:00.000Z",
                    "ttl": 1
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "description": "Your organisation has not opted into agent verification (`feature disabled`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "feature disabled"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "feature disabled",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "description": "Agent Passports are not enabled for this deployment. The body is `{\"error\":\"Not found\"}`, returned before the API key is checked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "Not found"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "Not found",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "description": "`Payload too large` — the request body exceeded the size limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "description": "Unexpected fault.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "description": "The signing key is unavailable or invalid (fail-closed: there is no silent fallback to another key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/agent/verify": {
      "post": {
        "operationId": "postAgentVerify",
        "tags": [
          "Agent Trust"
        ],
        "summary": "Verify a Web Bot Auth agent signature",
        "description": "Your backend forwards an incoming request's signature headers and target URL. Noxtica verifies the Web Bot Auth (RFC 9421) signature and joins the verdict to your organisation's agent policy registry for an allow or deny decision.\n\nVerification fails open end to end: any fault yields verdict `unsigned` and `matchedAction: null`. The endpoint is advisory and never blocks an agent itself. A structurally matched but unverified identity can never receive a binary `allow` (it is withheld to `null`), and a graduated `allow` is downgraded to `challenge`.\n\nSome response fields are additive and appear only when the matching capability is enabled for your organisation: `kya` and `policyAction` (the graduated ladder) and `disclosure` (EU AI Act Article 50 disclosure, only for a detected or verified agent).",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "headers"
                ],
                "properties": {
                  "headers": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "The forwarded Web Bot Auth signature headers (`signature-input`, `signature`, `signature-agent`)."
                  },
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "The signed target URI the signature covers."
                  },
                  "attestation": {
                    "type": "object",
                    "properties": {
                      "tier": {
                        "type": "string",
                        "enum": [
                          "hardware",
                          "software",
                          "suspicious",
                          "unavailable",
                          "failed"
                        ]
                      },
                      "anchored": {
                        "type": "boolean"
                      }
                    }
                  },
                  "riskBand": {
                    "type": "string",
                    "enum": [
                      "critical",
                      "high",
                      "medium",
                      "low",
                      "minimal"
                    ]
                  },
                  "jti": {
                    "type": "string",
                    "description": "Device-correlated token id; when graduated agent policy is enabled for your organisation and it has registry rows, it completes an `agent_attestation_chain` link."
                  }
                }
              },
              "example": {
                "headers": {
                  "key": "example-headers"
                },
                "url": "https://verify.noxtica.com/s/kycs.tenant_42.9f3a8b7c6d5e4f3a2b1c0d9e8f7a6b5c",
                "attestation": {
                  "tier": "hardware",
                  "anchored": true
                },
                "riskBand": "critical",
                "jti": "example-jti"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A resolved Web Bot Auth verdict plus the KYA registry join.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "verdict",
                    "keyid",
                    "signatureAgent",
                    "matchedAction"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "verdict": {
                      "type": "string",
                      "enum": [
                        "verified",
                        "unsigned",
                        "invalid_signature",
                        "expired",
                        "replay"
                      ]
                    },
                    "keyid": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "signatureAgent": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "matchedAction": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "allow",
                        "deny"
                      ]
                    },
                    "attestation": {
                      "type": "object",
                      "description": "Present ONLY when the caller forwarded a valid `tier`.",
                      "properties": {
                        "tier": {
                          "type": "string",
                          "enum": [
                            "hardware",
                            "software",
                            "suspicious",
                            "unavailable",
                            "failed"
                          ]
                        },
                        "anchored": {
                          "type": "boolean"
                        },
                        "provenance": {
                          "type": "string",
                          "enum": [
                            "caller_attested"
                          ],
                          "description": "Always `caller_attested` — this handler never independently corroborates the forwarded tier."
                        }
                      }
                    },
                    "riskBand": {
                      "type": "string",
                      "enum": [
                        "critical",
                        "high",
                        "medium",
                        "low",
                        "minimal"
                      ]
                    },
                    "kya": {
                      "type": "object",
                      "description": "Present only when graduated agent policy is enabled for your organisation.",
                      "properties": {
                        "verified": {
                          "type": "boolean"
                        },
                        "agentId": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "source": {
                          "type": "string",
                          "enum": [
                            "web-bot-auth",
                            "skyfire"
                          ]
                        },
                        "attestationTier": {
                          "type": "string",
                          "enum": [
                            "hardware",
                            "software",
                            "suspicious",
                            "unavailable",
                            "failed"
                          ]
                        },
                        "attestationTierProvenance": {
                          "type": "string",
                          "enum": [
                            "caller_attested"
                          ]
                        },
                        "cfVerified": {
                          "type": "boolean",
                          "description": "Present ONLY on a Cloudflare Verified AI Agents match."
                        },
                        "cfVerifiedName": {
                          "type": "string"
                        },
                        "trustCaveat": {
                          "type": "string",
                          "enum": [
                            "structural_only_signature_unverified"
                          ],
                          "description": "Present ONLY when the identity came from the Skyfire interop adapter."
                        }
                      }
                    },
                    "disclosure": {
                      "type": "object",
                      "description": "Present only when EU AI Act disclosure is enabled for your organisation and a detected or verified agent is present.",
                      "properties": {
                        "required": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "regulation": {
                          "type": "string",
                          "enum": [
                            "eu_ai_act_art_50"
                          ]
                        },
                        "obligation": {
                          "type": "string"
                        },
                        "agentVerified": {
                          "type": "boolean"
                        }
                      }
                    },
                    "policyAction": {
                      "type": "string",
                      "enum": [
                        "allow",
                        "observe",
                        "challenge",
                        "deny"
                      ],
                      "description": "Present only when graduated agent policy is enabled for your organisation and an attributable identity exists — the advisory graduated enforcement tier (Noxtica never acts on it)."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "verdict": "verified",
                  "keyid": "example-keyid",
                  "signatureAgent": "example-signatureAgent",
                  "matchedAction": "allow",
                  "attestation": {
                    "tier": "hardware",
                    "anchored": true,
                    "provenance": "caller_attested"
                  },
                  "riskBand": "critical",
                  "kya": {
                    "verified": true,
                    "agentId": "example-agentId",
                    "source": "web-bot-auth",
                    "attestationTier": "hardware",
                    "attestationTierProvenance": "caller_attested",
                    "cfVerified": true,
                    "cfVerifiedName": "example-cfVerifiedName",
                    "trustCaveat": "structural_only_signature_unverified"
                  },
                  "disclosure": {
                    "required": true,
                    "regulation": "eu_ai_act_art_50",
                    "obligation": "example-obligation",
                    "agentVerified": true
                  },
                  "policyAction": "allow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/v1_InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/ip/reputation": {
      "post": {
        "operationId": "postIpReputation",
        "tags": [
          "IP & Risk Scoring"
        ],
        "summary": "Look up IP reputation",
        "description": "Returns the reputation verdict `{category, source, confidence}` for one IP address supplied by your backend, from the same feed Noxtica uses to score its own submissions. When the address is not listed the response is `listed: false`; an unlisted address, an empty feed or a lookup fault never produces an error.\n\nThe queried IP is echoed back in the response, which is your own input. It is not stored or logged unless your organisation has opted into raw-IP storage; only the derived verdict is retained. A served call counts toward your organisation's IP-reputation usage.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ip"
                ],
                "properties": {
                  "ip": {
                    "type": "string",
                    "description": "An IPv4 dotted-quad or IPv6 address."
                  }
                }
              },
              "example": {
                "ip": "example-ip"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The derived reputation verdict, or `listed:false` when the engine has no signal.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "ip",
                    "listed"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "ip": {
                      "type": "string",
                      "description": "Echoed back verbatim (the caller's own query input)."
                    },
                    "listed": {
                      "type": "boolean"
                    },
                    "verdict": {
                      "type": "object",
                      "description": "Present ONLY when `listed` is true.",
                      "properties": {
                        "category": {
                          "type": "string"
                        },
                        "source": {
                          "type": "string"
                        },
                        "confidence": {
                          "type": "number"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "ip": "example-ip",
                  "listed": true,
                  "verdict": {
                    "category": "example-category",
                    "source": "example-source",
                    "confidence": 1
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/v1_InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/score": {
      "post": {
        "operationId": "postScore",
        "tags": [
          "IP & Risk Scoring"
        ],
        "summary": "Score a signals payload",
        "description": "Scores an already-collected `signals` payload from your backend and returns the headline score, confidence, tier and flags that the Noxtica collector computes for its own submissions. Call it from a server with an `sk_` key that carries the `read:device` scope (the default scope of every key); never from a browser.\n\n**Request.** Send either the complete enriched payload `{\"signals\": {...}, \"serverSideSignals\": {...}, ...}` or, for older callers, the inner `signals` object on its own, which is wrapped once. The body may be up to 256 KiB. The raw payload is not logged.\n\n**Response.** `score` is the engine's score, `tier` its risk band and `flags` the flags that fired. `confidence` is an integer from 0 to 100 (50 is neutral). `attestation` is always `null` on this route: the payload is supplied by the caller and cannot be trusted as hardware evidence, so no hardware-verification claim is ever made.\n\n**Allowance.** Calls within your plan's monthly Score API allowance are included. Beyond it the score is still served and the response carries a `nudge` object with `code: free_tier_exceeded`; the score is never withheld.\n\n**Errors.** `400` `Invalid body` when the body is not a JSON object or is larger than 256 KiB, and `Invalid signals` when `signals` is present but is not an object; `401`, `403`, `429` and `500` as on every key-authenticated route. No `413` is returned: an oversized body is a `400`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The documented form is the complete enriched payload (`{ signals: {...}, serverSideSignals?: {...}, ... }`) submit scores; a legacy caller MAY instead POST the inner `signals` object directly and it is wrapped exactly once.",
                "additionalProperties": true
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The re-scored verdict, mapped onto the public 0-100 confidence scale.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "score",
                    "confidence",
                    "tier",
                    "flags",
                    "attestation"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "score": {
                      "type": "number"
                    },
                    "confidence": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100,
                      "description": "Normalised to 0-100 (the engine's native value is 0-1), matching `/v1/attest/verify`'s scale."
                    },
                    "tier": {
                      "type": "string",
                      "description": "The engine's `riskLevel` band."
                    },
                    "flags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "attestation": {
                      "type": "null",
                      "description": "ALWAYS `null` on this surface — see the honesty-gate note above."
                    },
                    "nudge": {
                      "type": "object",
                      "description": "Present ONLY once the free monthly allotment is exceeded — informational only, never withholds the score.",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "free_tier_exceeded"
                          ]
                        },
                        "message": {
                          "type": "string"
                        },
                        "free_serving_allotment": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "score": 1,
                  "confidence": 0,
                  "tier": "example-tier",
                  "flags": [
                    "example-flags"
                  ],
                  "attestation": null,
                  "nudge": {
                    "code": "free_tier_exceeded",
                    "message": "example-message",
                    "free_serving_allotment": 1
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/v1_InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/protected-actions/verify": {
      "post": {
        "operationId": "postProtectedActionsVerify",
        "tags": [
          "Protected Actions"
        ],
        "summary": "Redeem a protected-action request id",
        "description": "Call this from your backend before letting a protected action (login, sign-up, checkout, ...) proceed. Send the `requestId` your browser integration obtained for the action: the resolved value of `noxticaClient.protect(name)`, the hidden `nox_request_id` input of a `data-nox-protect` form, or `form.dataset.noxRequestId` / the `noxtica:protected` event of a script-submitted form. Use an `sk_` key with the `read:device` scope, the default scope of every key.\n\n**Single use inside a 300-second window.** The first valid call consumes the receipt and answers `200` with `consumed: true`. A replay inside the window is `409 already_consumed`. An unknown, expired or other-organisation id is `404 request_not_found`, and after 300 seconds every id is `404`.\n\n**Binding.** `action` is required: an id minted for `login` never satisfies a `checkout` check (`422 action_mismatch`; the receipt is not consumed). `expectedDomain` (a hostname, origin or URL) additionally binds the page that ran the action (`422 domain_mismatch`; not consumed). A request that arrives without a `requestId` (a fail-open timeout, a blocked script) means \"no client signal\", never a pass.\n\n**Result.** `verdict` is the risk band (`minimal`, `low`, `medium`, `high`, `critical`); `score` (higher is riskier) and `confidence` (0 to 1) are the values the browser received, and this server-to-server call always returns them. The request body is limited to 2048 bytes. A `500` carries no `ok` field and never consumes the receipt.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "requestId",
                  "action"
                ],
                "properties": {
                  "requestId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The protected submission's request id."
                  },
                  "action": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_-]{0,31}$",
                    "description": "The protected action name the tenant expects (`login`, `checkout`, ...)."
                  },
                  "expectedDomain": {
                    "type": "string",
                    "maxLength": 269,
                    "description": "Optional hostname/origin/URL the action must have run on."
                  }
                }
              },
              "example": {
                "requestId": "8f1c2a52-6a6e-4b0e-9d52-2a7c1d9e4b10",
                "action": "login",
                "expectedDomain": "www.example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The receipt was valid and has now been consumed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "requestId",
                    "action",
                    "fingerprintId",
                    "verdict",
                    "score",
                    "confidence",
                    "domain",
                    "issuedAt",
                    "expiresAt",
                    "consumed",
                    "consumedAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "requestId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "action": {
                      "type": "string"
                    },
                    "fingerprintId": {
                      "type": "string",
                      "description": "The device id of the protected submission (the same value the dashboard keys on)."
                    },
                    "verdict": {
                      "type": "string",
                      "enum": [
                        "minimal",
                        "low",
                        "medium",
                        "high",
                        "critical"
                      ]
                    },
                    "score": {
                      "type": "number"
                    },
                    "confidence": {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 1
                    },
                    "domain": {
                      "type": "string",
                      "description": "Hostname of the page that ran the action."
                    },
                    "issuedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "consumed": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "This call consumed the receipt."
                    },
                    "consumedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "requestId": "8f1c2a52-6a6e-4b0e-9d52-2a7c1d9e4b10",
                  "action": "login",
                  "fingerprintId": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
                  "verdict": "low",
                  "score": 12,
                  "confidence": 0.91,
                  "domain": "www.example.com",
                  "issuedAt": "2026-10-08T10:00:00.000Z",
                  "expiresAt": "2026-10-08T10:05:00.000Z",
                  "consumed": true,
                  "consumedAt": "2026-10-08T10:00:02.000Z"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_body`, `invalid_request_id`, `invalid_action` or `invalid_expected_domain`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedActionVerifyError"
                },
                "example": {
                  "ok": false,
                  "error": "invalid_request_id",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "`request_not_found` — unknown, expired (older than 300 s) or minted for another tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedActionVerifyError"
                },
                "example": {
                  "ok": false,
                  "error": "request_not_found",
                  "requestId": "req-test"
                }
              }
            }
          },
          "409": {
            "description": "`already_consumed` — the receipt was already redeemed (replay inside the window).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedActionVerifyError"
                },
                "example": {
                  "ok": false,
                  "error": "already_consumed",
                  "consumed": true,
                  "consumedAt": "2026-10-08T10:00:02.000Z",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large` — over the 2048-byte cap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedActionVerifyError"
                },
                "example": {
                  "ok": false,
                  "error": "payload_too_large",
                  "requestId": "req-test"
                }
              }
            }
          },
          "422": {
            "description": "`action_mismatch` or `domain_mismatch` — the receipt is NOT consumed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedActionVerifyError"
                },
                "example": {
                  "ok": false,
                  "error": "action_mismatch",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/attest/android": {
      "post": {
        "operationId": "postAttestAndroid",
        "tags": [
          "Device Attestation"
        ],
        "summary": "Android Play Integrity attestation (mint / verify)",
        "description": "Server-to-server relay for Android Play Integrity attestation. Your backend — typically the backend-for-frontend a mobile app authenticates to — calls this route with an `sk_` key; the mobile app never calls it directly. Two actions are supported:\n\n1. `{\"action\":\"mint\"}` returns a single-use nonce, valid for 300 seconds and bound to your organisation and domain, that the app binds a Play Integrity standard token to.\n2. `{\"action\":\"verify\",\"nonce\":\"...\",\"token\":\"...\"}` (the older field name `integrityToken` is also accepted) claims the nonce exactly once, verifies the token with Google's Play Integrity service, records the result and returns the verdict. A `risk.flagged` webhook may be sent to subscribed endpoints.\n\n`{\"action\":\"assert\"}` is iOS-only and is refused on this route with the `GatedNotFound` 404. When verification with Google cannot complete, the verifier fails closed to `verificationLevel: \"unverified\"`: the result is stored but no verification credit is implied, and an unverified result is never silently upgraded.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "mint",
                      "verify"
                    ],
                    "default": "verify"
                  },
                  "domain_id": {
                    "type": "string",
                    "description": "OPTIONAL tenant-owned domain the nonce/claim is bound to."
                  },
                  "nonce": {
                    "type": "string",
                    "description": "REQUIRED for `verify` — the nonce returned by a prior `mint`."
                  },
                  "token": {
                    "type": "string",
                    "description": "REQUIRED for `verify` — the Play Integrity STANDARD token (also accepted under the legacy key `integrityToken`)."
                  }
                }
              },
              "example": {
                "action": "mint",
                "domain_id": "dom_3f2a8c91d6e5f470",
                "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                "token": "tok_9f2a1c3e5d7b4a6f8091c2d3e4f5a6b7"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The mint or verify outcome. A mint returns `{ok,nonce,ttlSeconds,platform}`; a verify returns `{ok,verdict,verificationLevel,platform,attestationId?}` (`attestationId` present only once a PostgreSQL authority row is recorded; `verdict:'error'` signals a recording fault, not a failed proof). IMPORTANT: `ok:true` is only the envelope-level \"request handled\" flag for BOTH mint and verify — it is present, and true, even when a `verify` call correctly REJECTS a forged/invalid token (fail-closed discipline; this route never returns a 4xx for a bad token). It is never a statement about the attestation's outcome. To determine pass/fail, check `verdict` (only ever `'pass'` when `verificationLevel` is `'verified'`) and `verificationLevel`, never `ok`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "platform"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Envelope-only \"request handled\" flag — always true on this 200 path for BOTH mint and a correctly-refused verify. NOT a verdict; check `verdict`/`verificationLevel` for the actual attestation outcome."
                    },
                    "platform": {
                      "type": "string",
                      "enum": [
                        "android"
                      ]
                    },
                    "nonce": {
                      "type": "string"
                    },
                    "ttlSeconds": {
                      "type": "integer"
                    },
                    "verdict": {
                      "type": "string",
                      "enum": [
                        "pass",
                        "fail",
                        "error",
                        "indeterminate"
                      ]
                    },
                    "verificationLevel": {
                      "type": "string",
                      "description": "e.g. `verified` / `unverified`."
                    },
                    "attestationId": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "platform": "android",
                  "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                  "ttlSeconds": 3600,
                  "verdict": "pass",
                  "verificationLevel": "verified",
                  "attestationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                }
              }
            }
          },
          "400": {
            "description": "`Missing nonce / token`, `Invalid Content-Length header`, or `Invalid JSON body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "`Could not mint challenge` — the tenant edge authority / single-use token store is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/attest/ios": {
      "post": {
        "operationId": "postAttestIos",
        "tags": [
          "Device Attestation"
        ],
        "summary": "iOS App Attest attestation (mint / verify / assert)",
        "description": "Server-to-server relay for iOS App Attest, with the same posture as `POST /v1/attest/android`: your backend calls it with an `sk_` key and the app never does. Three actions are supported:\n\n1. `{\"action\":\"mint\"}` returns a single-use nonce, valid for 300 seconds, that the app generates an App Attest key and attestation over.\n2. `{\"action\":\"verify\",\"nonce\":\"...\",\"token\":\"...\"}` (the older field name `attestationObject` is also accepted) parses the attestation object, checks the nonce binding, records the result, may send a `risk.flagged` webhook and returns the verdict. Verification currently covers structure, `rpIdHash` and nonce binding (`verificationLevel: \"structure_only\"`); promotion to `verified` additionally requires certificate-chain validation against pinned Apple intermediate key material, which is available only when it is configured for the deployment.\n3. `{\"action\":\"assert\",\"nonce\":\"...\",\"assertion\":\"...\",\"keyId\":\"...\"}` is a destructive account-deletion possession proof, available only when it is enabled for your organisation (otherwise the `GatedNotFound` 404). It verifies an App Attest assertion against the stored enrollment binding for `keyId`, enforces a monotonic sign counter (replay defence) and on success returns the bound `subjectId` and the fixed `actionScope`. The nonce is consumed even when the assertion fails.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "mint",
                      "verify",
                      "assert"
                    ],
                    "default": "verify"
                  },
                  "domain_id": {
                    "type": "string"
                  },
                  "nonce": {
                    "type": "string",
                    "description": "REQUIRED for `verify`/`assert` — the nonce returned by a prior `mint`."
                  },
                  "token": {
                    "type": "string",
                    "description": "REQUIRED for `verify` — the base64 CBOR App Attest attestation object (also accepted under the legacy key `attestationObject`)."
                  },
                  "assertion": {
                    "type": "string",
                    "description": "REQUIRED for `assert` — the base64url App Attest assertion CBOR."
                  },
                  "keyId": {
                    "type": "string",
                    "description": "REQUIRED for `assert` — the App Attest key id from the prior enrollment (`verify`) response."
                  }
                }
              },
              "example": {
                "action": "mint",
                "domain_id": "dom_3f2a8c91d6e5f470",
                "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                "token": "tok_9f2a1c3e5d7b4a6f8091c2d3e4f5a6b7",
                "assertion": "example-assertion",
                "keyId": "example-keyId"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The mint / verify / assert outcome. A successful `assert` additionally carries `subjectId` (server-resolved from the stored binding — never request-derived). IMPORTANT: `ok:true` is only the envelope-level \"request handled\" flag for mint/verify/assert alike — it is present, and true, even when a `verify` call correctly REJECTS a forged/invalid attestation object (fail-closed discipline; this route never returns a 4xx for a bad token). It is never a statement about the attestation's outcome. To determine pass/fail, check `verdict` (only ever `'pass'` when `verificationLevel` is `'verified'`) and `verificationLevel`, never `ok`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "platform"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Envelope-only \"request handled\" flag — always true on this 200 path for mint/verify/assert alike, including a correctly-refused verify. NOT a verdict; check `verdict`/`verificationLevel` for the actual attestation outcome."
                    },
                    "platform": {
                      "type": "string",
                      "enum": [
                        "ios"
                      ]
                    },
                    "nonce": {
                      "type": "string"
                    },
                    "ttlSeconds": {
                      "type": "integer"
                    },
                    "verdict": {
                      "type": "string",
                      "enum": [
                        "pass",
                        "fail",
                        "error",
                        "indeterminate"
                      ]
                    },
                    "verificationLevel": {
                      "type": "string"
                    },
                    "attestationId": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "platform": "ios",
                  "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                  "ttlSeconds": 3600,
                  "verdict": "pass",
                  "verificationLevel": "verified",
                  "attestationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                }
              }
            }
          },
          "400": {
            "description": "`Missing nonce / token`, `Missing nonce / assertion / keyId`, `Invalid Content-Length header`, or `Invalid JSON body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "description": "`Could not mint challenge`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/attest/verify": {
      "post": {
        "operationId": "postAttestVerify",
        "tags": [
          "Device Attestation"
        ],
        "summary": "Verify a CPU/GPU attestation proof",
        "description": "Returns a synchronous verdict for a one-off CPU/GPU proof-of-work attestation, driven by your backend on behalf of its web client outside the normal `/client/submit` flow. `{\"action\":\"verify\",\"challengeId\":\"...\",\"proof\":{...}}` redeems a challenge minted by `POST /v1/attest/challenge`.\n\nNot enabled by default: when the capability is not enabled for your organisation this route does not answer 404: it fails open to a neutral HTTP 200. When the capability is not enabled, or on any internal fault, the response is `{\"ok\":true,\"featureEnabled\":false,\"tier\":\"unavailable\",\"confidence\":50,\"anchored\":false}`. \"No signal\" is always a pass-through, never an outage.\n\nAbsence is not a penalty: a proof that reports no CPU/GPU capability scores neutral. Only a proof that is present but rejected (after a successful single-use nonce claim) is a `failed` signal, and every rejected proof collapses to the same public reason `verification_rejected`. The literal tier `hardware` is published only when publication of the hardware attestation level is enabled for your organisation; otherwise `hardware` and `software` are both reported as `attested`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "verify"
                    ],
                    "default": "verify",
                    "description": "This path always defaults/forces `verify`; use `POST /v1/attest/challenge` to mint."
                  },
                  "domain_id": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "gpu",
                      "cpu"
                    ],
                    "default": "gpu"
                  },
                  "challengeId": {
                    "type": "string",
                    "description": "REQUIRED — the `challengeId` returned by a prior `POST /v1/attest/challenge`."
                  },
                  "proof": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The CPU/GPU attestation proof payload narrowed by `narrowProof` against the obsidian VM's engine-internal proof shape. Left open (`additionalProperties: true`) — the exact engine field set (`adapterInfo`, ULP samples, raw multichannel evidence, etc.) is internal wire format with no independent public contract and is not safely documented from the handler alone."
                  }
                }
              },
              "example": {
                "action": "verify",
                "domain_id": "dom_3f2a8c91d6e5f470",
                "kind": "gpu",
                "challengeId": "chal_3c9b2f7e4a3d4c8e",
                "proof": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Either the NEUTRAL feature-off/fail-open band, or a scored verdict with the M3-masked published tier.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "featureEnabled",
                    "tier",
                    "confidence",
                    "anchored",
                    "reasons",
                    "verifiedAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "featureEnabled": {
                      "type": "boolean"
                    },
                    "tier": {
                      "type": "string",
                      "enum": [
                        "hardware",
                        "software",
                        "attested",
                        "suspicious",
                        "failed",
                        "unavailable"
                      ]
                    },
                    "confidence": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    },
                    "anchored": {
                      "type": "boolean"
                    },
                    "reasons": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "verifiedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "featureEnabled": true,
                  "tier": "hardware",
                  "confidence": 0,
                  "anchored": true,
                  "reasons": [
                    "example-reasons"
                  ],
                  "verifiedAt": "2026-09-13T12:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid Content-Length header` or `Invalid JSON body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/attest/challenge": {
      "post": {
        "operationId": "postAttestChallenge",
        "tags": [
          "Device Attestation"
        ],
        "summary": "Mint a CPU/GPU attestation challenge",
        "description": "Mints a single-use, organisation- and domain-bound, origin-anchored challenge and returns the directive your client feeds to the attestation runtime. The URL path forces `action: \"challenge\"` whatever the body says. Redeem the returned `challengeId` at `POST /v1/attest/verify`.\n\nThe availability and fail-open posture are the same as for `POST /v1/attest/verify`: a missing `Origin` header, an authority fault or a missing integrity pin all collapse to `{\"ok\":true,\"featureEnabled\":true,\"challenge\":null,\"reason\":\"challenge_unavailable\"}` with HTTP 200, never a 5xx.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "domain_id": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "gpu",
                      "cpu"
                    ],
                    "default": "gpu"
                  }
                }
              },
              "example": {
                "domain_id": "dom_3f2a8c91d6e5f470",
                "kind": "gpu"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A minted challenge directive, or the neutral `challenge_unavailable` refusal.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "featureEnabled",
                    "verifiedAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "featureEnabled": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "challenge_unavailable",
                        "attest_verify_disabled",
                        "attest_verify_tenant_disabled"
                      ]
                    },
                    "challenge": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "challengeId": {
                          "type": "string"
                        },
                        "nonce": {
                          "type": "string"
                        },
                        "loaderUrl": {
                          "type": "string",
                          "format": "uri"
                        },
                        "wasmUrl": {
                          "type": "string",
                          "format": "uri"
                        },
                        "wasmSha256": {
                          "type": "string"
                        },
                        "seed": {
                          "type": "string"
                        },
                        "level": {
                          "type": "integer"
                        },
                        "rawEvidenceCommitmentVersion": {
                          "type": "integer"
                        },
                        "expiresAt": {
                          "type": "integer",
                          "description": "Epoch milliseconds."
                        }
                      }
                    },
                    "verifiedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "featureEnabled": true,
                  "reason": "challenge_unavailable",
                  "challenge": {
                    "challengeId": "chal_3c9b2f7e4a3d4c8e",
                    "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                    "loaderUrl": "https://example.com",
                    "wasmUrl": "https://example.com",
                    "wasmSha256": "example-wasmSha256",
                    "seed": "seed-01",
                    "level": 1,
                    "rawEvidenceCommitmentVersion": 1,
                    "expiresAt": 1768435200
                  },
                  "verifiedAt": "2026-09-13T12:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid Content-Length header` or `Invalid JSON body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/integrity/verify": {
      "post": {
        "operationId": "postIntegrityVerify",
        "tags": [
          "Web/App Integrity"
        ],
        "summary": "Combined web/webview integrity verdict",
        "description": "Combines three existing signals into one integrity verdict for a web or webview session; it adds no new scoring path: (1) the CPU/GPU attestation verdict, from an optional `challengeId` / `proof` pair where absence is neutral, (2) the calibrated risk score over a caller-supplied `signals` payload, and (3) the virtualization-corroboration marker read from the score's flags.\n\nNot enabled by default. When the capability is not enabled for your organisation, or on any fault, the route fails open to a neutral HTTP 200 `{\"ok\":true,\"integrity\":\"unverified\",\"signals\":{\"attestation\":\"unavailable\",\"virtualization\":\"unavailable\",\"score\":\"minimal\"},\"confidence\":50}`; it never answers 5xx or 404. The `attestation` label is masked exactly as on `POST /v1/attest/verify`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "domain_id": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "gpu",
                      "cpu"
                    ],
                    "default": "gpu"
                  },
                  "challengeId": {
                    "type": "string",
                    "description": "OPTIONAL — a prior `/v1/attest/challenge` id; absence skips the attestation leg (neutral)."
                  },
                  "proof": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "OPTIONAL CPU/GPU attestation proof, same internal wire shape as `/v1/attest/verify`'s `proof` — left open for the same reason."
                  },
                  "signals": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The caller-supplied calibrated-score signals payload (same lenient envelope as `/v1/score` — accepts `{signals:{...}}` or a bare object)."
                  }
                }
              },
              "example": {
                "domain_id": "dom_3f2a8c91d6e5f470",
                "kind": "gpu",
                "challengeId": "chal_3c9b2f7e4a3d4c8e",
                "proof": {},
                "signals": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The combined integrity verdict, or the neutral feature-off/fail-open band.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "featureEnabled",
                    "integrity",
                    "signals",
                    "confidence",
                    "verifiedAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "featureEnabled": {
                      "type": "boolean"
                    },
                    "integrity": {
                      "type": "string",
                      "enum": [
                        "trusted",
                        "suspicious",
                        "unverified"
                      ]
                    },
                    "signals": {
                      "type": "object",
                      "properties": {
                        "attestation": {
                          "type": "string",
                          "enum": [
                            "hardware",
                            "software",
                            "attested",
                            "suspicious",
                            "failed",
                            "unavailable"
                          ]
                        },
                        "virtualization": {
                          "type": "string",
                          "enum": [
                            "corroborated",
                            "unavailable"
                          ]
                        },
                        "score": {
                          "type": "string",
                          "description": "The calibrated engine's `riskLevel` band."
                        }
                      }
                    },
                    "confidence": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    },
                    "reason": {
                      "type": "string",
                      "description": "Present only on the neutral/feature-off band (e.g. noxtrust_disabled, noxtrust_tenant_disabled, tenant_db_unavailable, score_fault_neutral, internal_neutral)."
                    },
                    "verifiedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "featureEnabled": true,
                  "integrity": "trusted",
                  "signals": {
                    "attestation": "hardware",
                    "virtualization": "corroborated",
                    "score": "example-score"
                  },
                  "confidence": 0,
                  "reason": "user_requested",
                  "verifiedAt": "2026-09-13T12:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid Content-Length header`, `Invalid JSON body`, or `Invalid body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`Content-Type must be application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/app-config": {
      "post": {
        "operationId": "postAppConfig",
        "tags": [
          "Mobile App Config"
        ],
        "summary": "Forced-update and kill-switch decision for an app launch",
        "description": "Server-to-server call from your backend (for example the backend-for-frontend a mobile app authenticates to) that decides one launch outcome for a mobile app. Send `platform`, `appVersion` and, on Android, the client-asserted Play Integrity `appRecognitionVerdict`. The first rule that applies wins:\n\n1. `reason: \"killed\"` — an emergency kill switch is active.\n2. `reason: \"forced_update\"` — `appVersion` is below the minimum version configured for the platform. This is a forced-update nudge, not an anti-tamper control: a patched app can report any version.\n3. `reason: \"app_not_recognized\"` — Android only, when `appRecognitionVerdict` is not `PLAY_RECOGNIZED`; a missing verdict counts as not recognised. This is advisory: the verdict is client-asserted and is not verified with Google, unlike `/v1/attest/android`. iOS is exempt.\n\nOtherwise `reason: \"ok\"`. Every decision is audited with coarse, non-personal facts. The request body is limited to 1024 bytes.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:device"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platform",
                  "appVersion"
                ],
                "properties": {
                  "platform": {
                    "type": "string",
                    "enum": [
                      "android",
                      "ios"
                    ]
                  },
                  "appVersion": {
                    "type": "string"
                  },
                  "appRecognitionVerdict": {
                    "type": "string",
                    "description": "Client-asserted Play Integrity app-recognition verdict (e.g. `PLAY_RECOGNIZED`); Android only, ADVISORY, never independently verified."
                  }
                }
              },
              "example": {
                "platform": "android",
                "appVersion": "example-appVersion",
                "appRecognitionVerdict": "example-appRecognitionVerdict"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resolved forced-update / kill-switch decision.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "allowed",
                    "reason",
                    "minVersion"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "allowed": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "killed",
                        "forced_update",
                        "app_not_recognized"
                      ]
                    },
                    "minVersion": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The operator-configured floor for this platform, or `null` when unset."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "allowed": true,
                  "reason": "ok",
                  "minVersion": "example-minVersion"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid body`, `Invalid or missing platform`, `Invalid or missing appVersion`, or `Invalid appRecognitionVerdict`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "413": {
            "description": "`Payload too large` — over the 1024-byte cap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/passkey/challenge": {
      "post": {
        "operationId": "postPasskeyChallenge",
        "tags": [
          "Passkey"
        ],
        "summary": "Mint a WebAuthn ceremony challenge",
        "description": "Server-to-server (`sk_` key, scope `write:passkey`). Mints a single-use, time-limited WebAuthn ceremony challenge. You supply an opaque `subject_id`, the exact `domain_id` of one of your active domains and the ceremony `purpose` (`register` or `assert`). The subject's existence is deliberately not checked at mint time. The domain must resolve to an active route of your organisation, and its origin is bound into the challenge, which prevents a later cross-domain ceremony.\n\nNot enabled by default: when the passkey capability is not enabled for your organisation, every passkey endpoint answers the `GatedNotFound` 404.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:passkey"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_id",
                  "domain_id",
                  "purpose"
                ],
                "properties": {
                  "subject_id": {
                    "type": "string",
                    "description": "Opaque caller-generated subject correlation id."
                  },
                  "domain_id": {
                    "type": "string",
                    "description": "The tenant's exact registered domain id this ceremony is bound to."
                  },
                  "purpose": {
                    "type": "string",
                    "enum": [
                      "register",
                      "assert"
                    ]
                  }
                }
              },
              "example": {
                "subject_id": "subj_4821",
                "domain_id": "dom_3f2a8c91d6e5f470",
                "purpose": "register"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A minted challenge plus the WebAuthn RP/ceremony parameters, or a closed `ok:false` refusal.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "challenge",
                        "purpose",
                        "ttlSeconds",
                        "rp",
                        "pubKeyCredParams"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "challenge": {
                          "type": "string"
                        },
                        "purpose": {
                          "type": "string",
                          "enum": [
                            "register",
                            "assert"
                          ]
                        },
                        "ttlSeconds": {
                          "type": "integer"
                        },
                        "rp": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "description": "The WebAuthn `rpId` derived from the tenant's registered origin."
                            },
                            "origins": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "format": "uri"
                              }
                            }
                          }
                        },
                        "pubKeyCredParams": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string",
                                "enum": [
                                  "public-key"
                                ]
                              },
                              "alg": {
                                "type": "integer",
                                "description": "COSE algorithm id (ES256 -7 or RS256 -257)."
                              }
                            }
                          }
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "reason"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        },
                        "reason": {
                          "type": "string",
                          "enum": [
                            "invalid_request",
                            "purpose_mismatch",
                            "client_data_invalid",
                            "challenge_invalid",
                            "challenge_mismatch",
                            "origin_not_allowed",
                            "rp_id_mismatch",
                            "user_not_present",
                            "user_not_verified",
                            "attestation_invalid",
                            "attestation_signature_invalid",
                            "unsupported_algorithm",
                            "assertion_signature_invalid",
                            "sign_count_regression",
                            "subject_unverified",
                            "credential_exists",
                            "credential_unknown",
                            "storage_error",
                            "step_up_unconfigured",
                            "step_up_transport_error",
                            "step_up_provider_error",
                            "step_up_no_match",
                            "step_up_reference_required",
                            "aaguid_blocked",
                            "attestation_required",
                            "attestation_untrusted",
                            "aaguid_status_rejected",
                            "step_up_reference_replayed",
                            "step_up_claim_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "ok": true,
                  "challenge": "Y2hhbGxlbmdlLW5vbmNlLTAx",
                  "purpose": "register",
                  "ttlSeconds": 3600,
                  "rp": {
                    "id": "example-id",
                    "origins": [
                      "https://example.com"
                    ]
                  },
                  "pubKeyCredParams": [
                    {
                      "type": "public-key",
                      "alg": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`{\"ok\":false,\"reason\":\"invalid_request\"}` — malformed body, missing fields, or an unresolvable `domain_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "reason"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "invalid_request",
                        "purpose_mismatch",
                        "client_data_invalid",
                        "challenge_invalid",
                        "challenge_mismatch",
                        "origin_not_allowed",
                        "rp_id_mismatch",
                        "user_not_present",
                        "user_not_verified",
                        "attestation_invalid",
                        "attestation_signature_invalid",
                        "unsupported_algorithm",
                        "assertion_signature_invalid",
                        "sign_count_regression",
                        "subject_unverified",
                        "credential_exists",
                        "credential_unknown",
                        "storage_error",
                        "step_up_unconfigured",
                        "step_up_transport_error",
                        "step_up_provider_error",
                        "step_up_no_match",
                        "step_up_reference_required",
                        "aaguid_blocked",
                        "attestation_required",
                        "attestation_untrusted",
                        "aaguid_status_rejected",
                        "step_up_reference_replayed",
                        "step_up_claim_unavailable"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "invalid_request"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/passkey/register": {
      "post": {
        "operationId": "postPasskeyRegister",
        "tags": [
          "Passkey"
        ],
        "summary": "Verify a WebAuthn attestation and register a passkey",
        "description": "Server-to-server (`sk_` key, scope `write:passkey`). Verifies a WebAuthn attestation and registers the credential. In order: the `register` challenge is consumed (single use); the bound subject must have an active, verified device or KYC proof; the attestation is verified against only the exact active domain bound into the challenge; an optional authenticator (AAGUID) trust policy may reject the credential before storage when it runs in enforce mode; the credential is stored against the opaque `subject_id`, with no personal data.\n\nEvery ceremony or policy refusal returns the same closed reason-code catalogue at a uniform HTTP 200 `{\"ok\":false}`, except `invalid_request` and `purpose_mismatch`, which answer 400, so a status code never reveals whether a credential exists. Availability is the same as for `POST /v1/passkey/challenge`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:passkey"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "challenge",
                  "attestationObject",
                  "clientDataJSON"
                ],
                "properties": {
                  "challenge": {
                    "type": "string",
                    "description": "base64url — the challenge minted by `POST /v1/passkey/challenge` with `purpose:'register'`."
                  },
                  "attestationObject": {
                    "type": "string",
                    "description": "base64url CBOR WebAuthn attestation object."
                  },
                  "clientDataJSON": {
                    "type": "string",
                    "description": "base64url WebAuthn `clientDataJSON`."
                  },
                  "transports": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "OPTIONAL authenticator transports (e.g. `usb`, `nfc`, `internal`), persisted comma-joined."
                  }
                }
              },
              "example": {
                "challenge": "Y2hhbGxlbmdlLW5vbmNlLTAx",
                "attestationObject": "example-attestationObject",
                "clientDataJSON": "example-clientDataJSON",
                "transports": [
                  "example-transports"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The registered credential id + bound subject, or a closed `ok:false` refusal.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "credentialId",
                        "subjectId"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "credentialId": {
                          "type": "string"
                        },
                        "subjectId": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "reason"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        },
                        "reason": {
                          "type": "string",
                          "enum": [
                            "invalid_request",
                            "purpose_mismatch",
                            "client_data_invalid",
                            "challenge_invalid",
                            "challenge_mismatch",
                            "origin_not_allowed",
                            "rp_id_mismatch",
                            "user_not_present",
                            "user_not_verified",
                            "attestation_invalid",
                            "attestation_signature_invalid",
                            "unsupported_algorithm",
                            "assertion_signature_invalid",
                            "sign_count_regression",
                            "subject_unverified",
                            "credential_exists",
                            "credential_unknown",
                            "storage_error",
                            "step_up_unconfigured",
                            "step_up_transport_error",
                            "step_up_provider_error",
                            "step_up_no_match",
                            "step_up_reference_required",
                            "aaguid_blocked",
                            "attestation_required",
                            "attestation_untrusted",
                            "aaguid_status_rejected",
                            "step_up_reference_replayed",
                            "step_up_claim_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "ok": true,
                  "credentialId": "example-credentialId",
                  "subjectId": "subj_4821"
                }
              }
            }
          },
          "400": {
            "description": "`{\"ok\":false,\"reason\":\"invalid_request\"}` or `{\"ok\":false,\"reason\":\"purpose_mismatch\"}` — the only two reasons that map to HTTP 400.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "reason"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "invalid_request",
                        "purpose_mismatch",
                        "client_data_invalid",
                        "challenge_invalid",
                        "challenge_mismatch",
                        "origin_not_allowed",
                        "rp_id_mismatch",
                        "user_not_present",
                        "user_not_verified",
                        "attestation_invalid",
                        "attestation_signature_invalid",
                        "unsupported_algorithm",
                        "assertion_signature_invalid",
                        "sign_count_regression",
                        "subject_unverified",
                        "credential_exists",
                        "credential_unknown",
                        "storage_error",
                        "step_up_unconfigured",
                        "step_up_transport_error",
                        "step_up_provider_error",
                        "step_up_no_match",
                        "step_up_reference_required",
                        "aaguid_blocked",
                        "attestation_required",
                        "attestation_untrusted",
                        "aaguid_status_rejected",
                        "step_up_reference_replayed",
                        "step_up_claim_unavailable"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "invalid_request"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/passkey/assert": {
      "post": {
        "operationId": "postPasskeyAssert",
        "tags": [
          "Passkey"
        ],
        "summary": "Verify a WebAuthn assertion",
        "description": "Server-to-server (`sk_` key, scope `write:passkey`). Verifies a WebAuthn assertion against a stored credential. In order: the `assert` challenge is consumed (single use) and yields the bound `subject_id`; the active credential is looked up by credential id (an unknown id gives the closed `credential_unknown`); the credential must belong to the challenge's subject (otherwise `challenge_mismatch`, so a challenge minted for subject A can never assert subject B's credential); the signature is verified over `authenticatorData || SHA-256(clientDataJSON)` with the stored public key, against only the domain bound into the challenge; the signature counter must strictly advance (otherwise `sign_count_regression`, a replay defence). On success the counter is advanced and the bound `subjectId` is returned.\n\nEvery failure returns the same uniform HTTP 200 `{\"ok\":false}`; credential-level reasons are an integrator diagnostic, not an enumeration oracle, because this surface is intra-organisation and credential ids are high-entropy. Availability is the same as for `POST /v1/passkey/challenge`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:passkey"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "challenge",
                  "credentialId",
                  "authenticatorData",
                  "clientDataJSON",
                  "signature"
                ],
                "properties": {
                  "challenge": {
                    "type": "string",
                    "description": "base64url — minted with `purpose:'assert'`."
                  },
                  "credentialId": {
                    "type": "string"
                  },
                  "authenticatorData": {
                    "type": "string",
                    "description": "base64url WebAuthn `authenticatorData`."
                  },
                  "clientDataJSON": {
                    "type": "string",
                    "description": "base64url WebAuthn `clientDataJSON`."
                  },
                  "signature": {
                    "type": "string",
                    "description": "base64url WebAuthn assertion signature."
                  }
                }
              },
              "example": {
                "challenge": "Y2hhbGxlbmdlLW5vbmNlLTAx",
                "credentialId": "example-credentialId",
                "authenticatorData": "example-authenticatorData",
                "clientDataJSON": "example-clientDataJSON",
                "signature": "MEUCIQDx1F2a3B4c5D6e7F8a9B0c1D2e3F4a5B6c7D8e9F0a1B2c3D4e5F6a7B8c"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The verified bound subject, or a closed `ok:false` refusal.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "verified",
                        "subjectId"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "verified": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "subjectId": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "reason"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        },
                        "reason": {
                          "type": "string",
                          "enum": [
                            "invalid_request",
                            "purpose_mismatch",
                            "client_data_invalid",
                            "challenge_invalid",
                            "challenge_mismatch",
                            "origin_not_allowed",
                            "rp_id_mismatch",
                            "user_not_present",
                            "user_not_verified",
                            "attestation_invalid",
                            "attestation_signature_invalid",
                            "unsupported_algorithm",
                            "assertion_signature_invalid",
                            "sign_count_regression",
                            "subject_unverified",
                            "credential_exists",
                            "credential_unknown",
                            "storage_error",
                            "step_up_unconfigured",
                            "step_up_transport_error",
                            "step_up_provider_error",
                            "step_up_no_match",
                            "step_up_reference_required",
                            "aaguid_blocked",
                            "attestation_required",
                            "attestation_untrusted",
                            "aaguid_status_rejected",
                            "step_up_reference_replayed",
                            "step_up_claim_unavailable"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "ok": true,
                  "verified": true,
                  "subjectId": "subj_4821"
                }
              }
            }
          },
          "400": {
            "description": "`{\"ok\":false,\"reason\":\"invalid_request\"}` or `{\"ok\":false,\"reason\":\"purpose_mismatch\"}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "reason"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "invalid_request",
                        "purpose_mismatch",
                        "client_data_invalid",
                        "challenge_invalid",
                        "challenge_mismatch",
                        "origin_not_allowed",
                        "rp_id_mismatch",
                        "user_not_present",
                        "user_not_verified",
                        "attestation_invalid",
                        "attestation_signature_invalid",
                        "unsupported_algorithm",
                        "assertion_signature_invalid",
                        "sign_count_regression",
                        "subject_unverified",
                        "credential_exists",
                        "credential_unknown",
                        "storage_error",
                        "step_up_unconfigured",
                        "step_up_transport_error",
                        "step_up_provider_error",
                        "step_up_no_match",
                        "step_up_reference_required",
                        "aaguid_blocked",
                        "attestation_required",
                        "attestation_untrusted",
                        "aaguid_status_rejected",
                        "step_up_reference_replayed",
                        "step_up_claim_unavailable"
                      ]
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "invalid_request"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/handoff/mint": {
      "post": {
        "operationId": "postHandoffMint",
        "tags": [
          "Device Handoff"
        ],
        "summary": "Mint a browser-to-app handoff claim",
        "description": "Server-to-server (`sk_` key, scope `write:handoff`): the caller is your own backend, never a browser or a mobile device. Mints a session-bound, single-use claim that hands a verified browser session to a native app. It returns two independent 256-bit secrets, stored only as hashes: `exchangeCode`, the only value meant for the QR code or deep link, and `challenge`, a separate binding secret that you convey through your own out-of-band channel and never embed in the QR code. Both are required together at `POST /v1/handoff/consume`. Minting again for the same `sessionId` atomically supersedes any pending claim for that session, which supports cross-device rescans.\n\nNot enabled by default: when the capability is not enabled for your organisation both handoff endpoints answer the `GatedNotFound` 404.\n\n**Key scope availability.** At the time of writing the `write:handoff` scope is not among the scopes that can be selected when creating a server API key in the Backoffice, so a key created there cannot call the handoff endpoints.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:handoff"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sessionId"
                ],
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "maxLength": 256,
                    "description": "Opaque caller-generated correlation id; no PII."
                  }
                }
              },
              "example": {
                "sessionId": "01J8Z3K7QN6R2S4T6V8W0X2Y4Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The minted claim secrets and TTL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "exchangeCode",
                    "challenge",
                    "sessionId",
                    "ttlSeconds",
                    "expiresAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "exchangeCode": {
                      "type": "string",
                      "description": "base64url(32 random bytes) — the ONLY secret meant for the QR/deep-link."
                    },
                    "challenge": {
                      "type": "string",
                      "description": "base64url(32 random bytes) — conveyed OUT-OF-BAND, never in the QR."
                    },
                    "sessionId": {
                      "type": "string"
                    },
                    "ttlSeconds": {
                      "type": "integer"
                    },
                    "expiresAt": {
                      "type": "integer",
                      "description": "Epoch milliseconds."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "exchangeCode": "example-exchangeCode",
                  "challenge": "Y2hhbGxlbmdlLW5vbmNlLTAx",
                  "sessionId": "01J8Z3K7QN6R2S4T6V8W0X2Y4Z",
                  "ttlSeconds": 3600,
                  "expiresAt": 1768435200
                }
              }
            }
          },
          "400": {
            "description": "`Invalid or missing sessionId` or `Invalid body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "description": "`Payload too large` — over the 1024-byte cap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/handoff/consume": {
      "post": {
        "operationId": "postHandoffConsume",
        "tags": [
          "Device Handoff"
        ],
        "summary": "Consume a browser-to-app handoff claim",
        "description": "Server-to-server (`sk_` key, scope `write:handoff`): called by your app's own backend when it resolves the deep link, never by the mobile device. The caller must present all of `sessionId`, `exchangeCode` and `challenge`. An exact match on every bound field, a pending status and an unexpired claim are all required, and the claim is consumed atomically, so an incorrect attempt never burns the real claim.\n\nEvery mismatch — a replay, another organisation, another session, a swapped challenge or an expired claim — gives the same closed `claim_invalid` rejection at a uniform HTTP 200, never a 4xx, because revealing which field failed would expose the claim's binding. Availability and the key-scope note are the same as for `POST /v1/handoff/mint`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:handoff"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sessionId",
                  "exchangeCode",
                  "challenge"
                ],
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "maxLength": 256
                  },
                  "exchangeCode": {
                    "type": "string",
                    "maxLength": 512,
                    "description": "The `exchangeCode` scanned from the QR/deep-link."
                  },
                  "challenge": {
                    "type": "string",
                    "maxLength": 512,
                    "description": "The separately-conveyed binding secret."
                  }
                }
              },
              "example": {
                "sessionId": "01J8Z3K7QN6R2S4T6V8W0X2Y4Z",
                "exchangeCode": "example-exchangeCode",
                "challenge": "Y2hhbGxlbmdlLW5vbmNlLTAx"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The consume outcome — always HTTP 200 whether accepted or refused.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "sessionId"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "sessionId": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "reason"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        },
                        "reason": {
                          "type": "string",
                          "enum": [
                            "claim_invalid"
                          ]
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "ok": true,
                  "sessionId": "01J8Z3K7QN6R2S4T6V8W0X2Y4Z"
                }
              }
            }
          },
          "400": {
            "description": "`Invalid or missing sessionId/exchangeCode/challenge` or `Invalid body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "description": "`Payload too large` — over the 2048-byte cap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/device-delegation/create": {
      "post": {
        "operationId": "deviceDelegationCreate",
        "tags": [
          "Device Delegation"
        ],
        "summary": "Create a subject-scoped device delegation",
        "description": "Creates a delegation of device authority for a subject. The delegation is born `active`, subject to a cap on active delegations per subject (`device_delegation_limit_exceeded` when the cap is reached). The organisation is always the authenticated one: a tenant id in the body is never used and is omitted from the response. Every id field must match `^[a-zA-Z0-9._:-]{1,128}$`, except the server-generated delegation `id`, which is a UUID.\n\nNot enabled by default: when the device-registry capability is not enabled for your organisation the route answers the `GatedNotFound` 404. The three delegation operations share one rate-limit bucket per API key.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subjectId",
                  "delegatorId",
                  "delegateId"
                ],
                "properties": {
                  "subjectId": {
                    "type": "string",
                    "pattern": "^[a-zA-Z0-9._:-]{1,128}$",
                    "description": "The subject the delegation concerns (opaque, never raw PII)."
                  },
                  "delegatorId": {
                    "type": "string",
                    "pattern": "^[a-zA-Z0-9._:-]{1,128}$",
                    "description": "The principal granting the delegation."
                  },
                  "delegateId": {
                    "type": "string",
                    "pattern": "^[a-zA-Z0-9._:-]{1,128}$",
                    "description": "The device/principal receiving the delegation."
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "device_use",
                      "device_manage"
                    ],
                    "default": "device_use",
                    "description": "Defaults to `device_use` when omitted or not one of the closed values."
                  },
                  "evidenceRef": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[a-zA-Z0-9._:-]{1,128}$",
                    "description": "Optional opaque id referencing the evidence/consent record justifying the delegation."
                  },
                  "expiresAt": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "format": "int64",
                    "description": "Optional epoch-ms timestamp. Omitted ⇒ no expiry. When present MUST be finite and strictly within `[now + 60000, now + 31536000000]` (a 1-minute floor, 1-year ceiling) — rejected, never clamped, outside that window."
                  },
                  "createdBy": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[a-zA-Z0-9._:-]{1,128}$",
                    "description": "Optional opaque actor id for the audit trail."
                  }
                }
              },
              "example": {
                "subjectId": "subj_4821",
                "delegatorId": "dev_7a2b9c4d1e6f3a8b",
                "delegateId": "dev_2b9c4d1e6f3a8b7a",
                "scope": "device_use",
                "evidenceRef": "obj-front",
                "expiresAt": 1768435200,
                "createdBy": "tenant_42"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The minted delegation, born `active`. `tenantId` is omitted (the caller already knows its own tenant).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "delegation",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "delegation": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "subjectId": {
                          "type": "string"
                        },
                        "delegatorId": {
                          "type": "string"
                        },
                        "delegateId": {
                          "type": "string"
                        },
                        "scope": {
                          "type": "string",
                          "enum": [
                            "device_use",
                            "device_manage"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "active"
                          ]
                        },
                        "evidenceRef": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "expiresAt": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "format": "int64"
                        },
                        "revokedAt": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "format": "int64"
                        },
                        "createdBy": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "createdAt": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "updatedAt": {
                          "type": "integer",
                          "format": "int64"
                        }
                      }
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "delegation": {
                    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "subjectId": "subj_4821",
                    "delegatorId": "dev_7a2b9c4d1e6f3a8b",
                    "delegateId": "dev_2b9c4d1e6f3a8b7a",
                    "scope": "device_use",
                    "status": "active",
                    "evidenceRef": "obj-front",
                    "expiresAt": 1768435200,
                    "revokedAt": 1,
                    "createdBy": "tenant_42",
                    "createdAt": 1,
                    "updatedAt": 1
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`device_delegation_invalid_subject_id` / `_delegator_id` / `_delegate_id` / `_evidence_ref` / `_expiry` / `_created_by` / `_request` / `_limit_exceeded`, or a generic `invalid_request` for a malformed body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "Non-JSON `Content-Type` (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`device_delegation_storage_unavailable` — a D1 write fault.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/device-delegation/list": {
      "post": {
        "operationId": "deviceDelegationList",
        "tags": [
          "Device Delegation"
        ],
        "summary": "List a subject's active device delegations",
        "description": "Lists the active, non-expired delegations of a subject. A subject with no active delegations, or an unknown subject, returns an empty array, never a distinguishing status (no existence oracle). Availability and rate-limit bucket are the same as for `POST /v1/device-delegation/create`; a `401` never consumes the bucket.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subjectId"
                ],
                "properties": {
                  "subjectId": {
                    "type": "string",
                    "description": "The subject whose active delegations to list."
                  }
                }
              },
              "example": {
                "subjectId": "subj_4821"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The subject's active, non-expired delegations (empty array if none / unknown subject).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "delegations",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "delegations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "subjectId": {
                            "type": "string"
                          },
                          "delegatorId": {
                            "type": "string"
                          },
                          "delegateId": {
                            "type": "string"
                          },
                          "scope": {
                            "type": "string",
                            "enum": [
                              "device_use",
                              "device_manage"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "active"
                            ]
                          },
                          "evidenceRef": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "expiresAt": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "format": "int64"
                          },
                          "revokedAt": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "format": "int64"
                          },
                          "createdBy": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "createdAt": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "updatedAt": {
                            "type": "integer",
                            "format": "int64"
                          }
                        }
                      }
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "delegations": [
                    {
                      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "subjectId": "subj_4821",
                      "delegatorId": "dev_7a2b9c4d1e6f3a8b",
                      "delegateId": "dev_2b9c4d1e6f3a8b7a",
                      "scope": "device_use",
                      "status": "active",
                      "evidenceRef": "obj-front",
                      "expiresAt": 1768435200,
                      "revokedAt": 1,
                      "createdBy": "tenant_42",
                      "createdAt": 1,
                      "updatedAt": 1
                    }
                  ],
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — `subjectId` missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "Non-JSON `Content-Type` (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`device_delegation_storage_unavailable` — a D1 read fault.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/device-delegation/revoke": {
      "post": {
        "operationId": "deviceDelegationRevoke",
        "tags": [
          "Device Delegation"
        ],
        "summary": "Revoke a device delegation",
        "description": "Revokes a delegation. Revocation is terminal: a revoked delegation is never honoured again, and restoring access means creating a new delegation. A delegation id that belongs to another organisation answers `device_delegation_not_found`, byte-identical to an unknown id (no existence oracle). Availability and rate-limit bucket are the same as for `POST /v1/device-delegation/create`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The delegation's own server-minted id (not the subject/delegator/delegate id)."
                  },
                  "revokedBy": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Optional opaque actor id for the audit trail; omitted ⇒ recorded as a SYSTEM actor."
                  }
                }
              },
              "example": {
                "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                "revokedBy": "example-revokedBy"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The delegation was revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "revoked",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "revoked": {
                      "type": "boolean"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "revoked": true,
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — `id` missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "Either the capability is not enabled for your organisation (the `GatedNotFound` body), or the delegation `id` does not resolve for your organisation (`device_delegation_not_found`, byte-identical for an unknown id and an id of another organisation).",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/v1_ErrorEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "V1 endpoint not found"
                          ]
                        },
                        "path": {
                          "type": "string"
                        },
                        "requestId": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "Non-JSON `Content-Type` (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`device_delegation_storage_unavailable` — a D1 write fault.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/recovery/cases": {
      "post": {
        "operationId": "recoveryOpenCase",
        "tags": [
          "Account Recovery"
        ],
        "summary": "Open a recovery case (currently always refused)",
        "description": "Self-service recovery case opening is not available today. A well-formed request never creates a case: the route validates the shape of `installId` (a UUID, then discarded — it is never treated as a subject reference) and answers the same closed business rejection with HTTP 200, which is the status the mobile backend contract inspects. A request that carries a `subjectId` of any type is rejected with `400 invalid_request`, so that no key holder can name a foreign subject.\n\nNot enabled by default: when the device-registry capability is not enabled for your organisation the route answers the `GatedNotFound` 404. The four `/v1/recovery/*` operations share one rate-limit bucket per API key.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "installId",
                  "tier"
                ],
                "properties": {
                  "installId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Validated for shape only (UUID v1-5) and discarded — never a subject reference."
                  },
                  "tier": {
                    "type": "string",
                    "enum": [
                      "fresh_reverification",
                      "trusted_referee",
                      "alternate_evidence",
                      "device_history_attestation",
                      "dual_control_override"
                    ]
                  },
                  "evidenceRef": {
                    "type": "string",
                    "pattern": "^[a-zA-Z0-9._:-]{1,128}$",
                    "description": "Optional opaque id."
                  },
                  "subjectId": {
                    "description": "NOT part of the frozen contract. Any own-property `subjectId` (of any type, including `null`) is rejected with `400 invalid_request` — never accepted-and-ignored."
                  }
                }
              },
              "example": {
                "installId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                "tier": "fresh_reverification",
                "evidenceRef": "obj-front",
                "subjectId": "subj_4821"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always the closed business rejection today: no case is created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "reason",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "subject_resolution_required"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "subject_resolution_required",
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "Missing/invalid `installId` or `tier`, an invalid `evidenceRef` shape, or any own-property `subjectId` present.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "Non-JSON `Content-Type` (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`registry_write_failed` — unexpected handler error (never a raw exception).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "registry_write_failed"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "registry_write_failed",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "partial"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/recovery/cases/{caseId}": {
      "get": {
        "operationId": "recoveryGetCase",
        "tags": [
          "Account Recovery"
        ],
        "summary": "Read a recovery case's status",
        "description": "Polls the status of a recovery case. The stored case is redacted before it is returned: `subjectId`, `evidenceRef`, `refereeId`, `fraudReviewVerdict`, `createdBy` and `resolutionReasonCode` are never part of the response. A `caseId` that belongs to another organisation answers the same `404` as an unknown id (no existence oracle). Availability and rate-limit bucket are the same as for `POST /v1/recovery/cases`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "name": "caseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-zA-Z0-9._:-]{1,128}$"
            },
            "description": "Opaque case id, decoded by the router and re-validated inside the handler."
          }
        ],
        "responses": {
          "200": {
            "description": "The redacted case projection.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "case",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "case": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "tenantId": {
                          "type": "string"
                        },
                        "tier": {
                          "type": "string",
                          "enum": [
                            "fresh_reverification",
                            "trusted_referee",
                            "alternate_evidence",
                            "device_history_attestation",
                            "dual_control_override"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "opened",
                            "pending_referee",
                            "pending_dual_control",
                            "pending_fraud_review",
                            "approved",
                            "denied",
                            "blocked_external",
                            "expired"
                          ]
                        },
                        "resultingVerificationLevel": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "resultingStatus": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "active",
                            "active_limited",
                            "proof_restored"
                          ]
                        },
                        "slaDueAt": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "resolvedAt": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "format": "int64"
                        },
                        "createdAt": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "updatedAt": {
                          "type": "integer",
                          "format": "int64"
                        }
                      }
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "case": {
                    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "tenantId": "tenant_42",
                    "tier": "fresh_reverification",
                    "status": "opened",
                    "resultingVerificationLevel": "example-resultingVerificationLevel",
                    "resultingStatus": "active",
                    "slaDueAt": 1,
                    "resolvedAt": 1,
                    "createdAt": 1,
                    "updatedAt": 1
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` (opaque-id shape violation), or `Invalid caseId` (router-level `decodeURIComponent` throw on malformed percent-encoding).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "Either the capability is not enabled for your organisation (the `GatedNotFound` body), or `recovery_case_not_found`: the `caseId` is well formed but no case exists for your organisation (a case of another organisation resolves identically).",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "V1 endpoint not found"
                          ]
                        },
                        "path": {
                          "type": "string"
                        },
                        "requestId": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        },
                        "reason": {
                          "type": "string",
                          "enum": [
                            "recovery_case_not_found"
                          ]
                        },
                        "requestId": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "V1 endpoint not found",
                  "path": "/v1/kyc/verify",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`registry_write_failed` — unexpected handler error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "registry_write_failed"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "registry_write_failed",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/recovery/appeals": {
      "post": {
        "operationId": "recoveryOpenAppeal",
        "tags": [
          "Account Recovery"
        ],
        "summary": "Open an appeal (currently always refused)",
        "description": "Opening an appeal is not available today. Because no case can be opened, the route refuses unconditionally with the same closed reason as `POST /v1/recovery/cases`, and it does not read or validate the request body: any body, or none, produces the identical response. Availability and rate-limit bucket are the same as for the other recovery routes.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "caseId": {
                    "type": "string",
                    "description": "Nominal frozen-BFF-contract field. Never read or validated by this handler."
                  }
                }
              },
              "example": {
                "caseId": "example-caseId"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always the closed business rejection today: no appeal is created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "reason",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "subject_resolution_required"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "subject_resolution_required",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`registry_write_failed` — unexpected handler error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "registry_write_failed"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "registry_write_failed",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "partial"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/recovery/appeals/{appealId}": {
      "get": {
        "operationId": "recoveryGetAppeal",
        "tags": [
          "Account Recovery"
        ],
        "summary": "Read an appeal's status",
        "description": "Polls the status of an appeal. Unlike a case, no field of an appeal is redacted. A missing appeal reuses the case reason `recovery_case_not_found`; there is no separate appeal-not-found code. Availability and rate-limit bucket are the same as for the other recovery routes.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "name": "appealId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-zA-Z0-9._:-]{1,128}$"
            },
            "description": "Opaque appeal id, decoded by the router and re-validated inside the handler."
          }
        ],
        "responses": {
          "200": {
            "description": "The full (unredacted) appeal projection.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "appeal",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "appeal": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "tenantId": {
                          "type": "string"
                        },
                        "caseId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "openedAt": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "slaDueAt": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "resolvedAt": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "format": "int64"
                        },
                        "outcome": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "upheld",
                            "overturned",
                            "expired"
                          ]
                        },
                        "resolvedBy": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "createdAt": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "updatedAt": {
                          "type": "integer",
                          "format": "int64"
                        }
                      }
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "appeal": {
                    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "tenantId": "tenant_42",
                    "caseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                    "openedAt": 1,
                    "slaDueAt": 1,
                    "resolvedAt": 1,
                    "outcome": "pending",
                    "resolvedBy": "example-resolvedBy",
                    "createdAt": 1,
                    "updatedAt": 1
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` (opaque-id shape violation), or `Invalid appealId` (router-level `decodeURIComponent` throw).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "description": "Either the capability is not enabled for your organisation (the `GatedNotFound` body), or `recovery_case_not_found`: no appeal with that id exists for your organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "V1 endpoint not found"
                          ]
                        },
                        "path": {
                          "type": "string"
                        },
                        "requestId": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        },
                        "reason": {
                          "type": "string",
                          "enum": [
                            "recovery_case_not_found"
                          ]
                        },
                        "requestId": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "V1 endpoint not found",
                  "path": "/v1/kyc/verify",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`registry_write_failed` — unexpected handler error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "registry_write_failed"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": false,
                  "reason": "registry_write_failed",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/wallet/status": {
      "post": {
        "operationId": "walletStatus",
        "tags": [
          "Wallet Status"
        ],
        "summary": "Read a subject's device status and verification level",
        "description": "Read-only, organisation-scoped device status and `verificationLevel` for a subject. It is built on the same trust decision that `POST /v1/kyc/reverify` reads, plus relying-party log, consent-receipt and pairwise-pseudonym transparency reads, each independently fail-closed. This is the identity and device wallet, not a payment wallet.\n\n`verificationLevel` is present only on a trusted decision, never on a proof-only link; `proofOnly: true` marks a proof-only partition (status `proofed_unbound`, level `null`). `stepUpRequired` is reported, never enforced here, only when the decision demands it. An unexpected internal fault answers `200` with every field null (fail-closed: never a 5xx, never a positive status).\n\nNot enabled by default: when the device-registry capability is not enabled for your organisation the route answers the `GatedNotFound` 404.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subjectId"
                ],
                "properties": {
                  "subjectId": {
                    "type": "string",
                    "description": "The opaque FR-8 pseudonym identifying the device/subject."
                  }
                }
              },
              "example": {
                "subjectId": "subj_4821"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The readout — ALWAYS 200, including on internal fault (every field nulled in that case; never a happy-path 5xx).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "verificationLevel",
                    "status",
                    "pairwisePseudonym",
                    "consent",
                    "rpLog",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "verificationLevel": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Present only on a trusted decision."
                    },
                    "status": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "pairwisePseudonym": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "consent": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "version": {
                          "type": "string"
                        },
                        "grantedAt": {
                          "type": "string"
                        },
                        "revoked": {
                          "type": "boolean"
                        }
                      }
                    },
                    "rpLog": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "event": {
                            "type": "string"
                          },
                          "at": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "proofOnly": {
                      "type": "boolean",
                      "description": "Present (true) only for the proof-only partition."
                    },
                    "stepUpRequired": {
                      "type": "boolean",
                      "description": "Present (true) only when the authoritative decision demands step-up."
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "verificationLevel": "verified",
                  "status": "example-status",
                  "pairwisePseudonym": "example-pairwisePseudonym",
                  "consent": {
                    "version": "example-version",
                    "grantedAt": "example-grantedAt",
                    "revoked": true
                  },
                  "rpLog": [
                    {
                      "event": "example-event",
                      "at": "example-at"
                    }
                  ],
                  "proofOnly": true,
                  "stepUpRequired": true,
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — malformed body or missing `subjectId`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/wallet/suspend": {
      "post": {
        "operationId": "walletSuspend",
        "tags": [
          "Wallet Status"
        ],
        "summary": "Suspend a subject's device-bound wallet state",
        "description": "Executes the `suspended` transition for a subject's device-bound wallet state. Name the subject in one of two ways: `attestationId` (preferred, and it takes precedence when both are present), resolved server-side from a fresh single-use device-attestation claim; or `subjectId` (a trusted-caller fallback), which additionally requires the `write:identity:trusted-subject` scope and otherwise answers a genuine `403`.\n\nEvery refusal collapses to a closed result so that no response reveals whether a subject exists: an attestation that cannot be resolved gives `status: \"subject_not_attested\"`, and any illegal transition or internal fault gives `status: \"suspend_failed\"`. The route always answers `200`, with `suspended: true` on a legal transition. It shares the write-family rate-limit bucket of `POST /v1/kyc/verify`. Availability is the same as for `POST /v1/wallet/status`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          },
          {
            "v1_ApiKeyAuth": [
              "write:identity",
              "write:identity:trusted-subject"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "description": "EXACTLY one of `attestationId` or `subjectId` must be present (both may be sent; `attestationId` takes precedence and `subjectId` is then silently ignored).",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "attestationId": {
                    "type": "string",
                    "description": "Preferred. A fresh device-attestation reference the collector resolves to a subject server-side; consumed single-use."
                  },
                  "subjectId": {
                    "type": "string",
                    "description": "Trusted-caller fallback. Requires the additive `write:identity:trusted-subject` scope; the caller already knows the subject it names."
                  }
                }
              },
              "example": {
                "attestationId": "att_9c6b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
                "subjectId": "subj_4821"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ALWAYS 200. `suspended:true` on a legal transition; `suspended:false` with `status:'subject_not_attested'` on an attestation-resolution miss; `suspended:false` with `status:'suspend_failed'` on any illegal-transition reason or an internal fault.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "suspended",
                    "status",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "suspended": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "suspended",
                        "subject_not_attested",
                        "suspend_failed"
                      ]
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "suspended": true,
                  "status": "suspended",
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — malformed body, or neither `subjectId` nor `attestationId` present.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "description": "The `subjectId` fallback was used WITHOUT the additive `write:identity:trusted-subject` scope (the base `write:identity`-scope-missing case uses the standard `ScopeForbidden` shape).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "API key does not have write:identity:trusted-subject scope"
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "API key does not have write:identity:trusted-subject scope",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/offline/nonce-pool": {
      "post": {
        "operationId": "offlineNoncePoolMint",
        "tags": [
          "Offline Capture"
        ],
        "summary": "Mint a batch of offline-capture nonces",
        "description": "Mints 1 to 5 (default 1) single-use, time-limited nonces while the caller has connectivity, for later redemption at `POST /v1/offline/capture-bundle`. This is a resumable-upload scope, not a truly offline start: the client must be online when it mints. Each nonce is stored only as a keyed digest and the raw nonce is returned exactly once. The default lifetime is 72 hours.\n\nA caller classified as first-party capture on its API key must hold an active legal-basis receipt to mint; otherwise it is refused with `403 kyc_legal_basis_receipt_required`. Ordinary tenant traffic is unaffected. Not enabled by default: when the capability is not enabled for your organisation the route answers the `GatedNotFound` 404.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5,
                    "default": 1,
                    "description": "Non-integer or out-of-range values fall back to the default (1) rather than erroring."
                  }
                }
              },
              "example": {
                "count": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The minted nonce pool. Raw nonces are visible exactly once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "nonces",
                    "requestId"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "nonces": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "nonce": {
                            "type": "string"
                          },
                          "expiresAt": {
                            "type": "integer",
                            "format": "int64"
                          }
                        }
                      }
                    },
                    "requestId": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "nonces": [
                    {
                      "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                      "expiresAt": 1768435200
                    }
                  ],
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "Malformed body, bad `Content-Length`, or a non-JSON-object body (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "description": "`kyc_legal_basis_receipt_required` (legal-basis gate refusal), OR the standard scope-forbidden shape.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "Non-JSON `Content-Type` (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`offline_capture_storage_unavailable` (digest secret unset / mint fault), or `kyc_first_party_provenance_unavailable` (legal-basis gate fault).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/offline/capture-bundle": {
      "post": {
        "operationId": "offlineCaptureBundleAdmit",
        "tags": [
          "Offline Capture"
        ],
        "summary": "Admit a deferred-upload offline capture bundle",
        "description": "Redeems a nonce minted by `POST /v1/offline/nonce-pool` to admit an on-device-encrypted, time-limited capture bundle for deferred upload. This endpoint never sees the bundle's encryption key or media, only control-plane admission fields. It is capture-only: a body that carries any verdict- or attestation-shaped key (`verdict`, `verified`, `attestationId`, `reasonCodes` and similar) is rejected before any nonce lookup or write, because attestation stays online-only.\n\nThe call is idempotent on the `Idempotency-Key` header: a replay with an identical body returns the same stored response byte for byte, even long after the nonce's lifetime has elapsed, and a replay with a different body under the same key is a `409` conflict. The request body is limited to 1 MiB. Availability and the legal-basis rule are the same as for `POST /v1/offline/nonce-pool`.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:identity"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "bundleId",
                  "nonce",
                  "capturedAt"
                ],
                "properties": {
                  "bundleId": {
                    "type": "string",
                    "maxLength": 256,
                    "description": "Caller-assigned bundle identifier (accepted as `bundleId` or `bundle_id`)."
                  },
                  "nonce": {
                    "type": "string",
                    "description": "A nonce previously minted by `POST /v1/offline/nonce-pool`."
                  },
                  "capturedAt": {
                    "type": "integer",
                    "format": "int64",
                    "minimum": 0,
                    "description": "Epoch-millisecond capture timestamp (accepted as `capturedAt` or `captured_at`); must be a finite, non-negative safe integer."
                  }
                }
              },
              "example": {
                "bundleId": "example-bundleId",
                "nonce": "nonce_5f3a8c1e2d4b6f709182a3b4c5d6e7f8",
                "capturedAt": 0
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The bundle admission was accepted (fresh claim or a byte-identical idempotent replay).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "bundleId",
                    "status",
                    "capturedAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "bundleId": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "accepted"
                      ]
                    },
                    "capturedAt": {
                      "type": "integer",
                      "format": "int64"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "bundleId": "example-bundleId",
                  "status": "accepted",
                  "capturedAt": 1
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` (missing/invalid `Idempotency-Key` or required fields, malformed body), `offline_capture_verdict_claim_rejected` (verdict-shaped key present), or `offline_capture_nonce_invalid` (unknown nonce).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "description": "`kyc_legal_basis_receipt_required`, or the standard scope-forbidden shape.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "409": {
            "description": "`offline_capture_idempotency_conflict` (same key, differing body), or `offline_capture_nonce_replayed` (nonce already claimed by a different bundle).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "description": "`offline_capture_bundle_expired` — the nonce is past its TTL; the nonce row is deleted and the bundle is never processed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "description": "Body exceeds 1 MiB (`body_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "415": {
            "description": "Non-JSON `Content-Type` (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "`offline_capture_storage_unavailable` (digest secret unset, or a storage/admission fault), or `kyc_first_party_provenance_unavailable` (legal-basis gate fault, or a ref-stamp fault after a fresh claim — the claimed row is then deleted).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/v1_ErrorEnvelope"
                },
                "example": {
                  "error": "invalid_request",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/projects": {
      "get": {
        "operationId": "v1ApmListProjects",
        "tags": [
          "APM Observability"
        ],
        "summary": "List APM projects",
        "description": "Lists tenant-owned observability projects from PostgreSQL authority. Results are cursor-paginated and never cached.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmListLimit"
          },
          {
            "$ref": "#/components/parameters/ApmCursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Project page.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmProjectList"
                },
                "example": {
                  "ok": true,
                  "items": [
                    {
                      "project_id": "checkout",
                      "display_name": "Checkout",
                      "state": "active",
                      "region_id": "us-central1",
                      "data_plane_id": "primary",
                      "capture_enabled": false,
                      "enabled_signals": [
                        "traces"
                      ],
                      "authority_epoch": 7,
                      "erasure_epoch": 2,
                      "created_at": "2026-08-01T00:00:00.000Z",
                      "updated_at": "2026-09-01T00:00:00.000Z"
                    }
                  ],
                  "count": 1,
                  "truncated": false,
                  "next_cursor": null,
                  "freshness": "postgres_authority",
                  "completeness": "complete"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmControlUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/projects/{projectId}/environments": {
      "get": {
        "operationId": "v1ApmListProjectEnvironments",
        "tags": [
          "APM Observability"
        ],
        "summary": "List project environments",
        "description": "Lists environments belonging to one tenant-owned APM project from PostgreSQL authority.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmProjectIdPath"
          },
          {
            "$ref": "#/components/parameters/ApmListLimit"
          },
          {
            "$ref": "#/components/parameters/ApmCursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Environment page.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmEnvironmentList"
                },
                "example": {
                  "ok": true,
                  "project_id": "checkout",
                  "items": [
                    {
                      "project_id": "checkout",
                      "environment_id": "prod",
                      "display_name": "Production",
                      "environment_kind": "production",
                      "state": "active",
                      "region_id": "us-central1",
                      "data_plane_id": "primary",
                      "capture_enabled": false,
                      "allowed_signals": [
                        "traces"
                      ],
                      "sampling_policy_id": "baseline",
                      "retention_policy_version": 3,
                      "privacy_policy_version": 2,
                      "authority_epoch": 7,
                      "erasure_epoch": 2,
                      "quota_manifest_revision": 4,
                      "parser_manifest_revision": 5,
                      "created_at": "2026-08-01T00:00:00.000Z",
                      "updated_at": "2026-09-01T00:00:00.000Z"
                    }
                  ],
                  "count": 1,
                  "truncated": false,
                  "next_cursor": null,
                  "freshness": "postgres_authority",
                  "completeness": "complete"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmControlUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/projects/{projectId}/environments/{environmentId}/policy": {
      "get": {
        "operationId": "v1ApmGetEnvironmentPolicy",
        "tags": [
          "APM Observability"
        ],
        "summary": "Read an environment policy",
        "description": "Returns the authoritative environment policy plus bounded sampling and retention policy inventories.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmProjectIdPath"
          },
          {
            "$ref": "#/components/parameters/ApmEnvironmentIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Policy snapshot.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmPolicySnapshot"
                },
                "example": {
                  "ok": true,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "policy": {
                    "project_id": "checkout",
                    "environment_id": "prod",
                    "display_name": "Production",
                    "environment_kind": "production",
                    "state": "active",
                    "region_id": "us-central1",
                    "data_plane_id": "primary",
                    "capture_enabled": false,
                    "allowed_signals": [
                      "traces"
                    ],
                    "sampling_policy_id": "baseline",
                    "retention_policy_version": 3,
                    "privacy_policy_version": 2,
                    "authority_epoch": 7,
                    "erasure_epoch": 2,
                    "quota_manifest_revision": 4,
                    "parser_manifest_revision": 5,
                    "created_at": "2026-08-01T00:00:00.000Z",
                    "updated_at": "2026-09-01T00:00:00.000Z"
                  },
                  "sampling_policies": [],
                  "sampling_policy_count": 0,
                  "sampling_policies_truncated": false,
                  "retention_policies": [],
                  "retention_policy_count": 0,
                  "retention_policies_truncated": false,
                  "freshness": {
                    "source": "postgres_authority",
                    "updated_at": "2026-09-01T00:00:00.000Z"
                  },
                  "completeness": {
                    "sampling_policies": "complete",
                    "retention_policies": "complete"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmControlUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "put": {
        "operationId": "v1ApmUpdateEnvironmentPolicy",
        "tags": [
          "APM Observability"
        ],
        "summary": "Update an environment policy",
        "description": "Updates a closed allowlist using optimistic authority-epoch concurrency. While capture rollout is OFF, `capture_enabled: true` is unavailable and returns 409 `capture_unavailable`; clients may only leave it absent or set it false.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmProjectIdPath"
          },
          {
            "$ref": "#/components/parameters/ApmEnvironmentIdPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApmPolicyUpdate"
              },
              "example": {
                "expected_authority_epoch": 7,
                "capture_enabled": false,
                "allowed_signals": [
                  "traces"
                ],
                "sampling_policy_id": "baseline"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated authoritative policy.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmPolicyUpdateResult"
                },
                "example": {
                  "ok": true,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "policy": {
                    "project_id": "checkout",
                    "environment_id": "prod",
                    "display_name": "Production",
                    "environment_kind": "production",
                    "state": "active",
                    "region_id": "us-central1",
                    "data_plane_id": "primary",
                    "capture_enabled": false,
                    "allowed_signals": [
                      "traces"
                    ],
                    "sampling_policy_id": "baseline",
                    "retention_policy_version": 3,
                    "privacy_policy_version": 2,
                    "authority_epoch": 8,
                    "erasure_epoch": 2,
                    "quota_manifest_revision": 4,
                    "parser_manifest_revision": 5,
                    "created_at": "2026-08-01T00:00:00.000Z",
                    "updated_at": "2026-09-21T00:00:00.000Z"
                  },
                  "count": 1,
                  "truncated": false,
                  "freshness": {
                    "source": "postgres_authority",
                    "updated_at": "2026-09-21T00:00:00.000Z"
                  },
                  "completeness": "complete"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "409": {
            "description": "Capture is unavailable or the expected authority epoch is stale.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmConflict"
                },
                "example": {
                  "ok": false,
                  "error": "capture_unavailable",
                  "available": false,
                  "reason": "capture_rollout_disabled",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ApmControlUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/traces": {
      "get": {
        "operationId": "v1ApmSearchTraces",
        "tags": [
          "APM Observability"
        ],
        "summary": "Search traces",
        "description": "Searches the warehouse within one explicit project/environment and a range no longer than 31 days. Rows expose only the query allowlist; completeness and freshness are not inferred.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmProjectIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmEnvironmentIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmFrom"
          },
          {
            "$ref": "#/components/parameters/ApmTo"
          },
          {
            "$ref": "#/components/parameters/ApmQueryLimit"
          },
          {
            "$ref": "#/components/parameters/ApmCursor"
          },
          {
            "name": "service_name",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256,
              "pattern": "^[A-Za-z0-9._:/-]+$"
            }
          },
          {
            "name": "release_id",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ApmScopedId"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ok",
                "error"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Trace summary page.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmTraceSearchResult"
                },
                "example": {
                  "ok": true,
                  "available": true,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "items": [
                    {
                      "traceId": "0123456789abcdef0123456789abcdef",
                      "startTime": "2026-09-20T12:00:00.000Z",
                      "partial": false,
                      "completeness": "complete",
                      "freshnessWatermark": "2026-09-20T12:01:00.000Z",
                      "ingestedAt": "2026-09-20T12:01:01.000Z"
                    }
                  ],
                  "count": 1,
                  "truncated": false,
                  "next_cursor": null,
                  "partial": false,
                  "completeness": "complete",
                  "freshness": {
                    "watermark": "2026-09-20T12:01:00.000Z",
                    "ingested_at": "2026-09-20T12:01:01.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ApmInternal"
          },
          "503": {
            "$ref": "#/components/responses/ApmWarehouseUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/traces/{traceId}": {
      "get": {
        "operationId": "v1ApmGetTrace",
        "tags": [
          "APM Observability"
        ],
        "summary": "Read trace detail",
        "description": "Returns a trace summary and a bounded timeline. Parent-edge completeness is explicitly false in this launch contract.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmTraceIdPath"
          },
          {
            "$ref": "#/components/parameters/ApmProjectIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmEnvironmentIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmFrom"
          },
          {
            "$ref": "#/components/parameters/ApmTo"
          },
          {
            "$ref": "#/components/parameters/ApmQueryLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "Trace detail and timeline.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmTraceDetailResult"
                },
                "example": {
                  "ok": true,
                  "available": true,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "summary": {
                    "traceId": "0123456789abcdef0123456789abcdef",
                    "startTime": "2026-09-20T12:00:00.000Z",
                    "partial": false,
                    "completeness": "complete",
                    "freshnessWatermark": "2026-09-20T12:01:00.000Z",
                    "ingestedAt": "2026-09-20T12:01:01.000Z"
                  },
                  "timeline": [
                    {
                      "traceId": "0123456789abcdef0123456789abcdef",
                      "spanId": "aaaaaaaaaaaaaaaa",
                      "parentSpanId": "bbbbbbbbbbbbbbbb",
                      "completeness": "partial",
                      "ingestedAt": "2026-09-20T12:01:01.000Z"
                    }
                  ],
                  "count": 1,
                  "truncated": false,
                  "partial": true,
                  "completeness": {
                    "summary": "complete",
                    "timeline": "partial"
                  },
                  "freshness": {
                    "watermark": "2026-09-20T12:01:00.000Z",
                    "ingested_at": "2026-09-20T12:01:01.000Z"
                  },
                  "parent_edges_complete": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ApmInternal"
          },
          "503": {
            "$ref": "#/components/responses/ApmWarehouseUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/services": {
      "get": {
        "operationId": "v1ApmListServices",
        "tags": [
          "APM Observability"
        ],
        "summary": "List service health",
        "description": "Returns bounded aggregate health rows for one project/environment and a range no longer than 31 days.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmProjectIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmEnvironmentIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmFrom"
          },
          {
            "$ref": "#/components/parameters/ApmTo"
          },
          {
            "$ref": "#/components/parameters/ApmQueryLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "Service health page.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmServiceListResult"
                },
                "example": {
                  "ok": true,
                  "available": true,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "items": [
                    {
                      "service_name": "checkout-api",
                      "trace_count": 120,
                      "error_trace_count": 3,
                      "error_rate": 0.025,
                      "p50_duration_ms": 42,
                      "p95_duration_ms": 310,
                      "completeness": "unknown",
                      "freshness_watermark": "2026-09-20T12:01:00.000Z"
                    }
                  ],
                  "count": 1,
                  "truncated": false,
                  "partial": false,
                  "completeness": "unknown",
                  "freshness_watermark": "2026-09-20T12:01:00.000Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmWarehouseUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/services/{serviceName}/health": {
      "get": {
        "operationId": "v1ApmGetServiceHealth",
        "tags": [
          "APM Observability"
        ],
        "summary": "Read service health and dependencies",
        "description": "Returns health plus dependency aggregates. Either warehouse subquery may be unavailable; `partial`, availability booleans, and `reason` report that honestly.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmServiceNamePath"
          },
          {
            "$ref": "#/components/parameters/ApmProjectIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmEnvironmentIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmFrom"
          },
          {
            "$ref": "#/components/parameters/ApmTo"
          },
          {
            "$ref": "#/components/parameters/ApmQueryLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "Service health and dependencies.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmServiceHealthResult"
                },
                "example": {
                  "ok": true,
                  "available": true,
                  "partial": false,
                  "reason": null,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "service_name": "checkout-api",
                  "health": {
                    "service_name": "checkout-api",
                    "trace_count": 120,
                    "error_trace_count": 3,
                    "error_rate": 0.025,
                    "p50_duration_ms": 42,
                    "p95_duration_ms": 310,
                    "completeness": "unknown",
                    "freshness_watermark": "2026-09-20T12:01:00.000Z"
                  },
                  "health_available": true,
                  "items": [
                    {
                      "source_service": "checkout-api",
                      "target_service": "payments",
                      "protocol": "http",
                      "request_count": 50,
                      "error_count": 1,
                      "p50_duration_ms": 25,
                      "p95_duration_ms": 80,
                      "p99_duration_ms": 140,
                      "freshness_watermark": "2026-09-20T12:01:00.000Z"
                    }
                  ],
                  "count": 1,
                  "dependencies_available": true,
                  "truncated": false,
                  "completeness": "unknown",
                  "freshness_watermark": "2026-09-20T12:01:00.000Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmWarehouseUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/releases/{releaseId}/health": {
      "get": {
        "operationId": "v1ApmGetReleaseHealth",
        "tags": [
          "APM Observability"
        ],
        "summary": "Read release health",
        "description": "Returns bounded aggregate health rows for one release within the required project/environment and time range.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:apm"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmReleaseIdPath"
          },
          {
            "$ref": "#/components/parameters/ApmProjectIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmEnvironmentIdQuery"
          },
          {
            "$ref": "#/components/parameters/ApmFrom"
          },
          {
            "$ref": "#/components/parameters/ApmTo"
          },
          {
            "$ref": "#/components/parameters/ApmQueryLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "Release health page.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmReleaseHealthResult"
                },
                "example": {
                  "ok": true,
                  "available": true,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "release_id": "2026.09.20",
                  "items": [
                    {
                      "release_id": "2026.09.20",
                      "build_id": "build-42",
                      "trace_count": 100,
                      "error_trace_count": 2,
                      "error_rate": 0.02,
                      "p50_duration_ms": 40,
                      "p95_duration_ms": 300,
                      "completeness": "unknown",
                      "freshness_watermark": "2026-09-20T12:01:00.000Z"
                    }
                  ],
                  "count": 1,
                  "truncated": false,
                  "partial": false,
                  "completeness": "unknown",
                  "freshness_watermark": "2026-09-20T12:01:00.000Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmWarehouseUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/exports": {
      "post": {
        "operationId": "v1ApmCreateExport",
        "tags": [
          "APM Observability"
        ],
        "summary": "Create an asynchronous APM export",
        "description": "Creates a tenant-scoped manifest for a range no longer than 31 days. This endpoint never returns storage references, credentials, or a download URL.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:apm"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApmExportCreate"
              },
              "example": {
                "project_id": "checkout",
                "environment_id": "prod",
                "from": "2026-09-20T00:00:00.000Z",
                "to": "2026-09-21T00:00:00.000Z",
                "fact_names": [
                  "apm_spans",
                  "apm_trace_summaries"
                ],
                "purpose": "incident_review",
                "expires_in_seconds": 3600
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pending export manifest.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmExportStatus"
                },
                "example": {
                  "ok": true,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "export_manifest_id": "550e8400-e29b-41d4-a716-446655440000",
                  "purpose": "incident_review",
                  "fact_names": [
                    "apm_spans",
                    "apm_trace_summaries"
                  ],
                  "state": "pending",
                  "row_count": null,
                  "byte_count": null,
                  "created_at": "2026-09-21T00:00:00.000Z",
                  "expires_at": "2026-09-21T01:00:00.000Z",
                  "completed_at": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "503": {
            "$ref": "#/components/responses/ApmControlUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/exports/{exportId}": {
      "get": {
        "operationId": "v1ApmGetExport",
        "tags": [
          "APM Observability"
        ],
        "summary": "Read asynchronous export status",
        "description": "Returns the safe manifest projection and its real asynchronous state; no object reference or signing credential is exposed.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:export"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmExportIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Export status.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmExportStatus"
                },
                "example": {
                  "ok": true,
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "export_manifest_id": "550e8400-e29b-41d4-a716-446655440000",
                  "purpose": "incident_review",
                  "fact_names": [
                    "apm_spans"
                  ],
                  "state": "running",
                  "row_count": null,
                  "byte_count": null,
                  "created_at": "2026-09-21T00:00:00.000Z",
                  "expires_at": "2026-09-21T01:00:00.000Z",
                  "completed_at": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmControlUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/exports/{exportId}/download-grant": {
      "post": {
        "operationId": "v1ApmCreateExportDownloadGrant",
        "tags": [
          "APM Observability"
        ],
        "summary": "Request an export download grant",
        "description": "Reports readiness only. Collector signing authority is intentionally unavailable, so this launch contract never returns a URL, object reference, bearer token, or credential.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:export"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmExportIdPath"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApmDownloadGrantRequest"
              },
              "example": {
                "expires_in_seconds": 60
              }
            }
          }
        },
        "responses": {
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "409": {
            "description": "Export is not ready or failed.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmGrantUnavailable"
                },
                "example": {
                  "ok": false,
                  "available": false,
                  "reason": "export_not_ready",
                  "state": "running",
                  "requestId": "req-test"
                }
              }
            }
          },
          "410": {
            "description": "Export expired or was deleted.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmGrantUnavailable"
                },
                "example": {
                  "ok": false,
                  "available": false,
                  "reason": "export_expired",
                  "state": "expired",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "description": "Signing authority or control-plane storage is unavailable.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ApmGrantUnavailable"
                    },
                    {
                      "$ref": "#/components/schemas/ApmUnavailable"
                    }
                  ]
                },
                "example": {
                  "ok": false,
                  "available": false,
                  "reason": "download_signing_unavailable",
                  "state": "available",
                  "requestId": "req-test"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/privacy/subjects/{subjectHandle}/exports/{requestId}": {
      "get": {
        "operationId": "v1ApmGetPrivacyExport",
        "tags": [
          "APM Observability"
        ],
        "summary": "Read subject export status",
        "description": "Hashes the opaque subject handle before repository access and returns only the safe export projection. The cleartext handle and its hash are never returned.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:privacy_request"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmSubjectHandlePath"
          },
          {
            "$ref": "#/components/parameters/ApmPrivacyRequestIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Subject export status.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmPrivacyExportStatus"
                },
                "example": {
                  "ok": true,
                  "privacy_request_id": "dsar-42",
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "export_manifest_id": "550e8400-e29b-41d4-a716-446655440000",
                  "purpose": "privacy_subject_export",
                  "fact_names": [
                    "apm_spans"
                  ],
                  "state": "pending",
                  "row_count": null,
                  "byte_count": null,
                  "created_at": "2026-09-21T00:00:00.000Z",
                  "expires_at": "2026-09-21T01:00:00.000Z",
                  "completed_at": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmControlUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/apm/privacy/subjects/{subjectHandle}/erasures/{requestId}": {
      "get": {
        "operationId": "v1ApmGetPrivacyErasure",
        "tags": [
          "APM Observability"
        ],
        "summary": "Read subject erasure status",
        "description": "Returns aggregate deletion progress only. Subject identifiers, per-store object references, SQL, credentials, and PII are never exposed.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:privacy_request"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApmSubjectHandlePath"
          },
          {
            "$ref": "#/components/parameters/ApmPrivacyRequestIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Subject erasure status.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmErasureStatus"
                },
                "example": {
                  "ok": true,
                  "privacy_request_id": "dsar-42",
                  "project_id": "checkout",
                  "environment_id": "prod",
                  "state": "running",
                  "attempts": 1,
                  "created_at": "2026-09-21T00:00:00.000Z",
                  "updated_at": "2026-09-21T00:05:00.000Z",
                  "completed_at": null,
                  "stores": {
                    "count": 3,
                    "pending": 1,
                    "running": 1,
                    "succeeded": 1,
                    "failed": 0
                  },
                  "deleted": {
                    "rows": 120,
                    "objects": 2,
                    "bytes": 4096
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ApmInvalid"
          },
          "401": {
            "$ref": "#/components/responses/ApmUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ApmForbidden"
          },
          "404": {
            "$ref": "#/components/responses/ApmNotFound"
          },
          "429": {
            "$ref": "#/components/responses/ApmRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ApmControlUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/traces": {
      "post": {
        "operationId": "v1ApmIngestTraces",
        "tags": [
          "APM Ingest"
        ],
        "summary": "Ingest an OTLP/HTTP trace export",
        "description": "OTLP/HTTP trace ingestion. Any OpenTelemetry SDK or Collector OTLP/HTTP exporter can send to it: the body is a proto3-JSON `ExportTraceServiceRequest` (`application/json`) or its protobuf encoding (`application/x-protobuf`), and any other Content-Type is rejected with `415`. `Content-Encoding: gzip` or `deflate` is accepted. The collector hosts reject a wire body above 1 MiB, a decompressed body above 4 MiB, and a request with more than 500 spans, so size your exporter batches accordingly.\n\nAuthenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `traces`; a server API key is never accepted.\n\n**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.\n\nPer-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.\n\n**Errors.** Error bodies are JSON. The collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason); the dedicated ingest hosts answer `{\"code\": <HTTP status>, \"message\": \"...\", \"error\": \"<reason>\"}`. The reason strings (for example `apm_auth_invalid`, `apm_rate_limited`) are the same on both, so branch on the HTTP status and the reason, never on `code`. Success bodies follow the OTLP wire contract and use the request's own media type.",
        "security": [
          {
            "ApmIngestKeyAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://ingest.noxtica.com",
            "description": "Production ingest host"
          },
          {
            "url": "https://collect.noxtica.com",
            "description": "Production collector host (same path and credential)"
          }
        ],
        "parameters": [
          {
            "name": "Content-Encoding",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "identity",
                "gzip",
                "deflate"
              ]
            },
            "description": "Defaults to `identity` (uncompressed) when absent. Any other value is rejected with 415."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OtlpExportTraceServiceRequest"
              },
              "example": {
                "resourceSpans": [
                  {
                    "resource": {
                      "attributes": [
                        {
                          "key": "service.name",
                          "value": {
                            "stringValue": "checkout-api"
                          }
                        }
                      ]
                    },
                    "scopeSpans": [
                      {
                        "scope": {
                          "name": "@noxtica/otel-shim",
                          "version": "1.0.0"
                        },
                        "spans": [
                          {
                            "traceId": "AAAAAAAAAAAAAAAAAAAAAA==",
                            "spanId": "AAAAAAAAAAA=",
                            "name": "GET /health",
                            "kind": "SPAN_KIND_SERVER",
                            "startTimeUnixNano": "1700000000000000000",
                            "endTimeUnixNano": "1700000000010000000",
                            "status": {
                              "code": "STATUS_CODE_OK"
                            }
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            },
            "application/x-protobuf": {
              "schema": {
                "type": "string",
                "format": "binary",
                "description": "Protobuf-encoded `ExportTraceServiceRequest` — the default wire format most OpenTelemetry SDK/Collector OTLP exporters send."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Export accepted. An empty object means every span was accepted; `partialSuccess` reports spans dropped during validation without failing the whole request.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpTraceIngestResult"
                },
                "examples": {
                  "all_spans_accepted": {
                    "summary": "Every span accepted.",
                    "value": {}
                  },
                  "spans_partially_rejected": {
                    "summary": "Some spans failed validation.",
                    "value": {
                      "partialSuccess": {
                        "rejectedSpans": "3",
                        "errorMessage": "One or more spans failed validation and were not accepted"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`apm_malformed_otlp_json`/`apm_malformed_otlp_protobuf` — the body does not decode as a valid `ExportTraceServiceRequest` in its declared Content-Type.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "malformed_json": {
                    "summary": "Body is not a valid proto3-JSON ExportTraceServiceRequest.",
                    "value": {
                      "code": 3,
                      "message": "Malformed OTLP JSON: invalid_json",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_malformed_otlp_json",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "malformed_protobuf": {
                    "summary": "Body does not decode as a valid ExportTraceServiceRequest protobuf message.",
                    "value": {
                      "code": 3,
                      "message": "Malformed OTLP protobuf: decode_failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_malformed_otlp_protobuf",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "On the collector hosts always `Bearer realm=\"noxtica-otlp\", charset=\"UTF-8\"`; the ingest hosts do not send it."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "auth_missing": {
                    "summary": "No Authorization header.",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_missing",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "auth_invalid": {
                    "summary": "Credential does not resolve to an active key.",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_invalid",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "auth_expired": {
                    "summary": "now is outside [effective_at, expires_at).",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_expired",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The credential is active but not authorized for trace ingest.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "scope_denied": {
                    "summary": "\"traces\" is not in the credential's allowed_signals.",
                    "value": {
                      "code": 7,
                      "message": "Trace ingest is not authorized",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_scope_denied",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "purpose_disabled": {
                    "summary": "The credential's purpose_enabled flag is 0.",
                    "value": {
                      "code": 7,
                      "message": "Trace ingest is not authorized",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_purpose_disabled",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The wire or decompressed body exceeds its byte cap, or the request carries more than 500 spans.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "body_too_large": {
                    "summary": "Wire body exceeds the 1 MiB cap.",
                    "value": {
                      "code": 8,
                      "message": "Request body exceeds the OTLP JSON limit",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_body_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "decompressed_too_large": {
                    "summary": "Decompressed body exceeds the 4 MiB cap.",
                    "value": {
                      "code": 8,
                      "message": "Decompressed request body exceeds the OTLP JSON limit",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_body_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "batch_too_large": {
                    "summary": "More than 500 spans in one request.",
                    "value": {
                      "code": 8,
                      "message": "A request may contain at most 500 spans",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_batch_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not `application/json`, or Content-Encoding is set to a value other than `identity`, `gzip`, or `deflate`.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "unsupported_content_type": {
                    "summary": "Content-Type header is missing or not application/json.",
                    "value": {
                      "code": 3,
                      "message": "Content-Type must be application/json",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_unsupported_media_type",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "unsupported_content_encoding": {
                    "summary": "Content-Encoding is set to something other than identity/gzip/deflate.",
                    "value": {
                      "code": 3,
                      "message": "Unsupported Content-Encoding",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_unsupported_media_type",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Trace ingest rate limit exceeded (600 requests / 60s per `tenantId:projectId`).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the bucket allows another request."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 8,
                  "message": "Trace ingest rate limit exceeded",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_rate_limited",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "true"
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Always `5` on this status."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "authority_unavailable": {
                    "summary": "Tenant authority scope confirmation failed.",
                    "value": {
                      "code": 14,
                      "message": "Authority unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_authority_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  },
                  "queue_unavailable": {
                    "summary": "The durable ingest queue binding is missing or rejected the message.",
                    "value": {
                      "code": 14,
                      "message": "Trace ingest queue is unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_queue_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "partial"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/metrics": {
      "post": {
        "operationId": "v1ApmIngestMetrics",
        "tags": [
          "APM Ingest"
        ],
        "summary": "Ingest an OTLP/HTTP metrics export",
        "description": "OTLP/HTTP metrics ingestion: a proto3-JSON `ExportMetricsServiceRequest` (`application/json`) or its protobuf encoding (`application/x-protobuf`); any other Content-Type is rejected with `415`. `Content-Encoding: gzip` or `deflate` is accepted. The collector hosts reject a wire body above 1 MiB, a decompressed body above 4 MiB, and a request with more than 500 data points across every metric's `gauge`, `sum`, `histogram`, `exponentialHistogram` and `summary` arrays.\n\nAuthenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `metrics`.\n\n**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.\n\nPer-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.\n\n**Errors.** Error bodies are JSON. The collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason); the dedicated ingest hosts answer `{\"code\": <HTTP status>, \"message\": \"...\", \"error\": \"<reason>\"}`. The reason strings (for example `apm_auth_invalid`, `apm_rate_limited`) are the same on both, so branch on the HTTP status and the reason, never on `code`. Success bodies follow the OTLP wire contract and use the request's own media type.",
        "security": [
          {
            "ApmIngestKeyAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://ingest.noxtica.com",
            "description": "Production ingest host"
          },
          {
            "url": "https://collect.noxtica.com",
            "description": "Production collector host (same path and credential)"
          }
        ],
        "parameters": [
          {
            "name": "Content-Encoding",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "identity",
                "gzip",
                "deflate"
              ]
            },
            "description": "Defaults to `identity` (uncompressed) when absent. Any other value is rejected with 415."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OtlpExportMetricsServiceRequest"
              },
              "example": {
                "resourceMetrics": [
                  {
                    "resource": {
                      "attributes": [
                        {
                          "key": "service.name",
                          "value": {
                            "stringValue": "checkout-api"
                          }
                        }
                      ]
                    },
                    "scopeMetrics": [
                      {
                        "scope": {
                          "name": "@noxtica/otel-shim",
                          "version": "1.0.0"
                        },
                        "metrics": [
                          {
                            "name": "http.server.duration",
                            "unit": "ms",
                            "gauge": {
                              "dataPoints": [
                                {
                                  "timeUnixNano": "1700000000000000000",
                                  "asDouble": 12.5
                                }
                              ]
                            }
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            },
            "application/x-protobuf": {
              "schema": {
                "type": "string",
                "format": "binary",
                "description": "Protobuf-encoded `ExportMetricsServiceRequest` — the default wire format most OpenTelemetry SDK/Collector OTLP exporters send."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Export accepted. An empty object means every data point was accepted; `partialSuccess` reports data points dropped during validation without failing the whole request.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpMetricsIngestResult"
                },
                "examples": {
                  "all_points_accepted": {
                    "summary": "Every data point accepted.",
                    "value": {}
                  },
                  "points_partially_rejected": {
                    "summary": "Some data points failed validation.",
                    "value": {
                      "partialSuccess": {
                        "rejectedDataPoints": "3",
                        "errorMessage": "One or more metric data points failed validation and were not accepted"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`apm_malformed_metrics_payload` — the body does not decode as a valid `ExportMetricsServiceRequest` in its declared Content-Type.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 3,
                  "message": "Malformed OTLP metrics payload: decode_failed",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_malformed_metrics_payload",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "false"
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "On the collector hosts always `Bearer realm=\"noxtica-otlp\", charset=\"UTF-8\"`; the ingest hosts do not send it."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "auth_missing": {
                    "summary": "No Authorization header.",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_missing",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "auth_invalid": {
                    "summary": "Credential does not resolve to an active key.",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_invalid",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "auth_expired": {
                    "summary": "now is outside [effective_at, expires_at).",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_expired",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The credential is active but not authorized for metrics ingest.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "scope_denied": {
                    "summary": "\"metrics\" is not in the credential's allowed_signals.",
                    "value": {
                      "code": 7,
                      "message": "Metrics ingest is not authorized",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_scope_denied",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "purpose_disabled": {
                    "summary": "The credential's purpose_enabled flag is 0.",
                    "value": {
                      "code": 7,
                      "message": "Metrics ingest is not authorized",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_purpose_disabled",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The wire or decompressed body exceeds its byte cap, or the request carries more than 500 data points.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "body_too_large": {
                    "summary": "Wire body exceeds the 1 MiB cap.",
                    "value": {
                      "code": 8,
                      "message": "Request body exceeds the OTLP payload limit",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_body_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "decompressed_too_large": {
                    "summary": "Decompressed body exceeds the 4 MiB cap.",
                    "value": {
                      "code": 8,
                      "message": "Decompressed request body exceeds the OTLP payload limit",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_body_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "batch_too_large": {
                    "summary": "More than 500 data points in one request.",
                    "value": {
                      "code": 8,
                      "message": "A request may contain at most 500 data points",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_batch_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not `application/json` or `application/x-protobuf`, or Content-Encoding is set to a value other than `identity`, `gzip`, or `deflate`.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 3,
                  "message": "Content-Type must be application/json or application/x-protobuf",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_unsupported_media_type",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "false"
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Metrics ingest rate limit exceeded (600 requests / 60s per `tenantId:projectId`).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the bucket allows another request."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 8,
                  "message": "Metrics ingest rate limit exceeded",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_rate_limited",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "true"
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Always `5` on this status."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "authority_unavailable": {
                    "summary": "Tenant authority scope confirmation failed.",
                    "value": {
                      "code": 14,
                      "message": "Authority unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_authority_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  },
                  "queue_unavailable": {
                    "summary": "The durable ingest queue binding is missing or rejected the message.",
                    "value": {
                      "code": 14,
                      "message": "Metrics ingest queue is unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_queue_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "partial"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/logs": {
      "post": {
        "operationId": "v1ApmIngestLogs",
        "tags": [
          "APM Ingest"
        ],
        "summary": "Ingest an OTLP/HTTP logs export",
        "description": "OTLP/HTTP logs ingestion: a proto3-JSON `ExportLogsServiceRequest` (`application/json`) or its protobuf encoding (`application/x-protobuf`); any other Content-Type is rejected with `415`. `Content-Encoding: gzip` or `deflate` is accepted. The collector hosts reject a wire body above 1 MiB, a decompressed body above 4 MiB, and a request with more than 500 log records across every `resourceLogs[].scopeLogs[].logRecords[]`.\n\nAuthenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `logs`.\n\n**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.\n\nPer-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.\n\n**Errors.** Error bodies are JSON. The collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason); the dedicated ingest hosts answer `{\"code\": <HTTP status>, \"message\": \"...\", \"error\": \"<reason>\"}`. The reason strings (for example `apm_auth_invalid`, `apm_rate_limited`) are the same on both, so branch on the HTTP status and the reason, never on `code`. Success bodies follow the OTLP wire contract and use the request's own media type.",
        "security": [
          {
            "ApmIngestKeyAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://ingest.noxtica.com",
            "description": "Production ingest host"
          },
          {
            "url": "https://collect.noxtica.com",
            "description": "Production collector host (same path and credential)"
          }
        ],
        "parameters": [
          {
            "name": "Content-Encoding",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "identity",
                "gzip",
                "deflate"
              ]
            },
            "description": "Defaults to `identity` (uncompressed) when absent. Any other value is rejected with 415."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OtlpExportLogsServiceRequest"
              },
              "example": {
                "resourceLogs": [
                  {
                    "resource": {
                      "attributes": [
                        {
                          "key": "service.name",
                          "value": {
                            "stringValue": "checkout-api"
                          }
                        }
                      ]
                    },
                    "scopeLogs": [
                      {
                        "scope": {
                          "name": "@noxtica/otel-shim",
                          "version": "1.0.0"
                        },
                        "logRecords": [
                          {
                            "timeUnixNano": "1700000000000000000",
                            "severityNumber": 9,
                            "severityText": "INFO",
                            "body": {
                              "stringValue": "order placed"
                            }
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            },
            "application/x-protobuf": {
              "schema": {
                "type": "string",
                "format": "binary",
                "description": "Protobuf-encoded `ExportLogsServiceRequest` — the default wire format most OpenTelemetry SDK/Collector OTLP exporters send."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Export accepted. An empty object means every log record was accepted; `partialSuccess` reports log records dropped during validation without failing the whole request.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpLogsIngestResult"
                },
                "examples": {
                  "all_records_accepted": {
                    "summary": "Every log record accepted.",
                    "value": {}
                  },
                  "records_partially_rejected": {
                    "summary": "Some log records failed validation.",
                    "value": {
                      "partialSuccess": {
                        "rejectedLogRecords": "3",
                        "errorMessage": "One or more log records failed validation and were not accepted"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`apm_malformed_logs_payload` — the body does not decode as a valid `ExportLogsServiceRequest` in its declared Content-Type.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 3,
                  "message": "Malformed OTLP logs payload: decode_failed",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_malformed_logs_payload",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "false"
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "On the collector hosts always `Bearer realm=\"noxtica-otlp\", charset=\"UTF-8\"`; the ingest hosts do not send it."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "auth_missing": {
                    "summary": "No Authorization header.",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_missing",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "auth_invalid": {
                    "summary": "Credential does not resolve to an active key.",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_invalid",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "auth_expired": {
                    "summary": "now is outside [effective_at, expires_at).",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_expired",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The credential is active but not authorized for logs ingest.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "scope_denied": {
                    "summary": "\"logs\" is not in the credential's allowed_signals.",
                    "value": {
                      "code": 7,
                      "message": "Logs ingest is not authorized",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_scope_denied",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "purpose_disabled": {
                    "summary": "The credential's purpose_enabled flag is 0.",
                    "value": {
                      "code": 7,
                      "message": "Logs ingest is not authorized",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_purpose_disabled",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The wire or decompressed body exceeds its byte cap, or the request carries more than 500 log records.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "body_too_large": {
                    "summary": "Wire body exceeds the 1 MiB cap.",
                    "value": {
                      "code": 8,
                      "message": "Request body exceeds the OTLP payload limit",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_body_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "decompressed_too_large": {
                    "summary": "Decompressed body exceeds the 4 MiB cap.",
                    "value": {
                      "code": 8,
                      "message": "Decompressed request body exceeds the OTLP payload limit",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_body_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "batch_too_large": {
                    "summary": "More than 500 log records in one request.",
                    "value": {
                      "code": 8,
                      "message": "A request may contain at most 500 log records",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_batch_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not `application/json` or `application/x-protobuf`, or Content-Encoding is set to a value other than `identity`, `gzip`, or `deflate`.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 3,
                  "message": "Content-Type must be application/json or application/x-protobuf",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_unsupported_media_type",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "false"
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Logs ingest rate limit exceeded (600 requests / 60s per `tenantId:projectId`).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the bucket allows another request."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 8,
                  "message": "Logs ingest rate limit exceeded",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_rate_limited",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "true"
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Always `5` on this status."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "authority_unavailable": {
                    "summary": "Tenant authority scope confirmation failed.",
                    "value": {
                      "code": 14,
                      "message": "Authority unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_authority_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  },
                  "queue_unavailable": {
                    "summary": "The durable ingest queue binding is missing or rejected the message.",
                    "value": {
                      "code": 14,
                      "message": "Logs ingest queue is unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_queue_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "partial"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/profiles": {
      "post": {
        "operationId": "v1ApmIngestProfiles",
        "tags": [
          "APM Ingest"
        ],
        "summary": "Ingest an OTLP/HTTP JSON profiles export",
        "description": "OTLP/HTTP JSON profiles ingestion, a proto3-JSON `ProfilesData` (`opentelemetry.proto.profiles.v1development`; profiling is still an OpenTelemetry development signal). Unlike traces, metrics and logs, `application/json` is the only media type on the collector hosts: protobuf is rejected with `415`. `Content-Encoding: gzip` or `deflate` is accepted. The collector hosts reject a wire body above 2 MiB, a decompressed body above 8 MiB, and a request with more than 20 profiles across every `resourceProfiles[].scopeProfiles[].profiles[]`. The dedicated ingest hosts additionally accept the multipart upload format sent by the Noxtica host agent; that format is internal to the agent and not part of this contract.\n\nEach accepted profile's raw bytes are stored before the row is queued, so if profile storage is unavailable the whole request fails with `503 apm_profile_storage_unavailable` even though authentication and validation succeeded.\n\nAuthenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `profiles`.\n\n**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.\n\nPer-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.\n\n**Errors.** Error bodies are JSON. The collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason); the dedicated ingest hosts answer `{\"code\": <HTTP status>, \"message\": \"...\", \"error\": \"<reason>\"}`. The reason strings (for example `apm_auth_invalid`, `apm_rate_limited`) are the same on both, so branch on the HTTP status and the reason, never on `code`. Success bodies follow the OTLP wire contract and use the request's own media type.",
        "security": [
          {
            "ApmIngestKeyAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://ingest.noxtica.com",
            "description": "Production ingest host"
          },
          {
            "url": "https://collect.noxtica.com",
            "description": "Production collector host (same path and credential)"
          }
        ],
        "parameters": [
          {
            "name": "Content-Encoding",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "identity",
                "gzip",
                "deflate"
              ]
            },
            "description": "Defaults to `identity` (uncompressed) when absent. Any other value is rejected with 415."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OtlpProfilesData"
              },
              "example": {
                "resourceProfiles": [
                  {
                    "resource": {
                      "attributes": [
                        {
                          "key": "service.name",
                          "value": {
                            "stringValue": "checkout-api"
                          }
                        }
                      ]
                    },
                    "scopeProfiles": [
                      {
                        "scope": {
                          "name": "@noxtica/otel-shim",
                          "version": "1.0.0"
                        },
                        "profiles": [
                          {
                            "profileId": "AAAAAAAAAAAAAAAAAAAAAA==",
                            "startTimeUnixNano": "1700000000000000000",
                            "endTimeUnixNano": "1700000001000000000"
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Export accepted. An empty object means every profile was accepted and persisted; `partialSuccess` reports profiles dropped during validation, or stored with an unrecognized `profileType`, without failing the whole request.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpProfilesIngestResult"
                },
                "examples": {
                  "all_profiles_accepted": {
                    "summary": "Every profile accepted.",
                    "value": {}
                  },
                  "profiles_partially_rejected": {
                    "summary": "Some profiles failed validation.",
                    "value": {
                      "partialSuccess": {
                        "rejectedProfiles": "1",
                        "errorMessage": "1 profile(s) failed validation and were not accepted"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`apm_malformed_profile_json` — the body is not a valid proto3-JSON `ProfilesData`.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 3,
                  "message": "Malformed OTLP profiles JSON: invalid_resource_profiles",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_malformed_profile_json",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "false"
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "On the collector hosts always `Bearer realm=\"noxtica-otlp\", charset=\"UTF-8\"`; the ingest hosts do not send it."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "auth_missing": {
                    "summary": "No Authorization header.",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_missing",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "auth_invalid": {
                    "summary": "Credential does not resolve to an active key.",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_invalid",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "auth_expired": {
                    "summary": "now is outside [effective_at, expires_at).",
                    "value": {
                      "code": 16,
                      "message": "Authentication failed",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_auth_expired",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The credential is active but not authorized for profile ingest.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "scope_denied": {
                    "summary": "\"profiles\" is not in the credential's allowed_signals.",
                    "value": {
                      "code": 7,
                      "message": "Profile ingest is not authorized",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_scope_denied",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "purpose_disabled": {
                    "summary": "The credential's purpose_enabled flag is 0.",
                    "value": {
                      "code": 7,
                      "message": "Profile ingest is not authorized",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_purpose_disabled",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The wire or decompressed body exceeds its byte cap, or the request carries more than 20 profiles.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "body_too_large": {
                    "summary": "Wire body exceeds the 2 MiB cap.",
                    "value": {
                      "code": 8,
                      "message": "Request body exceeds the OTLP profiles limit",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_body_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "decompressed_too_large": {
                    "summary": "Decompressed body exceeds the 8 MiB cap.",
                    "value": {
                      "code": 8,
                      "message": "Decompressed request body exceeds the OTLP profiles limit",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_body_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  },
                  "batch_too_large": {
                    "summary": "More than 20 profiles in one request.",
                    "value": {
                      "code": 8,
                      "message": "A request may contain at most 20 profiles",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_batch_too_large",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "false"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not `application/json` (protobuf is never accepted on this route), or Content-Encoding is set to a value other than `identity`, `gzip`, or `deflate`.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 3,
                  "message": "Content-Type must be application/json",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_unsupported_media_type",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "false"
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Profile ingest rate limit exceeded (600 requests / 60s per `tenantId:projectId`).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the bucket allows another request."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "example": {
                  "code": 8,
                  "message": "Profile ingest rate limit exceeded",
                  "details": [
                    {
                      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                      "reason": "apm_rate_limited",
                      "domain": "ingest.noxtica.com",
                      "metadata": {
                        "requestId": "req-test",
                        "retryable": "true"
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "The tenant authority scope could not be confirmed, the profile object store is unavailable, or the durable ingest queue is unavailable.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Always `5` on this status."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OtlpStatus"
                },
                "examples": {
                  "authority_unavailable": {
                    "summary": "Tenant authority scope confirmation failed.",
                    "value": {
                      "code": 14,
                      "message": "Authority unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_authority_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  },
                  "profile_storage_unavailable": {
                    "summary": "The profile-payload object store binding is missing or rejected the write.",
                    "value": {
                      "code": 14,
                      "message": "Profile object storage is unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_profile_storage_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  },
                  "queue_unavailable": {
                    "summary": "The durable ingest queue binding is missing or rejected the message.",
                    "value": {
                      "code": 14,
                      "message": "Profile ingest queue is unavailable",
                      "details": [
                        {
                          "@type": "type.googleapis.com/google.rpc.ErrorInfo",
                          "reason": "apm_queue_unavailable",
                          "domain": "ingest.noxtica.com",
                          "metadata": {
                            "requestId": "req-test",
                            "retryable": "true"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "partial"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/deployments": {
      "post": {
        "operationId": "v1ApmIngestDeploymentMarker",
        "tags": [
          "APM Ingest"
        ],
        "summary": "Ingest a deployment marker",
        "description": "Records one service deployment (a release marker) as a small single-row JSON payload. It is not an OTLP batch, so its request and response shapes are its own rather than the OTLP contract used by traces, metrics, logs and profiles. Only `application/json` is accepted (otherwise `415`), compression is not supported, and the request body is limited to 16 KiB.\n\n`environment` is supplied by the caller but must equal the `environmentId` the ingestion key is bound to: the key's organisation, project and environment decide where a marker lands, and a mismatched value is rejected with `403 apm_environment_mismatch` instead of redirecting the marker. `version` is also the release id.\n\nAuthenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `deployments`.\n\n**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.\n\nPer-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.",
        "security": [
          {
            "ApmIngestKeyAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://ingest.noxtica.com",
            "description": "Production ingest host"
          },
          {
            "url": "https://collect.noxtica.com",
            "description": "Production collector host (same path and credential)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApmDeploymentMarkerRequest"
              },
              "example": {
                "service": "checkout-api",
                "version": "2026.09.25-1",
                "environment": "prod",
                "commit": "4f3c2a1",
                "timestamp": "2026-09-25T00:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Deployment marker accepted and queued to the warehouse.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmDeploymentMarkerAccepted"
                },
                "example": {
                  "ok": true,
                  "deployment_id": "dep-test",
                  "service": "checkout-api",
                  "version": "2026.09.25-1",
                  "environment": "prod",
                  "commit": "4f3c2a1",
                  "timestamp": "2026-09-25T00:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "`apm_malformed_json` — the body is not valid JSON; or `apm_invalid_payload` — a required field is missing, an unknown field is present, or a field fails its own pattern/format check (e.g. `commit` not a 7-64 char lowercase-hex SHA, or `timestamp` after the receipt time).",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmDeploymentError"
                },
                "examples": {
                  "malformed_json": {
                    "summary": "Body is not valid JSON.",
                    "value": {
                      "ok": false,
                      "error": "apm_malformed_json",
                      "message": "Malformed JSON body",
                      "requestId": "req-test"
                    }
                  },
                  "invalid_payload": {
                    "summary": "A field failed validation.",
                    "value": {
                      "ok": false,
                      "error": "apm_invalid_payload",
                      "message": "Invalid deployment marker payload",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "On the collector hosts always `Bearer realm=\"noxtica-otlp\", charset=\"UTF-8\"`; the ingest hosts do not send it."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmDeploymentError"
                },
                "examples": {
                  "auth_missing": {
                    "summary": "No Authorization header.",
                    "value": {
                      "ok": false,
                      "error": "apm_auth_missing",
                      "message": "Authentication failed",
                      "requestId": "req-test"
                    }
                  },
                  "auth_invalid": {
                    "summary": "Credential does not resolve to an active key.",
                    "value": {
                      "ok": false,
                      "error": "apm_auth_invalid",
                      "message": "Authentication failed",
                      "requestId": "req-test"
                    }
                  },
                  "auth_expired": {
                    "summary": "now is outside [effective_at, expires_at).",
                    "value": {
                      "ok": false,
                      "error": "apm_auth_expired",
                      "message": "Authentication failed",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The credential is active but not authorized for deployment marker ingest, or the request's `environment` does not match the credential's own `environmentId` scope.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmDeploymentError"
                },
                "examples": {
                  "scope_denied": {
                    "summary": "\"deployments\" is not in the credential's allowed_signals.",
                    "value": {
                      "ok": false,
                      "error": "apm_scope_denied",
                      "message": "Deployment marker ingest is not authorized",
                      "requestId": "req-test"
                    }
                  },
                  "purpose_disabled": {
                    "summary": "The credential's purpose_enabled flag is 0.",
                    "value": {
                      "ok": false,
                      "error": "apm_purpose_disabled",
                      "message": "Deployment marker ingest is not authorized",
                      "requestId": "req-test"
                    }
                  },
                  "environment_mismatch": {
                    "summary": "Request environment does not match the credential's own environment scope.",
                    "value": {
                      "ok": false,
                      "error": "apm_environment_mismatch",
                      "message": "environment does not match the authenticated key scope",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The request body exceeds the 16 KiB cap.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmDeploymentError"
                },
                "example": {
                  "ok": false,
                  "error": "apm_body_too_large",
                  "message": "Request body exceeds the deployment marker limit",
                  "requestId": "req-test"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not `application/json`.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmDeploymentError"
                },
                "example": {
                  "ok": false,
                  "error": "apm_unsupported_media_type",
                  "message": "Content-Type must be application/json",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "description": "Deployment marker rate limit exceeded (120 requests / 60s per `tenantId:projectId`).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the bucket allows another request."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmDeploymentError"
                },
                "example": {
                  "ok": false,
                  "error": "apm_rate_limited",
                  "message": "Deployment marker rate limit exceeded",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "description": "The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Always `5` on this status."
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApmDeploymentError"
                },
                "examples": {
                  "authority_unavailable": {
                    "summary": "Tenant authority scope confirmation failed.",
                    "value": {
                      "ok": false,
                      "error": "apm_authority_unavailable",
                      "message": "Authority unavailable",
                      "requestId": "req-test"
                    }
                  },
                  "queue_unavailable": {
                    "summary": "The durable ingest queue binding is missing or rejected the message.",
                    "value": {
                      "ok": false,
                      "error": "apm_queue_unavailable",
                      "message": "Deployment marker queue is unavailable",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          }
        },
        "x-noxtica-status": "partial"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "Health and Keys"
        ],
        "summary": "Collector health probe",
        "description": "Unauthenticated liveness and readiness probe of the collector. It performs no datastore\nconnectivity check: it answers whether the collector worker itself is alive.\n\n`status` is `ok`, or `degraded` while a background dead-letter watch reports unprocessed\nitems (still HTTP `200`). HTTP `503` is returned only when the deployment refuses\nreadiness; the body then also has `status: degraded`. The body can carry additional\ndiagnostic members; ignore members you do not recognise. `version` is the deployed\ncollector version, or `unknown` when none is reported.\n\nThe response sets no CORS headers, so call it from a server or a monitor, never from a\nbrowser. Excessive probing from one source address is rate limited (`429` with\n`Retry-After`). For incident history see the public status page.",
        "security": [],
        "responses": {
          "200": {
            "description": "The collector is alive.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-cache"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                },
                "example": {
                  "status": "ok",
                  "version": "1.0.0",
                  "timestamp": "2026-10-27T10:00:00.000Z"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/v1_RateLimited"
          },
          "503": {
            "description": "The deployment refuses readiness. `status` is `degraded`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                },
                "example": {
                  "status": "degraded",
                  "version": "1.0.0",
                  "timestamp": "2026-10-27T10:00:00.000Z"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/.well-known/noxtica/jwks.json": {
      "get": {
        "operationId": "getCollectorJwks",
        "tags": [
          "Health and Keys"
        ],
        "summary": "Public signing keys (JWKS)",
        "description": "The public keys Noxtica signs its envelopes with, as a standard JSON Web Key Set, so your\nbackend can verify a signed envelope offline, by `kid`, without calling Noxtica: the signed\npolicy and result envelopes of the browser integration and, unless a dedicated passport key\nis published (see `GET /.well-known/noxtica/agent-passport-jwks.json`), Agent Passports.\n\nThe document is unauthenticated and public by design: only the public members `kty`,\n`crv`, `x`, `y` and the descriptors `kid`, `use` and `alg` are ever served. During a key\nrotation it holds both the current and the incoming key (current first), so a verifier can\nresolve the new `kid` before the signer cuts over. The response is cacheable for 5 minutes.\nThe route answers `404` when signing is not configured for the deployment.",
        "security": [],
        "responses": {
          "200": {
            "description": "The public key set.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "public, max-age=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Jwks"
                },
                "example": {
                  "keys": [
                    {
                      "kty": "EC",
                      "crv": "P-256",
                      "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
                      "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
                      "kid": "nox-2026-01",
                      "use": "sig",
                      "alg": "ES256"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Signing is not configured for this deployment. The body is `{\"error\":\"Not found\"}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                },
                "example": {
                  "error": "Not found"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/.well-known/noxtica/agent-passport-jwks.json": {
      "get": {
        "operationId": "getAgentPassportJwks",
        "tags": [
          "Health and Keys"
        ],
        "summary": "Public Agent Passport signing keys (JWKS)",
        "description": "The public key of the dedicated Agent Passport signing key, when one is provisioned, as a\nstandard JSON Web Key Set. It is deliberately a separate document from\n`GET /.well-known/noxtica/jwks.json` so a result verifier that fetches only the main\ndocument can never accept a passport. Use this document, and only for Agent Passports.\n\nThe route answers `404` when no dedicated passport key exists; passports are then signed\nunder a key published on the main JWKS. During a rotation both the current and the\nincoming key are published (current first). The document is unauthenticated, public by\ndesign and cacheable for 5 minutes.",
        "security": [],
        "responses": {
          "200": {
            "description": "The public key set of the dedicated passport signing key.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "public, max-age=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Jwks"
                },
                "example": {
                  "keys": [
                    {
                      "kty": "EC",
                      "crv": "P-256",
                      "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
                      "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
                      "kid": "nox-passport-2026-01",
                      "use": "sig",
                      "alg": "ES256"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No dedicated passport key is configured. The body is `{\"error\":\"Not found\"}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                },
                "example": {
                  "error": "Not found"
                }
              }
            }
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/coding-agent/findings": {
      "get": {
        "operationId": "codingAgentListFindings",
        "tags": [
          "Coding Agent"
        ],
        "summary": "List recent findings for a coding agent",
        "description": "Reads recent raw signals for your organisation, narrowed to the scopes the coding-agent key\nholds. Results from every scope the key holds are merged: this route has no single required\nscope, and a scope narrows what is read instead of denying the call. At the time of writing\nthe scopes `rum_errors`, `apm_traces`, `apm_errors`, `apm_metrics` and `logs` have a findings\nsource; the other scopes a key can carry (`profiles`, `fingerprint_bot_signals`,\n`kyc_risk_summaries`) add nothing to this route. The `signal` field names the scope a row\ncame from. An empty array is a normal answer; this route never answers `404`.\n\n`since` defaults to 24 hours ago and `limit` to 50 (maximum 200). Create a coding-agent\nkey in the Backoffice under Integrations & API, Coding agent keys; keys start with `cak_`.",
        "security": [
          {
            "CodingAgentKeyAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://ingest.noxtica.com",
            "description": "Production"
          }
        ],
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only signals observed at or after this RFC 3339 instant. Defaults to 24 hours ago.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of signals to return. Defaults to 50; values above 200 are clamped to 200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A (possibly empty) array of signals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CodingAgentSignal"
                  }
                },
                "example": [
                  {
                    "signal": "rum_errors",
                    "reference_id": "evt_8f1c2a",
                    "excerpt": "error /checkout",
                    "observed_at": "2026-10-27T09:58:12Z"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "`schema_invalid` — `since` or `limit` could not be parsed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodingAgentError"
                },
                "example": {
                  "code": 400,
                  "message": "invalid query",
                  "error": "schema_invalid"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/CodingAgentUnauthorized"
          },
          "429": {
            "$ref": "#/components/responses/CodingAgentRateLimited"
          },
          "502": {
            "$ref": "#/components/responses/CodingAgentQueryFailed"
          },
          "503": {
            "$ref": "#/components/responses/CodingAgentUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/coding-agent/traces/{trace_id}": {
      "get": {
        "operationId": "codingAgentGetTrace",
        "tags": [
          "Coding Agent"
        ],
        "summary": "Read one trace for a coding agent",
        "description": "Returns the spans of one trace (a narrow projection: attributes, events and links are never\nreturned) from roughly the last 30 days. Requires the `apm_traces` scope on the key;\nwithout it the route answers `403 coding_agent_scope_denied`. An unknown trace id, or one\nthat belongs to another organisation, is a normal `200` with an empty `spans` array: this\nroute never answers `404`.",
        "security": [
          {
            "CodingAgentKeyAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://ingest.noxtica.com",
            "description": "Production"
          }
        ],
        "parameters": [
          {
            "name": "trace_id",
            "in": "path",
            "required": true,
            "description": "The trace id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The trace; `spans` is empty when the id is unknown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodingAgentTrace"
                },
                "example": {
                  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
                  "spans": [
                    {
                      "span_id": "00f067aa0ba902b7",
                      "service_name": "checkout-api",
                      "operation_name": "GET /health",
                      "start_time": "2026-10-27T09:58:00Z",
                      "end_time": "2026-10-27T09:58:00.010Z",
                      "duration_ns": 10000000,
                      "status_code": "STATUS_CODE_OK"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/CodingAgentUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CodingAgentScopeDenied"
          },
          "429": {
            "$ref": "#/components/responses/CodingAgentRateLimited"
          },
          "502": {
            "$ref": "#/components/responses/CodingAgentQueryFailed"
          },
          "503": {
            "$ref": "#/components/responses/CodingAgentUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/coding-agent/errors/{error_group_id}": {
      "get": {
        "operationId": "codingAgentGetErrorGroup",
        "tags": [
          "Coding Agent"
        ],
        "summary": "Read one error group for a coding agent",
        "description": "Returns one error group with its aggregate counts and a bounded list of recent occurrences\n(a narrow projection: stack frames are never returned) from roughly the last 30 days.\nRequires the `apm_errors` scope on the key; without it the route answers\n`403 coding_agent_scope_denied`. `404 coding_agent_error_group_not_found` means the group is\nunknown or belongs to another organisation; the two cases are indistinguishable by design.",
        "security": [
          {
            "CodingAgentKeyAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://ingest.noxtica.com",
            "description": "Production"
          }
        ],
        "parameters": [
          {
            "name": "error_group_id",
            "in": "path",
            "required": true,
            "description": "The error group id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The error group.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodingAgentErrorGroup"
                },
                "example": {
                  "error_group_id": "eg_5d41402abc4b2a76",
                  "first_seen": "2026-10-26T08:00:00Z",
                  "last_seen": "2026-10-27T09:58:12Z",
                  "occurrence_count": 42,
                  "affected_release_ids": [
                    "2026.10.26"
                  ],
                  "occurrences": [
                    {
                      "occurrence_id": "occ_91",
                      "exception_type": "TypeError",
                      "exception_message": "Cannot read properties of undefined",
                      "observed_at": "2026-10-27T09:58:12Z",
                      "release_id": "2026.10.26"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/CodingAgentUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CodingAgentScopeDenied"
          },
          "404": {
            "description": "`coding_agent_error_group_not_found` — the group is unknown or belongs to another organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodingAgentError"
                },
                "example": {
                  "code": 404,
                  "message": "Unknown error group",
                  "error": "coding_agent_error_group_not_found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/CodingAgentRateLimited"
          },
          "502": {
            "$ref": "#/components/responses/CodingAgentQueryFailed"
          },
          "503": {
            "$ref": "#/components/responses/CodingAgentUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/webhooks/event-types": {
      "get": {
        "operationId": "listWebhookEventTypes",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "List the events you can subscribe to",
        "description": "Server-to-server. Returns every event type an endpoint can subscribe to, grouped by product (`kyc`, `kyb`, `kym`, `risk`, `account`, `pulse`), in display order, with the\nname of the `data` schema each carries. Use it to build a subscription from the live catalog instead of copying event names. `identity` is `true` for the events that are delivered\nonly to an endpoint whose signing secret is sealed at rest (every KYC, KYB and KYM session and review event): rotate the secret of an older endpoint once before subscribing it to them.\n\nThe payload of every event is documented in the `webhooks` section of the identity verification API (KYC, KYB, KYM), the device intelligence API (risk and account) and the\nPulse tenant API; `dataType` names the schema.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:webhooks"
            ]
          }
        ],
        "parameters": [
          {
            "name": "product",
            "in": "query",
            "required": false,
            "description": "Return only the events of one product.",
            "schema": {
              "type": "string",
              "enum": [
                "kyc",
                "kyb",
                "kym",
                "risk",
                "account",
                "pulse"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The event catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEventTypesResponse"
                },
                "example": {
                  "ok": true,
                  "products": [
                    "kyc",
                    "kyb",
                    "kym",
                    "risk",
                    "account",
                    "pulse"
                  ],
                  "eventTypes": [
                    {
                      "type": "kyc.session.created",
                      "product": "kyc",
                      "group": "session",
                      "label": "Session created",
                      "description": "A hosted KYC session (invitation) was minted.",
                      "schemaVersion": "1",
                      "dataType": "KycSessionLifecycleData",
                      "identity": true
                    },
                    {
                      "type": "kyc.session.completed",
                      "product": "kyc",
                      "group": "session",
                      "label": "Session finished",
                      "description": "A hosted KYC session reached a terminal outcome: completed, unverified or error.",
                      "schemaVersion": "1",
                      "dataType": "KycSessionCompletedData",
                      "identity": true
                    }
                  ],
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_query_parameters` (an unknown query parameter) or `invalid_product`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "invalid_product",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "List your webhook endpoints",
        "description": "Server-to-server. Returns every webhook endpoint of your organisation, the ones registered in the Backoffice and the ones created through this API (one store). The signing secret is\nnever returned. `limit` is the most endpoints an organisation can register (25).",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:webhooks"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointList"
                },
                "example": {
                  "ok": true,
                  "webhooks": [
                    {
                      "id": "7f3a2c10-5b8e-4d2a-9c61-0e4a7b9d1f35",
                      "url": "https://hooks.example.com/noxtica",
                      "events": [
                        "kyc.session.created",
                        "kyc.session.completed",
                        "kyc.review.decided"
                      ],
                      "description": "Onboarding backend",
                      "enabled": true,
                      "secretSealed": true,
                      "createdAt": 1790760000000,
                      "updatedAt": 1790760000000,
                      "lastDeliveryAt": 1790762445000,
                      "lastStatus": "ok"
                    }
                  ],
                  "limit": 25,
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/WebhookApiUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "Register a webhook endpoint",
        "description": "Server-to-server. Registers an HTTPS endpoint and the events it receives. **The signing secret is returned once, in this response, and never again**: store it now. Use `rotate-secret`\nto replace it later.\n\n`url` must be `https`, without embedded credentials, and must not point at a private, loopback or internal address; the same URL cannot be registered twice. `events` is the exact list\nof event types the endpoint receives (see `GET /v1/webhooks/event-types`): there is no wildcard, no default subscription and an empty list is refused, so list every event you want, and\nan event type added later is not delivered to an endpoint that did not select it. At most 25 endpoints per organisation. The new endpoint is enabled. The secret is stored encrypted; when\nencryption is not available for your organisation the call is refused (`503 webhook_secret_kek_required`) and nothing is written.\n\nEvery create, update, delete and rotation is recorded in the organisation's audit log with your API key as the actor. The endpoints you create here appear in the Backoffice and the other\nway round.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:webhooks"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointCreate"
              },
              "example": {
                "url": "https://hooks.example.com/noxtica",
                "events": [
                  "kyc.session.created",
                  "kyc.session.opened",
                  "kyc.session.completed",
                  "kyc.review.decided"
                ],
                "description": "Onboarding backend"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Endpoint registered. `secret` is shown only here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointWithSecret"
                },
                "example": {
                  "ok": true,
                  "webhook": {
                    "id": "7f3a2c10-5b8e-4d2a-9c61-0e4a7b9d1f35",
                    "url": "https://hooks.example.com/noxtica",
                    "events": [
                      "kyc.session.created",
                      "kyc.session.opened",
                      "kyc.session.completed",
                      "kyc.review.decided"
                    ],
                    "description": "Onboarding backend",
                    "enabled": true,
                    "secretSealed": true,
                    "createdAt": 1790760000000,
                    "updatedAt": 1790760000000,
                    "lastDeliveryAt": null,
                    "lastStatus": null
                  },
                  "secret": "whsec_example_do_not_use_0123456789abcdef",
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`, `unknown_field` (a key other than `url`, `events`, `description`), `invalid_url`, `invalid_events` (not an array, an unknown event type, or empty; `message` says which), `invalid_description` (more than 200 characters) or `webhook_limit_reached` (25 endpoints already).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "examples": {
                  "invalidUrl": {
                    "value": {
                      "error": "invalid_url",
                      "message": "url must be a valid https URL (no localhost / private addresses",
                      "no embedded credentials).": null,
                      "requestId": "req-test"
                    }
                  },
                  "invalidEvents": {
                    "value": {
                      "error": "invalid_events",
                      "message": "Select at least one event type",
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/v1_GatedNotFound"
          },
          "409": {
            "description": "`url_already_registered`: this URL is already one of your endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "url_already_registered",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`unsupported_media_type`: the body is not `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "unsupported_media_type",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          },
          "500": {
            "description": "`webhook_secret_encryption_failed`: sealing the secret failed; nothing was written. Retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "webhook_secret_encryption_failed",
                  "message": "Sealing the webhook secret failed; nothing was written.",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/WebhookApiUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/webhooks/{webhookId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookId"
        }
      ],
      "get": {
        "operationId": "getWebhookEndpoint",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "Read a webhook endpoint",
        "description": "Server-to-server. Returns one endpoint of your organisation. The signing secret is never returned. An id of another organisation is indistinguishable from an unknown id (`404 webhook_not_found`).",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:webhooks"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointEnvelope"
                },
                "example": {
                  "ok": true,
                  "webhook": {
                    "id": "7f3a2c10-5b8e-4d2a-9c61-0e4a7b9d1f35",
                    "url": "https://hooks.example.com/noxtica",
                    "events": [
                      "kyc.session.created",
                      "kyc.session.completed"
                    ],
                    "description": null,
                    "enabled": true,
                    "secretSealed": true,
                    "createdAt": 1790760000000,
                    "updatedAt": 1790760000000,
                    "lastDeliveryAt": 1790762445000,
                    "lastStatus": "ok"
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/WebhookNotFound"
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/WebhookApiUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "put": {
        "operationId": "updateWebhookEndpoint",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "Change a webhook endpoint",
        "description": "Server-to-server. Partial update: send only the members you change (`url`, `events`, `description`, `enabled`); at least one is required. `events` replaces the whole list. Set `enabled`\nto `false` to pause deliveries without losing the endpoint; events that occur while it is disabled are not queued for it. The signing secret is not changed (see `rotate-secret`).",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:webhooks"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointUpdate"
              },
              "example": {
                "events": [
                  "kyc.session.created",
                  "kyc.session.completed",
                  "kyb.session.completed"
                ],
                "enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointEnvelope"
                },
                "example": {
                  "ok": true,
                  "webhook": {
                    "id": "7f3a2c10-5b8e-4d2a-9c61-0e4a7b9d1f35",
                    "url": "https://hooks.example.com/noxtica",
                    "events": [
                      "kyc.session.created",
                      "kyc.session.completed",
                      "kyb.session.completed"
                    ],
                    "description": "Onboarding backend",
                    "enabled": true,
                    "secretSealed": true,
                    "createdAt": 1790760000000,
                    "updatedAt": 1790763000000,
                    "lastDeliveryAt": 1790762445000,
                    "lastStatus": "ok"
                  },
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`, `unknown_field`, `empty_update` (no member sent), `invalid_url`, `invalid_events`, `invalid_description` or `invalid_enabled` (not a boolean).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "empty_update",
                  "message": "Send at least one of url, events, description, enabled.",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/WebhookNotFound"
          },
          "409": {
            "description": "`url_already_registered`: another of your endpoints already uses this URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "url_already_registered",
                  "requestId": "req-test"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`unsupported_media_type`: the body is not `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "unsupported_media_type",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/WebhookApiUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "delete": {
        "operationId": "deleteWebhookEndpoint",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "Delete a webhook endpoint",
        "description": "Server-to-server. Deletes the endpoint. Deliveries still waiting for it are dropped.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:webhooks"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint was deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointDeleted"
                },
                "example": {
                  "ok": true,
                  "id": "7f3a2c10-5b8e-4d2a-9c61-0e4a7b9d1f35",
                  "removed": true,
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/WebhookNotFound"
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/WebhookApiUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/webhooks/{webhookId}/rotate-secret": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookId"
        }
      ],
      "post": {
        "operationId": "rotateWebhookSecret",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "Rotate the signing secret of an endpoint",
        "description": "Server-to-server. Mints a new signing secret, returns it **once**, and stops the previous secret from verifying **at once**: deliveries sent after this call are signed with the new secret, so\nupdate your receiver first or accept both for a moment. The secret is stored encrypted, which is what the session and review events require: rotate an endpoint that was created before\nencrypted storage once before you subscribe it to them (`secretSealed` is `false` until you do). The call has no body.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:webhooks"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint and its new secret. `secret` is shown only here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointWithSecret"
                },
                "example": {
                  "ok": true,
                  "webhook": {
                    "id": "7f3a2c10-5b8e-4d2a-9c61-0e4a7b9d1f35",
                    "url": "https://hooks.example.com/noxtica",
                    "events": [
                      "kyc.session.created",
                      "kyc.session.completed"
                    ],
                    "description": null,
                    "enabled": true,
                    "secretSealed": true,
                    "createdAt": 1790760000000,
                    "updatedAt": 1790764000000,
                    "lastDeliveryAt": 1790762445000,
                    "lastStatus": "ok"
                  },
                  "secret": "whsec_example_do_not_use_fedcba9876543210",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/WebhookNotFound"
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          },
          "500": {
            "description": "`webhook_secret_encryption_failed`: sealing the new secret failed; the existing secret is unchanged. Retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "webhook_secret_encryption_failed",
                  "message": "Sealing the webhook secret failed; nothing was written.",
                  "requestId": "req-test"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/WebhookApiUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/webhooks/{webhookId}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookId"
        }
      ],
      "post": {
        "operationId": "sendWebhookTestEvent",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "Send a test delivery to an endpoint",
        "description": "Server-to-server. Sends **one** signed sample delivery of an event type to the endpoint, with the same four headers, envelope and signature as a real delivery, and reports what your\nreceiver answered. Use it to check reachability and signature verification before you rely on an endpoint, and to see the exact payload of any event.\n\n`eventType` picks the event (any type of `GET /v1/webhooks/event-types`, whether or not the endpoint subscribes to it); without it the first event of the endpoint is used. The sample carries\nobvious placeholder data (ids such as `test-session`), never real data, and its delivery id starts with `test_`: ignore deliveries with that prefix in your business logic. A test delivery is\na single attempt: it is not retried, it is not recorded in the delivery list, and it updates the endpoint's `lastStatus`. The test is audited before it leaves. A tighter rate limit than the\nother operations applies.\n\nThe call answers `200` whenever the attempt was made, even when your receiver refused it: read `delivered`, `status` and `result`. The session and review events are signed only with a sealed\nsecret; for an endpoint that does not have one the answer carries `error: secret_plaintext_ineligible` and nothing is sent: rotate the secret, then test again.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "write:webhooks"
            ]
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookTestRequest"
              },
              "example": {
                "eventType": "kyc.session.created"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The attempt was made (or refused for the reason in `error`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResult"
                },
                "examples": {
                  "delivered": {
                    "value": {
                      "ok": true,
                      "delivered": true,
                      "eventType": "kyc.session.created",
                      "deliveryId": "test_5b8e4d2a-9c61-4a7b-8f35-0e4a7b9d1f35",
                      "status": 200,
                      "result": "ok",
                      "ms": 183,
                      "requestId": "req-test"
                    }
                  },
                  "refused": {
                    "value": {
                      "ok": true,
                      "delivered": false,
                      "eventType": "kyc.session.created",
                      "deliveryId": "test_5b8e4d2a-9c61-4a7b-8f35-0e4a7b9d1f35",
                      "status": 500,
                      "result": "http_500",
                      "ms": 240,
                      "requestId": "req-test"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_json`, `unknown_field` or `invalid_event_type` (not a type of the event catalog).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "invalid_event_type",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/WebhookNotFound"
          },
          "413": {
            "$ref": "#/components/responses/v1_BodyTooLarge"
          },
          "415": {
            "description": "`unsupported_media_type`: a body was sent that is not `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "unsupported_media_type",
                  "requestId": "req-test"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/WebhookApiUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    },
    "/v1/webhooks/{webhookId}/deliveries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookId"
        }
      ],
      "get": {
        "operationId": "listWebhookDeliveries",
        "tags": [
          "Webhook Endpoints"
        ],
        "summary": "List recent deliveries of an endpoint",
        "description": "Server-to-server. Returns the recent deliveries of KYC, KYB and KYM events to one endpoint, newest first: what happened to each (still retrying, delivered, refused for good, or dead after the\nretry budget). The list is a notification surface, never authority, and it never holds a URL, a body or a secret: `lastError` is a closed code. Deliveries of the other products (risk scoring,\naccount and Pulse events) are not listed; their outcome is the endpoint's `lastStatus`. Test deliveries are not listed.\n\nPage with `cursor`: pass the `nextCursor` of the previous page unchanged; `nextCursor` is `null` on the last page.",
        "security": [
          {
            "v1_ApiKeyAuth": [
              "read:webhooks"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 50 (default 25). A value outside the range is refused with `400 limit_out_of_bounds`.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `nextCursor` of the previous page, unchanged. Anything else is refused with `400 invalid_cursor`.",
            "schema": {
              "type": "string",
              "maxLength": 320
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of deliveries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryList"
                },
                "example": {
                  "ok": true,
                  "deliveries": [
                    {
                      "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.completed:7f3a2c10-5b8e-4d2a-9c61-0e4a7b9d1f35",
                      "eventType": "kyc.session.completed",
                      "state": "delivered",
                      "attempts": 1,
                      "httpStatus": 200,
                      "lastError": null,
                      "createdAt": 1790762445000,
                      "deliveredAt": 1790762446000
                    },
                    {
                      "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.review_pending:7f3a2c10-5b8e-4d2a-9c61-0e4a7b9d1f35",
                      "eventType": "kyc.session.review_pending",
                      "state": "retrying",
                      "attempts": 2,
                      "httpStatus": 503,
                      "lastError": "http_503",
                      "createdAt": 1790762400000,
                      "deliveredAt": null
                    }
                  ],
                  "nextCursor": null,
                  "requestId": "req-test"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_query_parameters`, `limit_out_of_bounds` or `invalid_cursor`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookApiError"
                },
                "example": {
                  "error": "limit_out_of_bounds",
                  "message": "limit must be an integer from 1 to 50.",
                  "requestId": "req-test"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/v1_InvalidApiKey"
          },
          "403": {
            "$ref": "#/components/responses/v1_ScopeForbidden"
          },
          "404": {
            "$ref": "#/components/responses/WebhookNotFound"
          },
          "429": {
            "$ref": "#/components/responses/WebhookRateLimited"
          },
          "503": {
            "$ref": "#/components/responses/WebhookApiUnavailable"
          }
        },
        "x-noxtica-status": "available"
      },
      "servers": [
        {
          "url": "https://collect.noxtica.com",
          "description": "Production",
          "x-noxtica-key-prefix": "sk_prod_"
        }
      ]
    }
  },
  "webhooks": {
    "kyc.session.completed": {
      "post": {
        "operationId": "kycWebhookSessionCompleted",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A hosted verification session reached a business outcome",
        "description": "Sent by Noxtica to every endpoint you registered for `kyc.session.completed`\n(Backoffice, Integrations & API, Webhooks). See \"Webhooks (KYC)\" in the API description\nfor the headers, the signature, retries and deduplication; they are common to all six\nKYC events.\n\n**When it fires.** A hosted session reached `completed`, `unverified` or `error`.\n**It does not fire when a session lands in `review_pending`.** With the identity provider\nconfigured today, a successful hosted capture ends in `review_pending` (the provider\nroutes every success to manual review, reason code `provider_manual_review`); the event\nis sent later, when a Backoffice reviewer approves or overrides the case and the session\nmoves `review_pending` to `completed`. The platform sends it right after the decision and\nretries on its 5-minute tick, so allow up to about 5 minutes between the reviewer's\ndecision and the delivery. A `review_pending` session never expires, so your system must\nkeep waiting for the decision (or poll `GET /v1/kyc/sessions/{sessionId}`). It never fires\nfor `expired` or `cancelled_by_user` sessions.\n\n`status: completed` means a reviewer decided; it does not mean the person is verified,\nbecause an `override` completes the session too. The payload does not say which decision\nwas made: read `verification` and `review` on `GET /v1/kyc/sessions/{sessionId}`.\n\nThis is a notification, not authority: before you act on it, read\n`GET /v1/kyc/sessions/{sessionId}`. The payload carries no subject reference; correlate\nby `data.sessionId`, the id you stored when you minted the session.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionCompletedWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c",
                "type": "kyc.session.completed",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "completed",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "reasonCodes": [],
                  "occurredAt": 1790762445000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks (KYC)\" for how other statuses are treated."
          }
        }
      }
    },
    "kyc.session.cancelled": {
      "post": {
        "operationId": "kycWebhookSessionCancelled",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "The end customer cancelled a hosted verification session",
        "description": "Sent when the customer confirms on the hosted page that they do not want to continue and\nthe session actually moves to `cancelled_by_user`. Available only when session\ncancellation is enabled for your organisation. Subscriptions are opt-in per event: an\nendpoint registered before this event existed does not receive it until you add it to the\nendpoint's event list.\n\n`data.cancelReason` and `data.cancelledAt` are delivered only in this event.\n`GET /v1/kyc/sessions/{sessionId}` reports the terminal status `cancelled_by_user` but does\nnot return the cancel reason or the cancel time. An expiry never sends this event; observe\nit by reading the session status.\n\nSee \"Webhooks (KYC)\" in the API description for headers, signature, retries and\ndeduplication.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionCancelledWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c",
                "type": "kyc.session.cancelled",
                "created_at": 1790762300000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "cancelled_by_user",
                  "cancelReason": "not_now",
                  "cancelledAt": 1790762300000,
                  "occurredAt": 1790762300000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks (KYC)\" for how other statuses are treated."
          }
        }
      }
    },
    "kyc.verification.completed": {
      "post": {
        "operationId": "kycWebhookVerificationCompleted",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A KYC verification request reached a terminal state",
        "description": "Sent when a verification request (`POST /v1/kyc/verify`, or the verification a hosted\nsession runs when the customer submits) reaches a terminal state: `completed`,\n`terminal_error`, `expired` or `cancelled`. The non-terminal states `retryable_error`\nand `provider_unknown` emit nothing. `data.verdict` is the stored canonical verdict\n(`verified`, `unverified` or `error`; `error` for an infrastructure-faulted terminal\nrequest that has no verdict).\n\nA hosted session's submit runs the same pipeline, so an endpoint subscribed to both events\ncan receive `kyc.verification.completed` for the same attempt as\n`kyc.session.*`; with the identity provider configured today the verdict of a successful\nhosted capture is `unverified` with the reason code `provider_manual_review`. Delivery\norder between the two events is not guaranteed. `data.verificationId` equals\n`session.verificationId` on the session status read.\n\nSee \"Webhooks (KYC)\" in the API description for headers, signature, retries and\ndeduplication.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycVerificationCompletedWebhookEvent"
              },
              "example": {
                "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6:wh_7f3a2c",
                "type": "kyc.verification.completed",
                "created_at": 1790762440000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "verdict": "unverified",
                  "reasonCodes": [
                    "provider_manual_review"
                  ],
                  "idType": "passport",
                  "occurredAt": 1790762440000,
                  "deliveryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks (KYC)\" for how other statuses are treated."
          }
        }
      }
    },
    "kyc.erasure.completed": {
      "post": {
        "operationId": "kycWebhookErasureCompleted",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A KYC erasure request finished successfully",
        "description": "Sent when an erasure request reaches `completed`. A `partial` erasure never emits this\nevent. The payload deliberately carries no subject identifier or subject digest: an erasure\nnotification must not re-establish a link to the person whose linkage was just erased.\n`data.requestId` is the erasure request's own id.\n\nSee \"Webhooks (KYC)\" in the API description for headers, signature, retries and\ndeduplication.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycErasureCompletedWebhookEvent"
              },
              "example": {
                "id": "er_4b81d2:wh_7f3a2c",
                "type": "kyc.erasure.completed",
                "created_at": 1790900000000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "requestId": "er_4b81d2",
                  "outcome": "completed",
                  "occurredAt": 1790900000000,
                  "deliveryId": "er_4b81d2:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks (KYC)\" for how other statuses are treated."
          }
        }
      }
    },
    "kyc.document.expiry_warning": {
      "post": {
        "operationId": "kycWebhookDocumentExpiryWarning",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A verified identity document is approaching or past its expiry",
        "description": "Sent by the scheduled document-expiry monitoring pass when a monitored verification's\ndocument crosses the 90, 60 or 30 day threshold or has expired (`data.alertType`). Only\navailable when document-expiry monitoring is enabled for your organisation. Each\nthreshold fires once per monitored verification.\n\nUnlike the other KYC events, this event is not read from the delivery ledger: the\nscheduled pass makes a single signed delivery attempt to each subscribed endpoint and\ndoes not retry a failed attempt. Deduplicate on the delivery id anyway, and use\n`data.verificationId` to correlate with the original `kyc.verification.completed`.\nSee \"Webhooks (KYC)\" for headers and signature.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycDocumentExpiryWarningWebhookEvent"
              },
              "example": {
                "id": "sched_91c0:wh_7f3a2c",
                "type": "kyc.document.expiry_warning",
                "created_at": 1798538000000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "alertType": "expiry_30d",
                  "documentExpiresAt": 1801130000000,
                  "occurredAt": 1798538000000,
                  "deliveryId": "sched_91c0:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks (KYC)\" for how other statuses are treated."
          }
        }
      }
    },
    "kyc.reverification.due": {
      "post": {
        "operationId": "kycWebhookReverificationDue",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A previously verified identity is due for re-verification",
        "description": "Sent by the same scheduled monitoring pass as `kyc.document.expiry_warning` when a\nre-verification schedule entry reaches its due time. Only available when document-expiry\nmonitoring is enabled for your organisation, and only for a verification that has a\nre-verification schedule entry. Single signed delivery attempt, no retry, as for\n`kyc.document.expiry_warning`.\n\nSee \"Webhooks (KYC)\" in the API description for headers and signature.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycReverificationDueWebhookEvent"
              },
              "example": {
                "id": "sched_92d1:wh_7f3a2c",
                "type": "kyc.reverification.due",
                "created_at": 1801130000000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "dueAt": 1801130000000,
                  "occurredAt": 1801130000000,
                  "deliveryId": "sched_92d1:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks (KYC)\" for how other statuses are treated."
          }
        }
      }
    },
    "kyc.session.created": {
      "post": {
        "operationId": "kycWebhookSessionCreated",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A hosted verification session (invitation) was created",
        "description": "Sent to every endpoint subscribed to `kyc.session.created` when a hosted KYC session is created: by `POST /v1/kyc/sessions` or by an invitation in the Backoffice. `data.previousStatus` is `null`.\n\n`data.status` is `pending` and `data.previousStatus` names the status the session left (`null` when there is none). Correlate by `data.sessionId`, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint's event list. The delivery id is `<sessionId>:kyc.session.created:<webhookId>` and is identical on every retry.\n\nSee \"Webhooks\" in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read `GET /v1/kyc/sessions/{sessionId}`.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionLifecycleWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.created:wh_7f3a2c",
                "type": "kyc.session.created",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "pending",
                  "previousStatus": null,
                  "verificationId": null,
                  "reasonCodes": [],
                  "domainId": null,
                  "occurredAt": 1790762445000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.created:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks\" in the API description for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "kyc.session.opened": {
      "post": {
        "operationId": "kycWebhookSessionOpened",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "The customer opened a hosted verification session",
        "description": "Sent to every endpoint subscribed to `kyc.session.opened` when the customer opens the hosted link and the session leaves `pending`.\n\n`data.status` is `opened` and `data.previousStatus` names the status the session left (`null` when there is none). Correlate by `data.sessionId`, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint's event list. The delivery id is `<sessionId>:kyc.session.opened:<webhookId>` and is identical on every retry.\n\nSee \"Webhooks\" in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read `GET /v1/kyc/sessions/{sessionId}`.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionLifecycleWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.opened:wh_7f3a2c",
                "type": "kyc.session.opened",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "opened",
                  "previousStatus": "pending",
                  "verificationId": null,
                  "reasonCodes": [],
                  "domainId": null,
                  "occurredAt": 1790762445000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.opened:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks\" in the API description for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "kyc.session.verifying": {
      "post": {
        "operationId": "kycWebhookSessionVerifying",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A hosted verification session started verifying",
        "description": "Sent to every endpoint subscribed to `kyc.session.verifying` when the customer has submitted and the verification is running. The outcome follows as another event.\n\n`data.status` is `verifying` and `data.previousStatus` names the status the session left (`null` when there is none). Correlate by `data.sessionId`, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint's event list. The delivery id is `<sessionId>:kyc.session.verifying:<webhookId>` and is identical on every retry.\n\nSee \"Webhooks\" in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read `GET /v1/kyc/sessions/{sessionId}`.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionLifecycleWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.verifying:wh_7f3a2c",
                "type": "kyc.session.verifying",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "verifying",
                  "previousStatus": "opened",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "reasonCodes": [],
                  "domainId": null,
                  "occurredAt": 1790762445000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.verifying:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks\" in the API description for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "kyc.session.review_pending": {
      "post": {
        "operationId": "kycWebhookSessionReviewPending",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A hosted verification session is waiting for a manual review",
        "description": "Sent to every endpoint subscribed to `kyc.session.review_pending` when the verification needs a decision by a Backoffice reviewer before the session can finish.\n\n`data.status` is `review_pending` and `data.previousStatus` names the status the session left (`null` when there is none). Correlate by `data.sessionId`, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint's event list. The delivery id is `<sessionId>:kyc.session.review_pending:<webhookId>` and is identical on every retry.\n\nA `review_pending` session never expires. `kyc.session.completed` follows when a reviewer approves or overrides the case; `kyc.review.decided` announces the decision itself.\n\nSee \"Webhooks\" in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read `GET /v1/kyc/sessions/{sessionId}`.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionLifecycleWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.review_pending:wh_7f3a2c",
                "type": "kyc.session.review_pending",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "review_pending",
                  "previousStatus": "verifying",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "reasonCodes": [
                    "provider_manual_review"
                  ],
                  "domainId": null,
                  "occurredAt": 1790762445000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.review_pending:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks\" in the API description for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "kyc.session.unverified": {
      "post": {
        "operationId": "kycWebhookSessionUnverified",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A hosted verification session ended unverified",
        "description": "Sent to every endpoint subscribed to `kyc.session.unverified` when the session ends `unverified`. `data.reasonCodes` lists the closed reason codes.\n\n`data.status` is `unverified` and `data.previousStatus` names the status the session left (`null` when there is none). Correlate by `data.sessionId`, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint's event list. The delivery id is `<sessionId>:kyc.session.unverified:<webhookId>` and is identical on every retry.\n\n`kyc.session.completed` also announces this outcome (it announces every terminal outcome: `completed`, `unverified` and `error`); subscribe to this event instead of it when you only care about the narrower case.\n\nSee \"Webhooks\" in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read `GET /v1/kyc/sessions/{sessionId}`.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionLifecycleWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.unverified:wh_7f3a2c",
                "type": "kyc.session.unverified",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "unverified",
                  "previousStatus": "verifying",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "reasonCodes": [],
                  "domainId": null,
                  "occurredAt": 1790762445000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.unverified:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks\" in the API description for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "kyc.session.error": {
      "post": {
        "operationId": "kycWebhookSessionError",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A hosted verification session ended with an error",
        "description": "Sent to every endpoint subscribed to `kyc.session.error` when the session ends with a verification error.\n\n`data.status` is `error` and `data.previousStatus` names the status the session left (`null` when there is none). Correlate by `data.sessionId`, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint's event list. The delivery id is `<sessionId>:kyc.session.error:<webhookId>` and is identical on every retry.\n\n`kyc.session.completed` also announces this outcome; subscribe to this event instead of it when you only care about the narrower case.\n\nSee \"Webhooks\" in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read `GET /v1/kyc/sessions/{sessionId}`.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionLifecycleWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.error:wh_7f3a2c",
                "type": "kyc.session.error",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "error",
                  "previousStatus": "verifying",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "reasonCodes": [],
                  "domainId": null,
                  "occurredAt": 1790762445000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.error:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks\" in the API description for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "kyc.session.expired": {
      "post": {
        "operationId": "kycWebhookSessionExpired",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A hosted verification session expired",
        "description": "Sent to every endpoint subscribed to `kyc.session.expired` when a session lapsed before it reached an outcome (the unopened link expired, or the capture window passed). The event is sent when the expiry is recorded, which happens when the session is next used or when the platform's expiry sweep runs, so it can follow the moment the session lapsed by some time; the status read always shows the current state.\n\n`data.status` is `expired` and `data.previousStatus` names the status the session left (`null` when there is none). Correlate by `data.sessionId`, the id you stored when you minted the session. Subscriptions are opt-in per event: an endpoint registered before this event existed does not receive it until you add it to the endpoint's event list. The delivery id is `<sessionId>:kyc.session.expired:<webhookId>` and is identical on every retry.\n\nSee \"Webhooks\" in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: before you act on it, read `GET /v1/kyc/sessions/{sessionId}`.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycSessionLifecycleWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.expired:wh_7f3a2c",
                "type": "kyc.session.expired",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "status": "expired",
                  "previousStatus": "opened",
                  "verificationId": null,
                  "reasonCodes": [],
                  "domainId": null,
                  "occurredAt": 1790762445000,
                  "deliveryId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:kyc.session.expired:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks\" in the API description for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "kyc.review.decided": {
      "post": {
        "operationId": "kycWebhookReviewDecided",
        "tags": [
          "KYC Webhooks"
        ],
        "summary": "A reviewer approved or overrode a verification",
        "description": "Sent to every endpoint subscribed to `kyc.review.decided` when a Backoffice reviewer, or your own back office through `POST /v1/kyc/verifications/{verificationId}/review-decision`, writes an affirmative decision, `approve` or `override`, for a verification. A changed decision is a new delivery: `data.decisionVersion` increases with every written decision, and the delivery id is `<verificationId>:kyc.review.decided:<decisionVersion>:<webhookId>`.\n\nThis is the event of the decision itself. It is sent for a hosted session and for a verification you created through the API, which has no session to complete. An escalation (review state `in_review`, no decision) sends nothing. The event is written in the same transaction as the decision and sent right after it, with the platform's 5-minute tick as the durable retry path, so allow up to about 5 minutes.\n\n`data.decision` is `approve` or `override`. Only `approve` makes the verification `verified` (`verification.source` is then `review`); an `override` does not, and there is no `reject` decision today. `data.reviewState` is the state of the review after the decision: `approved` for `approve`, `overridden` for `override`. `data.sessionId` is the hosted session the verification belongs to, or `null` for a verification you created through the API. `data.actor.type` says who wrote the decision: `panel` (a Backoffice reviewer) or `api` (your own back office through the review decision route); a decision from either source has the same payload shape, the same `decisionVersion` counter and the same delivery id format. `data.reasonCodes` holds the closed reason codes a Backoffice reviewer attached (an empty array for an `api` decision), never free text. `data.reviewReasonCode` is the closed reason code of an `api` decision (for `approve`: `identity_confirmed`, `registry_crosscheck_passed`, `name_variant_accepted`, `manual_document_check_passed` or `other`; for `override`: `identity_mismatch`, `registry_crosscheck_failed`, `document_invalid`, `suspected_fraud`, `liveness_not_accepted` or `other`) and `null` for a `panel` decision. A delivery created before this member existed carries neither `actor` nor `reviewReasonCode`: read an absent `actor` as `panel`. The payload never carries the reviewer, the API key or the note.\n\nFor a hosted session that was `review_pending`, `kyc.session.completed` follows or arrives alongside, as a separate delivery with no guaranteed order. `kyc.verification.completed` is not sent again after a review. After either event, read the current outcome: `verification` and `review` on `GET /v1/kyc/sessions/{sessionId}` (hosted session), or on `GET /v1/kyc/verifications/{verificationId}/identity` (any verification; needs the `read:identity_details` scope).\n\nSee \"Webhooks\" in the API description for the headers, the signature, retries and deduplication. A webhook is a notification, not authority: read the verification before you act on it.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KycReviewDecidedWebhookEvent"
              },
              "example": {
                "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6:kyc.review.decided:1:wh_7f3a2c",
                "type": "kyc.review.decided",
                "created_at": 1790762445000,
                "tenant_id": "tenant_42",
                "data": {
                  "schemaVersion": "1",
                  "verificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "sessionId": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                  "decision": "approve",
                  "reviewState": "approved",
                  "decisionVersion": 1,
                  "reasonCodes": [],
                  "actor": {
                    "type": "panel"
                  },
                  "reviewReasonCode": null,
                  "occurredAt": 1790762445000,
                  "deliveryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6:kyc.review.decided:1:wh_7f3a2c"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhooks\" in the API description for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "submission.scored": {
      "post": {
        "operationId": "webhookSubmissionScored",
        "tags": [
          "Verdict Webhooks"
        ],
        "summary": "A submission was scored",
        "description": "Sent for every scored browser or app submission of your organisation (the broadest\nsubscription: use `risk.high` or `risk.flagged` if you only need the notable ones). The\nBackoffice \"Send test\" button sends a sample of this event type, with placeholder verdict\ndata and a valid signature.\n\n`data.event_id` is the request id of the submission: correlate it with the `X-Request-Id`\nyour browser integration received. `data.device_id` is the opaque device id\n(`fingerprintId`). This is a notification, not authority; read the device\n(`GET /v1/device/{deviceId}`) before taking an irreversible action.\n\nSee \"Webhook delivery and retries\" in the API description for the headers, the signature,\nretries and deduplication.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerdictWebhookEvent"
              },
              "example": {
                "id": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21",
                "type": "submission.scored",
                "created_at": 1790762445000,
                "tenant_id": "YOUR_TENANT_ID",
                "data": {
                  "score": 12,
                  "risk_level": "low",
                  "confidence": 0.91,
                  "flags": [],
                  "reason_codes": [],
                  "device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
                  "domain_id": "dom_42",
                  "event_id": "8f1c2a52-6a6e-4b0e-9d52-2a7c1d9e4b10"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhook delivery and retries\" for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "risk.high": {
      "post": {
        "operationId": "webhookRiskHigh",
        "tags": [
          "Verdict Webhooks"
        ],
        "summary": "A submission was scored high or critical",
        "description": "Sent when a scored submission lands in the `high` or `critical` risk band. The payload is\nthe same verdict shape as `submission.scored`. A submission in that band also produces\n`submission.scored` (and `risk.flagged` when a flag fired) for endpoints subscribed to\nthose types.\n\nSee \"Webhook delivery and retries\" in the API description for the headers, the signature,\nretries and deduplication.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerdictWebhookEvent"
              },
              "example": {
                "id": "0b9d7b1c-2f0e-4c8b-9a52-7c1b3a1f9e11",
                "type": "risk.high",
                "created_at": 1790762446000,
                "tenant_id": "YOUR_TENANT_ID",
                "data": {
                  "score": 87,
                  "risk_level": "high",
                  "confidence": 0.88,
                  "flags": [
                    "example_flag"
                  ],
                  "reason_codes": [],
                  "device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
                  "domain_id": "dom_42",
                  "event_id": "3a7d1e52-6a6e-4b0e-9d52-2a7c1d9e4b11"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhook delivery and retries\" for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "risk.flagged": {
      "post": {
        "operationId": "webhookRiskFlagged",
        "tags": [
          "Verdict Webhooks"
        ],
        "summary": "A risk flag fired",
        "description": "Sent when at least one risk flag fired on a scored submission, whatever its risk band.\n\nThe same event type is also sent after each mobile attestation `verify` (see\n`POST /v1/attest/android` and `POST /v1/attest/ios`), with `flags` of the form\n`mobile_attest:<platform>`, `mobile_attest_level:<level>` and `mobile_attest_verdict:<verdict>`.\nThere `data.device_id` can be an empty string, which means that no stable per-device\nidentity is available for the event: do not group events on an empty `device_id`, and use\n`data.event_id`, which is always present, as the per-event key.\n\nSee \"Webhook delivery and retries\" in the API description for the headers, the signature,\nretries and deduplication.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerdictWebhookEvent"
              },
              "example": {
                "id": "9c1e5d2a-3b4f-4a6c-8d7e-1f2a3b4c5d6e",
                "type": "risk.flagged",
                "created_at": 1790762447000,
                "tenant_id": "YOUR_TENANT_ID",
                "data": {
                  "score": 40,
                  "risk_level": "medium",
                  "confidence": 0.7,
                  "flags": [
                    "example_flag"
                  ],
                  "reason_codes": [],
                  "device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
                  "domain_id": "dom_42",
                  "event_id": "5e8f2a52-6a6e-4b0e-9d52-2a7c1d9e4b12"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhook delivery and retries\" for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "risk.cluster_flagged": {
      "post": {
        "operationId": "webhookRiskClusterFlagged",
        "tags": [
          "Verdict Webhooks"
        ],
        "summary": "A device cluster was flagged as bad",
        "description": "Sent once, on the first escalation, when a cluster of devices (devices sharing an\nobservational sub-fingerprint) is flagged bad in real time because a high or critical\nsubmission came with strong cluster evidence. It lets you react to an emerging cluster\nbefore slower offline feeds catch up. A cluster that is already flagged does not produce\na new delivery for every further submission.\n\nThe payload has the same verdict shape as `submission.scored`; `data.risk_level` is the\ncluster's band. It carries no raw IP address and no personal data. Available only when\nreal-time cluster propagation is enabled for the deployment.\n\nSee \"Webhook delivery and retries\" in the API description for the headers, the signature,\nretries and deduplication.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerdictWebhookEvent"
              },
              "example": {
                "id": "2a4c6e8f-1b3d-4f5a-9c7e-0d2b4f6a8c1e",
                "type": "risk.cluster_flagged",
                "created_at": 1790762448000,
                "tenant_id": "YOUR_TENANT_ID",
                "data": {
                  "score": 91,
                  "risk_level": "critical",
                  "confidence": 0.93,
                  "flags": [
                    "example_flag"
                  ],
                  "reason_codes": [],
                  "device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
                  "domain_id": "dom_42",
                  "event_id": "7a9b1c52-6a6e-4b0e-9d52-2a7c1d9e4b13"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhook delivery and retries\" for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    },
    "account.agent_linked": {
      "post": {
        "operationId": "webhookAccountAgentLinked",
        "tags": [
          "Verdict Webhooks"
        ],
        "summary": "A verified agent was linked to an account for the first time",
        "description": "Sent once, when a cryptographically verified signed agent (Web Bot Auth, RFC 9421) is linked\nto one of your end-user accounts for the first time, that is when the (agent, account)\npair had no earlier link. It lets you react when a new agent starts acting on an account.\n\nThe payload carries the keyed `account_hash` (a 16-character lowercase-hex pseudonym; the\nraw account id is never stored or sent), the verified agent's `agent_keyid` and\n`agent_host`, and the usual `device_id`, `domain_id` and `event_id`. It carries no raw IP\naddress and no personal data. Use `GET /v1/accounts/{accountHash}/devices` to list the\ndevices and agents linked to the account.\n\nSee \"Webhook delivery and retries\" in the API description for the headers, the signature,\nretries and deduplication.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignatureHeader"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookEventHeader"
          },
          {
            "$ref": "#/components/parameters/v1_WebhookDeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentLinkedWebhookEvent"
              },
              "example": {
                "id": "4d6f8a1c-5e7b-4d9f-8a2c-3b5d7f9a1c2e",
                "type": "account.agent_linked",
                "created_at": 1790762449000,
                "tenant_id": "YOUR_TENANT_ID",
                "data": {
                  "account_hash": "0a1b2c3d4e5f6071",
                  "agent_keyid": "agent-key-1",
                  "agent_host": "agent.example.com",
                  "device_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
                  "domain_id": "dom_42",
                  "event_id": "9b1d3f52-6a6e-4b0e-9d52-2a7c1d9e4b14"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx status. See \"Webhook delivery and retries\" for how other statuses are treated."
          }
        },
        "x-noxtica-status": "available"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_<random>",
        "description": "Server-to-server tenant API key. Header: `Authorization: Bearer sk_...`. Scopes are self-mintable per tenant; the scope required by each operation is listed in that operation's `security` block. Recognized KYC/KYB/KYM scopes: `write:identity` (verify, batch, reverify, evidence, sessions, erasure and activate — all share this one scope), `read:identity_details` (the identity details read routes only; it authorises no write and `write:identity` does not imply it), `write:kyc_review_decision` (the review decision route only; it reads nothing and `write:identity` does not imply it), `write:kym` (KYM transactions). A key keeps its scopes until a tenant administrator changes them (Edit scopes on the key in the Backoffice, API Configuration); create a new key as the other way to add one.\nThe `read:evidence` scope opens the evidence image routes only (the list, the access grant and the access log; the signed URL a grant returns needs no key). It authorises no write, and neither `write:identity` nor `read:identity_details` implies it."
      },
      "SessionTokenAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "kycs.<tenant>.<secret>",
        "description": "Opaque hosted-session bearer token, minted once by the matching `POST .../sessions` S2S call and never re-issued. The tenant id rides inside the token itself. Additionally origin-enforced against the tenant's `ALLOWED_ORIGINS` allowlist — a missing or disallowed `Origin` header refuses with `403 origin_not_allowed` even given a valid token."
      },
      "McpTokenAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "mcp_<random>",
        "description": "MCP token (`Authorization: Bearer mcp_...`) created under Integrations & API, MCP tokens. A static opaque bearer token, not OAuth. It is accepted only by this endpoint."
      },
      "tenantKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_prod_<32 hex>",
        "description": "The platform's tenant API key (minted in the Backoffice; scopes `read:pulse`, `write:pulse`), for the tenant's own back-end on `/pulse/v1/tenant/*` only. Looked up through an HMAC hash; a device token or a web session is refused.",
        "x-noxtica-scopes": {
          "read:pulse": "Read subject status and links.",
          "write:pulse": "Issue link codes, unlink and reverse decisions."
        }
      },
      "v1_ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_<random>",
        "description": "Server-to-server tenant API key, sent as `Authorization: Bearer sk_...` (`sk_prod_...` keys). Keys carry scopes: the scope an operation requires is listed in its `security` block, and a valid key without it receives `403` whose `error` names the missing scope. A key keeps its scopes until a tenant administrator changes them in the Backoffice (API Configuration, Edit scopes on the key); a new key is the other way. A missing, malformed, unknown or revoked key, or a key of a suspended organisation, receives a `401` whose body is identical for every cause. Create and revoke keys in the Backoffice under Integrations & API. Use a key only from your own servers — never from a browser or a mobile app."
      },
      "ApmIngestKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_apm_<random>",
        "description": "Dedicated ingestion key (`sk_apm_...`, matching `^sk_apm_[A-Za-z0-9_-]{24,256}$`), created per project and environment under Integrations & API → Ingestion API keys. It carries an allowlist of signals chosen at creation and is the ONLY credential the ingestion operations accept: a server API key (`sk_...`), however broad its scopes, is rejected there, and an `sk_apm_` key is rejected on every other route."
      },
      "CodingAgentKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "cak_<random>",
        "description": "Coding-agent read key (`Authorization: Bearer cak_...`, matching `^cak_[A-Za-z0-9_-]{24,256}$`), created under Integrations & API → Coding agent keys with a chosen set of scopes. It is accepted only by the coding-agent read operations, on the ingestion hosts; a server API key (`sk_...`) or an ingestion key (`sk_apm_...`) is rejected there."
      }
    },
    "parameters": {
      "IdempotencyKeyRequired": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "description": "A replayed key returns the byte-identical stored response — no second provider call, no double charge."
      },
      "IdempotencyKeyOptional": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "description": "When supplied, a replayed key returns the SAME minted session (WITHOUT re-exposing its one-shot URL) instead of a fresh mint. Omit entirely for a plain, always-fresh mint."
      },
      "WebhookSignatureHeader": {
        "name": "X-Noxtica-Signature",
        "in": "header",
        "required": true,
        "description": "`sha256=` followed by the lowercase hex HMAC-SHA256 of the string `<X-Noxtica-Timestamp>.<raw request body>`, keyed with the endpoint's signing secret (the secret string's UTF-8 bytes). Verify against the raw body bytes before parsing, with a constant-time comparison.",
        "schema": {
          "type": "string",
          "pattern": "^sha256=[0-9a-f]{64}$",
          "example": "sha256=9f2c4b7e1a8d3c6f0b5e7a9d2c4f6e8b1a3d5c7e9f0b2d4f6a8c0e1b3d5f7a9c"
        }
      },
      "WebhookTimestampHeader": {
        "name": "X-Noxtica-Timestamp",
        "in": "header",
        "required": true,
        "description": "Unix time in milliseconds at which this attempt was signed. Every retry is re-signed with a fresh timestamp, so compare it with your own clock (a 300 second tolerance is recommended; Noxtica itself enforces none), not with the body's `created_at`.",
        "schema": {
          "type": "integer",
          "format": "int64",
          "example": 1790762445123
        }
      },
      "WebhookEventHeader": {
        "name": "X-Noxtica-Event",
        "in": "header",
        "required": true,
        "description": "The event type; equals `type` in the body.",
        "schema": {
          "type": "string",
          "example": "kyc.session.completed"
        }
      },
      "WebhookDeliveryHeader": {
        "name": "X-Noxtica-Delivery",
        "in": "header",
        "required": true,
        "description": "The delivery id; equals `id` in the body and `data.deliveryId`. It is identical on every retry of the same delivery: use it as the deduplication key.",
        "schema": {
          "type": "string",
          "example": "6d3c3a8e-6b4b-4a8f-8f4e-0a0e6b3d9d21:wh_7f3a2c"
        }
      },
      "TenantAppId": {
        "name": "appId",
        "in": "query",
        "required": false,
        "description": "Narrows an `externalUserId` that is linked in several applications of the tenant (else `409 ambiguous_subject`).",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_.:-]{1,64}$"
        }
      },
      "TenantLinkSubject": {
        "name": "subject",
        "in": "path",
        "required": true,
        "description": "The pairwise `plnk_…` subject or the tenant's own `externalUserId` (1..256 characters, percent-encoded).",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256
        },
        "example": "plnk_01M4GW6CR553W8AAWXEG17RDJW"
      },
      "ApmListLimit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "ApmQueryLimit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "ApmCursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 512
        }
      },
      "ApmProjectIdPath": {
        "name": "projectId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ApmScopedId"
        }
      },
      "ApmEnvironmentIdPath": {
        "name": "environmentId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ApmScopedId"
        }
      },
      "ApmProjectIdQuery": {
        "name": "project_id",
        "in": "query",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ApmScopedId"
        }
      },
      "ApmEnvironmentIdQuery": {
        "name": "environment_id",
        "in": "query",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ApmScopedId"
        }
      },
      "ApmFrom": {
        "name": "from",
        "in": "query",
        "required": true,
        "description": "Inclusive RFC 3339 range start. The interval to `to` must not exceed 31 days.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "ApmTo": {
        "name": "to",
        "in": "query",
        "required": true,
        "description": "Exclusive RFC 3339 range end, after `from`; the interval must not exceed 31 days.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "ApmTraceIdPath": {
        "name": "traceId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^(?!0{32}$)[0-9a-f]{32}$"
        }
      },
      "ApmServiceNamePath": {
        "name": "serviceName",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256,
          "pattern": "^[A-Za-z0-9._:/-]+$"
        }
      },
      "ApmReleaseIdPath": {
        "name": "releaseId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ApmScopedId"
        }
      },
      "ApmExportIdPath": {
        "name": "exportId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ApmScopedId"
        }
      },
      "ApmSubjectHandlePath": {
        "name": "subjectHandle",
        "in": "path",
        "required": true,
        "description": "Opaque subject handle; hashed before authentication, logging, storage, or repository access.",
        "schema": {
          "type": "string",
          "minLength": 16,
          "maxLength": 512,
          "pattern": "^[A-Za-z0-9._~:/+-]+$"
        }
      },
      "ApmPrivacyRequestIdPath": {
        "name": "requestId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ApmScopedId"
        }
      },
      "WebhookId": {
        "name": "webhookId",
        "in": "path",
        "required": true,
        "description": "The id of one of your webhook endpoints (`id` of the endpoint object).",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{1,128}$"
        }
      },
      "v1_WebhookEventHeader": {
        "name": "X-Noxtica-Event",
        "in": "header",
        "required": true,
        "description": "The event type; equals `type` in the body.",
        "schema": {
          "type": "string",
          "example": "submission.scored"
        }
      },
      "v1_WebhookDeliveryHeader": {
        "name": "X-Noxtica-Delivery",
        "in": "header",
        "required": true,
        "description": "The delivery id; equals `id` in the body and is identical on every retry of the same delivery. Deduplicate on it.",
        "schema": {
          "type": "string"
        }
      }
    },
    "headers": {
      "IdentityCacheControl": {
        "description": "Always `no-store, private`. Never cache or store a response of an identity details route or of the review decision route.",
        "schema": {
          "type": "string",
          "example": "no-store, private"
        }
      },
      "EvidenceNoStore": {
        "description": "Always `no-store, private`. Never cache or store a response of an evidence image route.",
        "schema": {
          "type": "string",
          "example": "no-store, private"
        }
      },
      "EvidenceContentDisposition": {
        "description": "Always `inline`, with the file name `evidence-<itemId>.<jpg|png|webp>`; the extension follows the image type.",
        "schema": {
          "type": "string",
          "example": "inline; filename=\"evidence-evd_example_0001.jpg\""
        }
      },
      "RateLimitLimit": {
        "description": "The request limit of the token's bucket.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Unix time, in seconds, at which the window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitBucket": {
        "description": "The name of the rate-limit bucket (`mcp`).",
        "schema": {
          "type": "string"
        }
      },
      "NoStore": {
        "description": "APM control/query responses are never cacheable.",
        "schema": {
          "type": "string",
          "const": "no-store"
        }
      }
    },
    "responses": {
      "InvalidRequest": {
        "description": "The request failed validation. `error` is a route-specific code such as `invalid_request`; read the operation description for the exact values it can return.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": "invalid_request",
              "requestId": "req-test"
            }
          }
        }
      },
      "ScopeForbidden": {
        "description": "The presented key lacks the required scope.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "example": "API key does not have write:identity scope"
                },
                "requestId": {
                  "type": "string"
                }
              }
            },
            "example": {
              "error": "API key does not have write:identity scope",
              "requestId": "req-test"
            }
          }
        }
      },
      "GatedNotFound": {
        "description": "The byte-identical \"V1 endpoint not found\" response returned when the route's module is not enabled for your organisation — indistinguishable from a route that does not exist (anti-enumeration by design; do not treat this as proof the path is wrong).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "enum": [
                    "V1 endpoint not found"
                  ]
                },
                "path": {
                  "type": "string"
                },
                "requestId": {
                  "type": "string"
                }
              }
            },
            "example": {
              "error": "V1 endpoint not found",
              "path": "/v1/kyc/verify",
              "requestId": "req-test"
            }
          }
        }
      },
      "BodyTooLarge": {
        "description": "`body_too_large` — the request exceeded this route's byte cap.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": "body_too_large",
              "requestId": "req-test"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. Wait `Retry-After` seconds (also `retryAfter` in the body) before retrying; the response carries no other rate-limit headers.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until the bucket allows another request."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "enum": [
                    "Rate limit exceeded"
                  ]
                },
                "retryAfter": {
                  "type": "integer"
                },
                "requestId": {
                  "type": "string"
                }
              }
            },
            "example": {
              "error": "Rate limit exceeded",
              "retryAfter": 1,
              "requestId": "req-test"
            }
          }
        }
      },
      "SessionExpired": {
        "description": "The session has expired — past its effective `expiresAt` (the original mint TTL while unopened, or `openedAt` plus the 30-minute capture window once opened) and never reached a terminal/completed state. The hosted verification page MUST stop polling on this response.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/KycSessionExpiredError"
            },
            "example": {
              "error": "session_expired",
              "code": "kyc_session_expired",
              "requestId": "req-test"
            }
          }
        }
      },
      "SessionExpiredOrCancelled": {
        "description": "The session can no longer be used by the hosted verification page, in one of two byte-stable shapes told apart by `code`. `kyc_session_expired` (`error: session_expired`) — the session is past its effective `expiresAt` (the original mint TTL while unopened, or `openedAt` plus the 30-minute capture window once opened) and never reached a terminal/completed state. `kyc_session_cancelled` (`error: session_cancelled`) — the end customer cancelled the session via `POST /v1/kyc/session-cancel` (terminal status `cancelled_by_user`). The hosted verification page MUST stop polling on either response.",
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/KycSessionExpiredError"
                },
                {
                  "$ref": "#/components/schemas/KycSessionCancelledError"
                }
              ]
            },
            "examples": {
              "expired": {
                "value": {
                  "error": "session_expired",
                  "code": "kyc_session_expired",
                  "requestId": "req-test"
                }
              },
              "cancelled": {
                "value": {
                  "error": "session_cancelled",
                  "code": "kyc_session_cancelled",
                  "requestId": "req-test"
                }
              }
            }
          }
        }
      },
      "GatedOrDomainNotFound": {
        "description": "Either the byte-identical \"V1 endpoint not found\" response returned when the route's module is not enabled for your organisation (see `GatedNotFound`), or `domain_not_found`: the `domainId` or `siteKey` is unknown, names a disabled domain, or belongs to another organisation. The three causes of `domain_not_found` get the same answer by design.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/KycGatedOrDomainNotFoundError"
            },
            "examples": {
              "gated": {
                "value": {
                  "error": "V1 endpoint not found",
                  "path": "/v1/kyc/sessions",
                  "requestId": "req-test"
                }
              },
              "domainNotFound": {
                "value": {
                  "error": "domain_not_found",
                  "requestId": "req-test"
                }
              }
            }
          }
        }
      },
      "InvalidApiKey": {
        "description": "The `Authorization: Bearer sk_...` header is missing or malformed, or the key is unknown or revoked. The body is deliberately identical for every one of those causes.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/KycSessionError"
            },
            "example": {
              "error": "invalid_api_key",
              "message": "Invalid or revoked API key",
              "requestId": "req-test"
            }
          }
        }
      },
      "ServerMisconfigured": {
        "description": "API-key authentication is not configured on this deployment (`server_misconfigured`). Retrying does not help; contact support with the `requestId`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/KycSessionError"
            },
            "example": {
              "error": "server_misconfigured",
              "message": "API key authentication is not configured on this server",
              "requestId": "req-test"
            }
          }
        }
      },
      "EvidenceInvalidRequest": {
        "description": "`invalid_request` — the request could not be parsed, or a query value is out of range. Nothing was read.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/EvidenceNoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": "invalid_request",
              "requestId": "req-test"
            }
          }
        }
      },
      "EvidenceReadForbidden": {
        "description": "`evidence_read_not_enabled` — a tenant administrator has not enabled evidence image access for your organisation (or withdrew it), so nothing was read; `evidence_storage_not_armed` — evidence storage is not on for your organisation; or the key lacks the `read:evidence` scope (`error` is `API key does not have read:evidence scope`). A tenant administrator fixes the first two in the Backoffice, KYC → Invitations → Settings tab (cards \"Evidence image API\" and \"Evidence storage\").",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/EvidenceNoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/KycEvidenceForbiddenError"
            },
            "examples": {
              "notEnabled": {
                "summary": "Evidence image access is not enabled for the organisation",
                "value": {
                  "error": "evidence_read_not_enabled",
                  "requestId": "req-test"
                }
              },
              "storageNotOn": {
                "summary": "Evidence storage is not on for the organisation",
                "value": {
                  "error": "evidence_storage_not_armed",
                  "requestId": "req-test"
                }
              },
              "scopeMissing": {
                "summary": "The key does not carry the scope",
                "value": {
                  "error": "API key does not have read:evidence scope",
                  "requestId": "req-test"
                }
              }
            }
          }
        }
      },
      "EvidenceReadNotFound": {
        "description": "The byte-identical \"V1 endpoint not found\" response, returned for a malformed id, an id that does not exist, an id or an image that belongs to another organisation or to another verification, an item that is not a readable image, or a route that is not available for your organisation: indistinguishable from an unknown path by design.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/EvidenceNoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "enum": [
                    "V1 endpoint not found"
                  ]
                },
                "path": {
                  "type": "string"
                },
                "requestId": {
                  "type": "string"
                }
              }
            },
            "example": {
              "error": "V1 endpoint not found",
              "path": "/v1/kyc/verifications/3fa85f64-5717-4562-b3fc-2c963f66afa6/evidence",
              "requestId": "req-test"
            }
          }
        }
      },
      "EvidenceNotAvailable": {
        "description": "`evidence_not_available` — the item is an image of this verification, but its storage did not finish successfully, so it cannot be opened. `reason` is `not_usable`.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/EvidenceNoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/KycEvidenceNotAvailableError"
            },
            "example": {
              "error": "evidence_not_available",
              "reason": "not_usable",
              "requestId": "req-test"
            }
          }
        }
      },
      "EvidenceVerificationDeleted": {
        "description": "`verification_deleted` — you deleted this verification; its images no longer exist.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/EvidenceNoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": "verification_deleted",
              "requestId": "req-test"
            }
          }
        }
      },
      "EvidenceReadUnavailable": {
        "description": "`evidence_audit_unavailable` or `evidence_unavailable` — retryable. The access could not be recorded, so nothing was released; or the stored images could not be read right now.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/EvidenceNoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": "evidence_unavailable",
              "requestId": "req-test"
            }
          }
        }
      },
      "IdentityInvalidFields": {
        "description": "`invalid_fields` or `invalid_request` — `fields` names a value this route does not accept (`allowed` lists the accepted values), or the request could not be parsed. Nothing was read.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/IdentityCacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/IdentityInvalidFieldsError"
            },
            "example": {
              "error": "invalid_fields",
              "allowed": [
                "documentNumber",
                "personalNumber",
                "mrzLines"
              ],
              "requestId": "req-test"
            }
          }
        }
      },
      "IdentityReadForbidden": {
        "description": "`identity_read_not_enabled` — a tenant administrator has not enabled identity details access for your organisation, so nothing was read; or the key lacks the `read:identity_details` scope (`error` is `API key does not have read:identity_details scope`).",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/IdentityCacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/IdentityForbiddenError"
            },
            "examples": {
              "notEnabled": {
                "summary": "Identity details access is not enabled for the organisation",
                "value": {
                  "error": "identity_read_not_enabled",
                  "message": "Identity details access is not enabled for this organisation. A tenant administrator can enable it in the Backoffice (KYC settings).",
                  "requestId": "req-test"
                }
              },
              "scopeMissing": {
                "summary": "The key does not carry the scope",
                "value": {
                  "error": "API key does not have read:identity_details scope",
                  "requestId": "req-test"
                }
              }
            }
          }
        }
      },
      "IdentityNotFound": {
        "description": "The byte-identical \"V1 endpoint not found\" response, returned for a malformed id, an id that does not exist, an id that belongs to another organisation, or a route that is not available for your organisation — indistinguishable from an unknown path by design.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/IdentityCacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "enum": [
                    "V1 endpoint not found"
                  ]
                },
                "path": {
                  "type": "string"
                },
                "requestId": {
                  "type": "string"
                }
              }
            },
            "example": {
              "error": "V1 endpoint not found",
              "path": "/v1/kyc/verifications/3fa85f64-5717-4562-b3fc-2c963f66afa6/identity",
              "requestId": "req-test"
            }
          }
        }
      },
      "IdentityUnavailable": {
        "description": "`identity_audit_unavailable` or `identity_unavailable` — retryable. The access could not be recorded, so nothing was released; or the stored details could not be read right now.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/IdentityCacheControl"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": "identity_unavailable",
              "requestId": "req-test"
            }
          }
        }
      },
      "ApiError": {
        "description": "Failure in the platform `apiError` envelope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "pulse_RateLimited": {
        "description": "Too many attempts.",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the window resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "TenantUnauthorized": {
        "description": "`missing_api_key` or `invalid_api_key` (malformed, unknown, revoked, or a device token). `WWW-Authenticate: Bearer realm=\"pulse-tenant\"`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "TenantForbidden": {
        "description": "`insufficient_scope` (the key lacks the scope the operation names) or `tenant_not_armed`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "TenantUnavailable": {
        "description": "`tenant_auth_unavailable` or `rate_limiter_unavailable` (fail closed).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "v1_InvalidRequest": {
        "description": "The request failed validation. `error` is a route-specific string such as `Invalid body` or `invalid_request`; read the operation description for the exact values it can return.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/v1_ErrorEnvelope"
            },
            "example": {
              "error": "Invalid body",
              "requestId": "req-test"
            }
          }
        }
      },
      "v1_InvalidApiKey": {
        "description": "The `Authorization: Bearer sk_...` header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/InvalidApiKeyError"
            },
            "example": {
              "error": "invalid_api_key",
              "message": "Invalid or revoked API key",
              "requestId": "req-test"
            }
          }
        }
      },
      "v1_ScopeForbidden": {
        "description": "The API key is valid but does not carry the scope this operation requires; `error` names the missing scope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ScopeForbiddenError"
            },
            "example": {
              "error": "API key does not have read:device scope",
              "requestId": "req-test"
            }
          }
        }
      },
      "v1_GatedNotFound": {
        "description": "The byte-identical \"V1 endpoint not found\" response returned when the route's module is not enabled for your organisation, and also for a path that does not exist — indistinguishable by design (anti-enumeration), so do not treat it as proof the path is wrong.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "enum": [
                    "V1 endpoint not found"
                  ]
                },
                "path": {
                  "type": "string"
                },
                "requestId": {
                  "type": "string"
                }
              }
            },
            "example": {
              "error": "V1 endpoint not found",
              "path": "/v1/score",
              "requestId": "req-test"
            }
          }
        }
      },
      "v1_BodyTooLarge": {
        "description": "The request body exceeded this route's byte cap.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/v1_ErrorEnvelope"
            },
            "example": {
              "error": "body_too_large",
              "requestId": "req-test"
            }
          }
        }
      },
      "v1_RateLimited": {
        "description": "Rate limit exceeded. Wait `Retry-After` seconds (also `retryAfter` in the body) before retrying; the response carries no other rate-limit headers.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until the bucket allows another request."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error",
                "retryAfter"
              ],
              "properties": {
                "error": {
                  "type": "string",
                  "enum": [
                    "Rate limit exceeded"
                  ]
                },
                "retryAfter": {
                  "type": "integer",
                  "description": "Seconds to wait; equals the `Retry-After` header."
                },
                "requestId": {
                  "type": "string"
                }
              }
            },
            "example": {
              "error": "Rate limit exceeded",
              "retryAfter": 12,
              "requestId": "req-test"
            }
          }
        }
      },
      "WebhookRateLimited": {
        "description": "`rate_limit_exceeded`: too many requests. Wait `Retry-After` seconds (also `retryAfter` in the body) before retrying. The test delivery has a tighter limit than the other operations.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until the bucket allows another request."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WebhookApiError"
            },
            "example": {
              "error": "rate_limit_exceeded",
              "retryAfter": 12,
              "requestId": "req-test"
            }
          }
        }
      },
      "WebhookNotFound": {
        "description": "`webhook_not_found`: no endpoint with this id exists in your organisation (an id of another organisation gets the same answer).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WebhookApiError"
            },
            "example": {
              "error": "webhook_not_found",
              "requestId": "req-test"
            }
          }
        }
      },
      "WebhookApiUnavailable": {
        "description": "`webhooks_unavailable`: a temporary fault, or `webhook_secret_kek_required`: encryption of webhook secrets is not available for your organisation, so nothing was written. Retry with backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WebhookApiError"
            },
            "example": {
              "error": "webhooks_unavailable",
              "requestId": "req-test"
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server fault. Retry with backoff and quote `requestId` to support if it persists.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/v1_ErrorEnvelope"
            },
            "example": {
              "error": "Internal server error",
              "requestId": "req-test"
            }
          }
        }
      },
      "ApmInvalid": {
        "description": "Closed-input validation failed.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApmError"
            },
            "example": {
              "ok": false,
              "error": "invalid_request",
              "reason": "invalid_range",
              "requestId": "req-test"
            }
          }
        }
      },
      "ApmUnauthorized": {
        "description": "Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApmError"
            },
            "example": {
              "ok": false,
              "error": "Unauthorized",
              "requestId": "req-test"
            }
          }
        }
      },
      "ApmForbidden": {
        "description": "The server API key lacks the operation's required scope.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApmError"
            },
            "example": {
              "ok": false,
              "error": "API key does not have read:apm scope",
              "requestId": "req-test"
            }
          }
        }
      },
      "ApmNotFound": {
        "description": "The tenant-scoped resource was not found.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApmError"
            },
            "example": {
              "ok": false,
              "error": "resource_not_found",
              "requestId": "req-test"
            }
          }
        }
      },
      "ApmRateLimited": {
        "description": "V1 read rate limit exceeded.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApmRateLimitError"
            },
            "example": {
              "ok": false,
              "error": "Rate limit exceeded",
              "retryAfter": 60,
              "requestId": "req-test"
            }
          }
        }
      },
      "ApmControlUnavailable": {
        "description": "Authoritative control-plane data is unavailable.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApmUnavailable"
            },
            "example": {
              "ok": false,
              "available": false,
              "reason": "control_plane_unavailable",
              "requestId": "req-test"
            }
          }
        }
      },
      "ApmWarehouseUnavailable": {
        "description": "Warehouse query or mandatory access audit is unavailable.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApmUnavailable"
            },
            "example": {
              "ok": false,
              "available": false,
              "reason": "warehouse_unavailable",
              "requestId": "req-test"
            }
          }
        }
      },
      "ApmInternal": {
        "description": "Unexpected internal trace-query failure.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApmError"
            },
            "example": {
              "ok": false,
              "error": "internal_error",
              "requestId": "req-test"
            }
          }
        }
      },
      "CodingAgentUnauthorized": {
        "description": "`coding_agent_auth_missing` (no `Authorization` header) or `coding_agent_auth_invalid` (a malformed, unknown, revoked or inactive coding-agent key).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CodingAgentError"
            },
            "example": {
              "code": 401,
              "message": "Authentication failed",
              "error": "coding_agent_auth_invalid"
            }
          }
        }
      },
      "CodingAgentScopeDenied": {
        "description": "`coding_agent_scope_denied` — the key is valid but lacks the scope this route requires.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CodingAgentError"
            },
            "example": {
              "code": 403,
              "message": "Authentication failed",
              "error": "coding_agent_scope_denied"
            }
          }
        }
      },
      "CodingAgentRateLimited": {
        "description": "`coding_agent_rate_limited` — the per-key request limit was exceeded. Wait `Retry-After` seconds before retrying.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until the key may call again."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CodingAgentError"
            },
            "example": {
              "code": 429,
              "message": "Rate limit exceeded for this coding-agent key",
              "error": "coding_agent_rate_limited"
            }
          }
        }
      },
      "CodingAgentQueryFailed": {
        "description": "`coding_agent_query_failed` — the read query failed. Retry with backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CodingAgentError"
            },
            "example": {
              "code": 502,
              "message": "Read query failed",
              "error": "coding_agent_query_failed"
            }
          }
        }
      },
      "CodingAgentUnavailable": {
        "description": "`coding_agent_authority_unavailable` (the key could not be checked) or `coding_agent_bq_unavailable` (the read store is not available). Retry with backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CodingAgentError"
            },
            "example": {
              "code": 503,
              "message": "Authority lookup failed",
              "error": "coding_agent_authority_unavailable"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "CountryCode": {
        "type": "string",
        "description": "An ISO 3166-1 alpha-2 country code in uppercase, for example `TR`, `GB` or `US`: one of the 249 officially assigned codes. Reserved and user-assigned codes such as `ZZ`, `XK`, `EU` and `UK` are not country codes here, and the code `UK` is `GB`.",
        "enum": [
          "AD",
          "AE",
          "AF",
          "AG",
          "AI",
          "AL",
          "AM",
          "AO",
          "AQ",
          "AR",
          "AS",
          "AT",
          "AU",
          "AW",
          "AX",
          "AZ",
          "BA",
          "BB",
          "BD",
          "BE",
          "BF",
          "BG",
          "BH",
          "BI",
          "BJ",
          "BL",
          "BM",
          "BN",
          "BO",
          "BQ",
          "BR",
          "BS",
          "BT",
          "BV",
          "BW",
          "BY",
          "BZ",
          "CA",
          "CC",
          "CD",
          "CF",
          "CG",
          "CH",
          "CI",
          "CK",
          "CL",
          "CM",
          "CN",
          "CO",
          "CR",
          "CU",
          "CV",
          "CW",
          "CX",
          "CY",
          "CZ",
          "DE",
          "DJ",
          "DK",
          "DM",
          "DO",
          "DZ",
          "EC",
          "EE",
          "EG",
          "EH",
          "ER",
          "ES",
          "ET",
          "FI",
          "FJ",
          "FK",
          "FM",
          "FO",
          "FR",
          "GA",
          "GB",
          "GD",
          "GE",
          "GF",
          "GG",
          "GH",
          "GI",
          "GL",
          "GM",
          "GN",
          "GP",
          "GQ",
          "GR",
          "GS",
          "GT",
          "GU",
          "GW",
          "GY",
          "HK",
          "HM",
          "HN",
          "HR",
          "HT",
          "HU",
          "ID",
          "IE",
          "IL",
          "IM",
          "IN",
          "IO",
          "IQ",
          "IR",
          "IS",
          "IT",
          "JE",
          "JM",
          "JO",
          "JP",
          "KE",
          "KG",
          "KH",
          "KI",
          "KM",
          "KN",
          "KP",
          "KR",
          "KW",
          "KY",
          "KZ",
          "LA",
          "LB",
          "LC",
          "LI",
          "LK",
          "LR",
          "LS",
          "LT",
          "LU",
          "LV",
          "LY",
          "MA",
          "MC",
          "MD",
          "ME",
          "MF",
          "MG",
          "MH",
          "MK",
          "ML",
          "MM",
          "MN",
          "MO",
          "MP",
          "MQ",
          "MR",
          "MS",
          "MT",
          "MU",
          "MV",
          "MW",
          "MX",
          "MY",
          "MZ",
          "NA",
          "NC",
          "NE",
          "NF",
          "NG",
          "NI",
          "NL",
          "NO",
          "NP",
          "NR",
          "NU",
          "NZ",
          "OM",
          "PA",
          "PE",
          "PF",
          "PG",
          "PH",
          "PK",
          "PL",
          "PM",
          "PN",
          "PR",
          "PS",
          "PT",
          "PW",
          "PY",
          "QA",
          "RE",
          "RO",
          "RS",
          "RU",
          "RW",
          "SA",
          "SB",
          "SC",
          "SD",
          "SE",
          "SG",
          "SH",
          "SI",
          "SJ",
          "SK",
          "SL",
          "SM",
          "SN",
          "SO",
          "SR",
          "SS",
          "ST",
          "SV",
          "SX",
          "SY",
          "SZ",
          "TC",
          "TD",
          "TF",
          "TG",
          "TH",
          "TJ",
          "TK",
          "TL",
          "TM",
          "TN",
          "TO",
          "TR",
          "TT",
          "TV",
          "TW",
          "TZ",
          "UA",
          "UG",
          "UM",
          "US",
          "UY",
          "UZ",
          "VA",
          "VC",
          "VE",
          "VG",
          "VI",
          "VN",
          "VU",
          "WF",
          "WS",
          "YE",
          "YT",
          "ZA",
          "ZM",
          "ZW"
        ],
        "example": "TR"
      },
      "CountryCodeList": {
        "type": "array",
        "description": "A list of `CountryCode` values: at least 1 and at most 249 entries, no duplicates, so the longest valid list is every country. The order you send is kept.",
        "minItems": 1,
        "maxItems": 249,
        "uniqueItems": true,
        "items": {
          "$ref": "#/components/schemas/CountryCode"
        },
        "example": [
          "TR",
          "GB",
          "US"
        ]
      },
      "ConflictError": {
        "type": "object",
        "description": "A `409` from `POST /v1/kyc/verify`: an in-flight or idempotency-key-bound conflict, or `evidence_incomplete` (with `missingEvidence`) when the named evidence group is not yet complete.",
        "properties": {
          "error": {
            "type": "string",
            "example": "idempotency_conflict"
          },
          "code": {
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          },
          "verificationId": {
            "$ref": "#/components/schemas/KycVerificationId"
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "request_in_progress"
              ]
            }
          },
          "requestId": {
            "type": "string"
          },
          "missingEvidence": {
            "type": "array",
            "description": "Present only with `evidence_incomplete`.",
            "items": {
              "type": "string",
              "enum": [
                "front",
                "back",
                "selfie"
              ]
            }
          }
        }
      },
      "KycVerifyMintRequest": {
        "type": "object",
        "required": [
          "action"
        ],
        "description": "`POST /v1/kyc/verify` with `action:'mint'`. Mints a fresh, tenant-scoped, single-use admission nonce (TTL 300s) bound in KV to the calling tenant — the ONLY way a server-to-server caller can legitimately obtain a nonce a subsequent `action:'verify'` call will accept. Runs the same auth/module-gate/legal-basis preflight as verify; NEVER meters, NEVER reserves spend, makes ZERO provider calls. The `Idempotency-Key` header is not required (and is ignored) for this branch.",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "mint"
            ],
            "description": "Selects the mint branch of this endpoint."
          },
          "evidenceGroup": {
            "type": "boolean",
            "default": false,
            "description": "When `true`, the response also carries an `evidenceGroupId` you upload your own captures into before verifying (see `POST /v1/kyc/evidence/handle`). Needs evidence storage enabled for your organisation; otherwise the call is a `404`. Omitted or `false` returns the plain nonce."
          }
        }
      },
      "KycVerifyRequest": {
        "description": "The body of `POST /v1/kyc/verify` — either branch selected by `action` (`verify` is the implicit default when `action` is omitted). Named as its own component (rather than an inline `oneOf`) so `openapi/generate.sh`'s csharp/generichost codegen produces a distinct composed wrapper type instead of colliding with either member schema's own generated name.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/KycVerifyMintRequest"
          },
          {
            "$ref": "#/components/schemas/KycVerifyVerifyRequest"
          }
        ]
      },
      "KycVerifyVerifyRequest": {
        "type": "object",
        "required": [
          "nonce",
          "id_type"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "verify"
            ],
            "default": "verify",
            "description": "Optional; `verify` is the implicit default when this field is omitted — every pre-existing caller is unaffected."
          },
          "nonce": {
            "type": "string",
            "description": "A fresh, tenant-scoped single-use admission nonce (minted by your own prior client-side flow / SDK step)."
          },
          "id_type": {
            "type": "string",
            "description": "Document/verification type hint for the resolved provider adapter (aliases: idType)."
          },
          "purpose": {
            "type": "string",
            "default": "identity_verification",
            "description": "One of the closed evidence-purpose catalog values."
          },
          "audience": {
            "type": "string",
            "description": "Defaults to the caller's own tenant id."
          },
          "deviceContext": {
            "type": "boolean",
            "description": "When true and device-intel is armed for the tenant, the response carries an ADVISORY deviceTrust block. Never influences the KYC verdict itself."
          },
          "payload": {
            "type": "object",
            "description": "Provider-specific verification payload (never logged or persisted verbatim). `verifiedDateOfBirth` and `minimumAge` keys are always stripped server-side before reaching any adapter — supplying them has no effect (defense-in-depth against self-attested age/DOB). With `evidenceGroupId` the uploaded evidence is resolved from the group; supplying `evidenceObjectIds` next to it is a `400`.",
            "properties": {
              "evidenceGroupId": {
                "type": "string",
                "format": "uuid",
                "description": "The `evidenceGroupId` returned by `action:'mint'` with `evidenceGroup:true`, after the document and selfie images were uploaded into it. The result's `verificationId` equals this id."
              },
              "jurisdiction": {
                "type": "string",
                "enum": [
                  "TR",
                  "EU",
                  "US"
                ],
                "description": "Optional with `evidenceGroupId`; must equal your organisation's evidence residency."
              },
              "operation": {
                "type": "string",
                "enum": [
                  "identity"
                ],
                "description": "Optional with `evidenceGroupId`; only `identity` is available through a group."
              }
            }
          }
        }
      },
      "KycVerifyResponse": {
        "type": "object",
        "required": [
          "ok",
          "verdict",
          "verificationLevel",
          "reasonCodes",
          "verificationId",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "verdict": {
            "type": "string",
            "enum": [
              "verified",
              "unverified",
              "error"
            ],
            "description": "`error` is an infra/provider fault, never a statement about the subject — always HTTP 200 (FR-6)."
          },
          "verificationLevel": {
            "type": "string",
            "enum": [
              "verified",
              "partial",
              "unverified",
              "inconclusive"
            ]
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KycReasonCode"
            }
          },
          "verificationId": {
            "type": "string",
            "format": "uuid"
          },
          "requestId": {
            "type": "string"
          },
          "legalBasisRef": {
            "type": "string",
            "description": "Present only when this request's tenant is running under classified legal-basis receipt gating."
          },
          "nfcAssurance": {
            "type": "object",
            "description": "Present only for eMRTD/passport-chip verification paths."
          },
          "deviceTrust": {
            "type": "object",
            "description": "Present only when the request set deviceContext:true and device-intel evidence is available. ADVISORY ONLY — never fed back into verdict/reasonCodes."
          }
        }
      },
      "KycVerifyMintResponse": {
        "type": "object",
        "required": [
          "ok",
          "nonce",
          "ttlSeconds"
        ],
        "description": "The success shape for `POST /v1/kyc/verify` with `action:'mint'`.",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "nonce": {
            "type": "string",
            "format": "uuid",
            "description": "Pass back verbatim as `nonce` on the follow-up `action:'verify'` (or omitted-action) call, from the SAME tenant, before it expires."
          },
          "ttlSeconds": {
            "type": "integer",
            "description": "Absolute KV expiry from mint time (300s as of 2026-09-13, `KYC_NONCE_TTL_SECONDS`). A verify attempt after this window elapses returns `reasonCodes:[nonce_expired]`; an unminted, already-consumed, or wrong-tenant nonce returns `reasonCodes:[nonce_unknown]`."
          },
          "evidenceGroupId": {
            "type": "string",
            "format": "uuid",
            "description": "Present only when the request set `evidenceGroup:true`. Pass it as `evidenceGroupId` to `POST /v1/kyc/evidence/handle`, then as `payload.evidenceGroupId` on the verify call."
          },
          "evidenceGroupExpiresAt": {
            "type": "integer",
            "description": "Present only with `evidenceGroupId`. Unix milliseconds at which the group closes."
          },
          "evidenceGroupTtlSeconds": {
            "type": "integer",
            "description": "Present only with `evidenceGroupId`. Group lifetime from mint (3600)."
          }
        }
      },
      "KycEvidenceItem": {
        "type": "object",
        "description": "One image a person captured during a verification. It holds no image bytes, hash or storage detail.",
        "required": [
          "itemId",
          "type",
          "side",
          "contentType",
          "size",
          "capturedAt"
        ],
        "properties": {
          "itemId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$",
            "description": "The opaque id of the image. Use it as `itemId` of the access route. Store it in a column that holds at least 128 characters."
          },
          "type": {
            "type": "string",
            "enum": [
              "document",
              "passport_page",
              "proof_of_address",
              "selfie",
              "document_near_face",
              "supporting_document"
            ],
            "description": "What the image shows. Videos, voice recordings and extracted data are never listed."
          },
          "side": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "front",
              "back",
              null
            ],
            "description": "`front` or `back` for a `document`; `null` for every other `type`."
          },
          "contentType": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp"
            ],
            "description": "The type of the image."
          },
          "size": {
            "type": "integer",
            "minimum": 0,
            "description": "The size of the image in bytes."
          },
          "capturedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When Noxtica received the image."
          }
        }
      },
      "KycEvidenceList": {
        "type": "object",
        "description": "The images of one verification that can be opened, newest first, at most 100.",
        "required": [
          "ok",
          "verificationId",
          "items",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "verificationId": {
            "$ref": "#/components/schemas/KycVerificationId"
          },
          "items": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/KycEvidenceItem"
            }
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycEvidenceAccess": {
        "type": "object",
        "description": "A signed URL for one image. It is valid for 120 seconds; whoever holds it can fetch the image until then.",
        "required": [
          "ok",
          "verificationId",
          "itemId",
          "url",
          "expiresAt",
          "expiresInSeconds",
          "contentType",
          "size",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "verificationId": {
            "$ref": "#/components/schemas/KycVerificationId"
          },
          "itemId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,128}$",
            "description": "The image the URL opens."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The signed URL, on the host you called. Fetch it with `GET` and no `Authorization` header. Treat it as a secret; do not log or store it."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the URL stops working."
          },
          "expiresInSeconds": {
            "type": "integer",
            "enum": [
              120
            ],
            "description": "The fixed lifetime of the URL, in seconds."
          },
          "contentType": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp"
            ],
            "description": "The type of the image the URL returns."
          },
          "size": {
            "type": "integer",
            "minimum": 0,
            "description": "The size of the image in bytes."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycEvidenceAccessEvent": {
        "type": "object",
        "description": "One recorded opening of an image. It never holds image bytes, a signed URL or its token.",
        "required": [
          "id",
          "at",
          "event",
          "itemId",
          "apiKeyId",
          "ip",
          "userAgent"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the record."
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "When it happened."
          },
          "event": {
            "type": "string",
            "enum": [
              "grant",
              "fetch"
            ],
            "description": "`grant` — a signed URL was issued. `fetch` — the image was served."
          },
          "itemId": {
            "type": "string",
            "description": "The image."
          },
          "apiKeyId": {
            "type": "string",
            "description": "The id of the API key that asked for the URL."
          },
          "ip": {
            "type": [
              "string",
              "null"
            ],
            "description": "The address the request came from as Noxtica saw it; `null` when there was none."
          },
          "userAgent": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 256,
            "description": "The `User-Agent` of the request, at most 256 characters; `null` when there was none."
          }
        }
      },
      "KycEvidenceAccessLog": {
        "type": "object",
        "description": "The recorded openings of the images of one verification, newest first.",
        "required": [
          "ok",
          "verificationId",
          "events",
          "nextBefore",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "verificationId": {
            "$ref": "#/components/schemas/KycVerificationId"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KycEvidenceAccessEvent"
            }
          },
          "nextBefore": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "A cursor in Unix milliseconds. Pass it unchanged as `before` to read the next page; `null` on the last page."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycEvidenceForbiddenError": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "`evidence_read_not_enabled`, `evidence_storage_not_armed`, or `API key does not have read:evidence scope`."
          },
          "message": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycEvidenceNotAvailableError": {
        "type": "object",
        "required": [
          "error",
          "reason",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "evidence_not_available"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "not_usable"
            ],
            "description": "`not_usable` — the image is not ready to be opened because its storage did not finish successfully."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycEvidenceGroupRefusal": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "description": "A verify or evidence-handle call that names an evidence group was refused before anything was admitted.",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "evidence_incomplete",
              "evidence_group_expired",
              "evidence_group_not_found",
              "evidence_storage_unavailable",
              "invalid_request"
            ]
          },
          "reason": {
            "type": "string",
            "description": "Present on some `invalid_request` bodies; a closed, non-sensitive reason code."
          },
          "missingEvidence": {
            "type": "array",
            "description": "Present only with `evidence_incomplete`.",
            "items": {
              "type": "string",
              "enum": [
                "front",
                "back",
                "selfie"
              ]
            }
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycReasonCode": {
        "type": "string",
        "description": "The closed, append-only KYC reason-code catalog. A raw provider string is NEVER returned verbatim — every provider token is mapped to one of these or to the generic `provider_unknown` fallback. Grouped families: `provider_*` (transport/lifecycle faults, ≤15), verification-match outcomes (`name_match`/`document_expired`/ `face_match`/`liveness_passed`/etc.), `nonce_*`/`request_*` (admission lifecycle), `subject_*` (linkage), `device_trust_*` (advisory), `adapter_*`/`callback_*` (non-documentary adapter foundation), `account_ownership_*` (bank ownership), `telco_*` (SIM-swap/number-verify — risk signals, never alone `verified`), `age_*` (age assurance), `evidence_*` (post-upload inspection), `policy_*` (response-side enforcement), `emrtd_*` (server-authoritative eMRTD passive authentication), `liveness_injection_*` (injection-detection, separate from the liveness verdict itself), and the three informational identity-extraction codes `identity_fields_unreadable`, `document_type_mismatch_selection` and `issuing_country_mismatch_selection` (see `extraction` on the identity read).",
        "enum": [
          "provider_timeout",
          "provider_unreachable",
          "provider_rejected",
          "provider_error",
          "provider_unknown",
          "provider_rate_limited",
          "provider_invalid_request",
          "provider_unsupported_id_type",
          "provider_manual_review",
          "provider_expired",
          "name_match",
          "name_mismatch",
          "dob_match",
          "dob_mismatch",
          "document_authentic",
          "document_expired",
          "document_tampered",
          "address_match",
          "address_mismatch",
          "face_match",
          "face_mismatch",
          "liveness_passed",
          "liveness_failed",
          "nonce_unknown",
          "nonce_expired",
          "nonce_consumed",
          "nonce_binding_mismatch",
          "request_in_progress",
          "request_storage_error",
          "subject_unavailable",
          "subject_linkage_failed",
          "device_trust_unavailable",
          "adapter_not_configured",
          "adapter_config_invalid",
          "callback_signature_invalid",
          "callback_timestamp_expired",
          "callback_replay",
          "account_ownership_match",
          "account_ownership_mismatch",
          "telco_sim_swap_recent",
          "telco_sim_swap_none",
          "telco_number_verified",
          "telco_number_mismatch",
          "age_meets_threshold",
          "age_below_threshold",
          "age_dob_unavailable",
          "age_estimate_inconclusive",
          "age_policy_not_configured",
          "age_dob_unverified",
          "callback_claim_error",
          "evidence_kind_forbidden",
          "evidence_mime_mismatch",
          "evidence_size_mismatch",
          "evidence_pixels_invalid",
          "evidence_unreadable",
          "policy_blocked",
          "policy_challenge_required",
          "emrtd_malformed_asn1",
          "emrtd_sod_signature_invalid",
          "emrtd_dg_hash_mismatch",
          "emrtd_csca_untrusted",
          "emrtd_csca_expired",
          "emrtd_csca_trust_store_unavailable",
          "emrtd_client_claim_ignored",
          "emrtd_verified",
          "emrtd_dg1_required",
          "emrtd_partial_no_chip_authentication",
          "liveness_injection_check_passed",
          "liveness_injection_check_failed",
          "liveness_injection_check_unavailable",
          "liveness_client_claim_ignored",
          "subject_missing_on_verified",
          "subject_invalid_on_verified",
          "subject_link_failed",
          "identity_fields_unreadable",
          "document_type_mismatch_selection",
          "issuing_country_mismatch_selection"
        ]
      },
      "KycBatchManifestJson": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "maxItems": 500,
            "items": {
              "type": "object",
              "required": [
                "id_type"
              ],
              "properties": {
                "id_type": {
                  "type": "string"
                },
                "purpose": {
                  "type": "string",
                  "default": "identity_verification"
                },
                "payload": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "KycBatchItemErrorCode": {
        "type": "string",
        "enum": [
          "missing_id_type",
          "invalid_row_shape",
          "admission_replay",
          "admission_in_progress",
          "admission_binding_mismatch",
          "admission_expired",
          "admission_storage_error"
        ]
      },
      "KycBatchSubmittedItem": {
        "type": "object",
        "properties": {
          "rowIndex": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "admitted",
              "evaluating",
              "completed",
              "rejected",
              "cancelled"
            ]
          },
          "errorCode": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycBatchItemErrorCode"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "KycBatchSubmitResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "batchId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "cancelled"
            ]
          },
          "itemCount": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KycBatchSubmittedItem"
            }
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycBatchStatusResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "batchId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "cancelled"
            ]
          },
          "itemCount": {
            "type": "integer"
          },
          "pendingCount": {
            "type": "integer"
          },
          "admittedCount": {
            "type": "integer"
          },
          "completedCount": {
            "type": "integer"
          },
          "rejectedCount": {
            "type": "integer"
          },
          "cancelledCount": {
            "type": "integer"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycBatchResultsResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "batchId": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "rowIndex": {
                  "type": "integer"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pending",
                    "admitted",
                    "evaluating",
                    "completed",
                    "rejected",
                    "cancelled"
                  ]
                },
                "verificationId": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "verdict": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "verified",
                    "unverified",
                    "error",
                    null
                  ]
                },
                "errorCode": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/KycBatchItemErrorCode"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KymTransactionResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "eventId": {
            "type": "string"
          },
          "deduplicated": {
            "type": "boolean"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycSessionError": {
        "type": "object",
        "description": "The error body of the hosted-session S2S routes. `error` is a closed machine code; `message` accompanies a few of them; `reason` and `reasonCode` are additive refinements.",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "enum": [
              "pg_unavailable",
              "backend_unavailable",
              "raw_storage_not_armed",
              "residency_unconfigured"
            ],
            "description": "On the retryable `503` answers, says which dependency could not answer (`pg_unavailable`, `backend_unavailable`). On `403 evidence_not_provisioned`, says why the organisation cannot store evidence (`raw_storage_not_armed`, `residency_unconfigured`)."
          },
          "reasonCode": {
            "type": "string",
            "enum": [
              "age_gender_platform_disabled",
              "age_gender_tenant_acknowledgement_missing"
            ],
            "description": "Present on `403 age_gender_estimation_not_enabled` only."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycHostedOptionsError": {
        "type": "object",
        "description": "The `400` body of `POST /v1/kyc/sessions`. `error` is a closed machine code. `field` and `reason` accompany `invalid_hosted_options` when the refusal concerns `hostedOptions.allowedIssuingCountries`.",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "field": {
            "type": "string",
            "description": "The request member that was refused, for example `allowedIssuingCountries`."
          },
          "reason": {
            "type": "string",
            "description": "Refines the refusal. `no_matching_document_country`: none of your configured document types is offered in any country of `hostedOptions.allowedIssuingCountries`."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycSessionEvidenceError": {
        "type": "object",
        "description": "The error body of a browser-facing hosted-session route that can refuse for evidence storage. `error` is a closed machine code; `refusal` accompanies `evidence_refused` only.",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "refusal": {
            "type": "string",
            "enum": [
              "evidence_not_provisioned",
              "residency_unconfigured",
              "residency_refused",
              "binding_unprovisioned",
              "evidence_storage_disabled",
              "verification_not_admitted"
            ],
            "description": "Present on `403 evidence_refused` only. Clients must treat a value they do not know as a generic unavailable answer."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycSessionStatus": {
        "type": "string",
        "enum": [
          "pending",
          "opened",
          "verifying",
          "review_pending",
          "completed",
          "unverified",
          "error",
          "expired",
          "cancelled_by_user"
        ],
        "description": "The hosted session lifecycle status. `pending` — minted, link not opened yet. `opened` — the customer reached the capture page. `verifying` — the customer submitted and the verification is running. `review_pending` — the verification finished and awaits a Backoffice review decision (today every successful hosted capture lands here; it never expires, and `kyc.session.completed` has NOT fired yet). `completed` — a business decision exists (a reviewer's approval or override: read `verification` and `review` on the status read for which). `unverified` — finished with a negative outcome and no manual-review code. `error` — a provider or infrastructure fault, or the submit was refused at admission. `expired` — the effective deadline passed without a decision (the status read reports it with `200`; `kyc.session.completed` never fires for it). `cancelled_by_user` — the customer cancelled on the hosted page."
      },
      "KycSessionCaptureStep": {
        "type": "string",
        "enum": [
          "consent",
          "id_front",
          "id_back",
          "selfie",
          "document_near_face",
          "liveness",
          "voice",
          "review",
          "submit"
        ],
        "description": "The furthest capture step the customer has reached."
      },
      "KycSessionEvidenceRefs": {
        "type": "object",
        "description": "Opaque object ids of the finalized (usable) uploads, by role. Never a URL or media bytes. A role appears only once its upload is finalized.",
        "properties": {
          "front": {
            "type": "string"
          },
          "back": {
            "type": "string"
          },
          "selfie": {
            "type": "string"
          },
          "voice": {
            "type": "string"
          },
          "documentNearFace": {
            "type": "string"
          }
        }
      },
      "KycSessionDecision": {
        "type": "object",
        "description": "The decision block, present once the pipeline or a review decision has produced one. `outcome` maps from the session status alone: `completed` to `approved`, `unverified` to `rejected`, `review_pending` to `review_pending`, `error` to `error`. It is not the review decision: a session a reviewer overrode is `completed` too and reads `approved` here. Use `verification` and `review` to tell an approval from an override.",
        "required": [
          "outcome",
          "reasonCodes",
          "decidedAt"
        ],
        "properties": {
          "outcome": {
            "type": "string",
            "enum": [
              "approved",
              "rejected",
              "review_pending",
              "error"
            ]
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "decidedAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms."
          }
        }
      },
      "KycVerificationId": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128,
        "pattern": "^[A-Za-z0-9:_-]{1,128}$",
        "description": "The identifier of one verification. Treat it as an opaque string and store it in a column that holds at least 128 characters. The API contract fixes two limits that hold for every id: it is never longer than 128 characters, and it only contains the characters `A-Z`, `a-z`, `0-9`, `:`, `_` and `-`. Today an id has one of two shapes. A verification created through `POST /v1/kyc/verify` (including one made from an `evidenceGroupId`, and each item of a batch) is a lower-case UUID: 36 characters, for example `3fa85f64-5717-4562-b3fc-2c963f66afa6`. The verification a hosted session runs when the customer submits is `hosted:` followed by 64 lower-case hexadecimal characters: 71 characters in all. Do not parse an id or rely on its prefix; a new shape may appear, but always within the two limits above.",
        "examples": [
          "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "hosted:be7e72ba6e421664bbe07172cc4fc6102314186dfa595b13664e744ffa8d0434"
        ]
      },
      "KycVerificationStatus": {
        "type": "string",
        "enum": [
          "verified",
          "unverified",
          "failed",
          "error",
          "pending"
        ],
        "description": "The outcome of a verification. `verified`, `unverified`, `failed` and `error` are the verdict words of the automated verification; `pending` means the verification has no verdict yet. A reviewer's `approve` decision turns the reported status into `verified` (see `KycVerificationStatusSource`); no other decision changes it."
      },
      "KycVerificationStatusSource": {
        "type": "string",
        "enum": [
          "pipeline",
          "review"
        ],
        "description": "Who produced the reported `status`. `pipeline` — the automated verdict exactly as it was recorded. `review` — a reviewer's `approve` decision was applied over it. An `override` decision never produces `review`."
      },
      "KycReviewState": {
        "type": "string",
        "enum": [
          "open",
          "in_review",
          "approved",
          "overridden",
          "closed"
        ],
        "description": "State of a reviewer's work on a verification. `in_review` — a reviewer escalated the case without deciding (no decision yet). `approved` — a reviewer approved it. `overridden` — a reviewer replaced the automated verdict. `open` and `closed` belong to the vocabulary but no Backoffice review action writes them today; handle an unknown or unexpected value as \"not decided\"."
      },
      "KycReviewDecision": {
        "type": "string",
        "enum": [
          "approve",
          "override"
        ],
        "description": "A reviewer's decision. `approve` — the reviewer affirmed the verification; the reported status becomes `verified`. `override` — the reviewer replaced the automated verdict; it is reported as it is but is not an approval and never makes the status `verified`. There is no `reject` decision today. An escalation is not a decision: it leaves the decision `null`."
      },
      "KycReviewDecisionReasonCode": {
        "type": "string",
        "enum": [
          "identity_confirmed",
          "registry_crosscheck_passed",
          "name_variant_accepted",
          "manual_document_check_passed",
          "identity_mismatch",
          "registry_crosscheck_failed",
          "document_invalid",
          "suspected_fraud",
          "liveness_not_accepted",
          "other"
        ],
        "description": "The closed reason code of a decision made through the API, never free text. Each decision accepts its own subset. `approve`: `identity_confirmed`, `registry_crosscheck_passed`, `name_variant_accepted`, `manual_document_check_passed`, `other`. `override`: `identity_mismatch`, `registry_crosscheck_failed`, `document_invalid`, `suspected_fraud`, `liveness_not_accepted`, `other`. `other` means none of the listed reasons. A code outside the subset of the decision is refused with `invalid_reason_code`."
      },
      "KycReviewDecisionRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "The decision to record. A member this route does not accept is refused with `invalid_body`.",
        "required": [
          "decision",
          "reasonCode",
          "reviewer"
        ],
        "properties": {
          "decision": {
            "$ref": "#/components/schemas/KycReviewDecision"
          },
          "reasonCode": {
            "$ref": "#/components/schemas/KycReviewDecisionReasonCode"
          },
          "reviewer": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Your own identifier of the person who took the decision: 1 to 128 visible characters, no surrounding spaces, no control characters. Stored with the decision and returned in the response; never sent in a webhook. Use an internal id rather than a name or an email address."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "description": "Optional free text, at most 2000 characters. Stored with the decision; never sent in a webhook and never returned."
          }
        }
      },
      "KycReviewDecisionResult": {
        "type": "object",
        "description": "The decision that now stands. `decision`, `reasonCode` and `reviewer` are the stored decision; on a replay they are those of the first request.",
        "required": [
          "requestId",
          "verificationId",
          "decision",
          "reasonCode",
          "reviewer",
          "state",
          "version",
          "decidedAt",
          "replayed"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "verificationId": {
            "type": "string",
            "description": "The decided verification. Format and limits of a verification id are in `KycVerificationId`."
          },
          "decision": {
            "$ref": "#/components/schemas/KycReviewDecision"
          },
          "reasonCode": {
            "$ref": "#/components/schemas/KycReviewDecisionReasonCode"
          },
          "reviewer": {
            "type": "string",
            "description": "The `reviewer` of the request."
          },
          "state": {
            "type": "string",
            "enum": [
              "approved",
              "overridden"
            ],
            "description": "The review state after the decision, `approved` for `approve` and `overridden` for `override`."
          },
          "version": {
            "type": "integer",
            "minimum": 1,
            "description": "The review version after the decision (`review.version` on the reads, `data.decisionVersion` of the `kyc.review.decided` event)."
          },
          "decidedAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the decision was written."
          },
          "replayed": {
            "type": "boolean",
            "description": "`true` when this is the stored answer of an earlier request with the same `Idempotency-Key` (the response also carries `Idempotent-Replayed: true`); `false` when this request wrote the decision."
          }
        }
      },
      "KycTenantReview": {
        "type": "object",
        "description": "What a reviewer did with a verification. No reviewer identity, no reviewer note and no reviewer reason codes are returned.",
        "required": [
          "state",
          "decision",
          "version",
          "decidedAt"
        ],
        "properties": {
          "state": {
            "$ref": "#/components/schemas/KycReviewState"
          },
          "decision": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycReviewDecision"
              },
              {
                "type": "null"
              }
            ],
            "description": "The reviewer's decision, or `null` while the state is `in_review`."
          },
          "version": {
            "type": "integer",
            "minimum": 1,
            "description": "Number of review writes for this verification, starting at 1 and increasing by one with every write (an escalation counts)."
          },
          "decidedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Unix ms of the last review write that carried a decision, or `null` while there is none."
          }
        }
      },
      "KycTenantVerificationOutcome": {
        "type": "object",
        "description": "The reported outcome of a verification and who produced it. Present on the status read once the session has a `verificationId`.",
        "required": [
          "status",
          "source"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/KycVerificationStatus"
          },
          "source": {
            "$ref": "#/components/schemas/KycVerificationStatusSource"
          }
        }
      },
      "KycTenantStageResult": {
        "type": "object",
        "description": "One analysis stage's tenant-facing result. Stages that carry extra advisory detail (`speech_profile`, `age_gender`, `scene_inventory`, `document_type_id`) add typed members next to these three; model and engine provenance is never exposed.",
        "required": [
          "status",
          "score",
          "reasonCodes"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pass",
              "review",
              "fail",
              "skipped",
              "pending"
            ]
          },
          "score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Range 0 to 1, or `null` when the stage produced no score."
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "KycTenantStages": {
        "type": "object",
        "description": "Per-stage results keyed by stage name. A stage that was not requested or has not run reads `status: skipped` or `pending` with a reason code, never a fabricated pass.",
        "properties": {
          "scene_inventory": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "document_type_id": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "ocr_back": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "mrz_crosscheck": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "selfie_quality": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "document_near_face": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "active_liveness": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "speech_profile": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "speech_analysis": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          },
          "age_gender": {
            "$ref": "#/components/schemas/KycTenantStageResult"
          }
        }
      },
      "KycScorecardEntry": {
        "type": "object",
        "required": [
          "status",
          "score",
          "checks",
          "reasonCodes"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pass",
              "review",
              "fail",
              "skipped",
              "pending"
            ]
          },
          "score": {
            "type": "number",
            "description": "Range 0 to 1; `0` for `pending` and `skipped` (no evaluation occurred)."
          },
          "checks": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name",
                "pass"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "pass": {
                  "type": "boolean"
                },
                "score": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "KycTenantScorecard": {
        "type": "object",
        "description": "The numeric per-step summary. A non-authoritative roll-up of what the pipeline already decided: never an approval by itself. `overall` is the roll-up of the other six.",
        "properties": {
          "document_front": {
            "$ref": "#/components/schemas/KycScorecardEntry"
          },
          "document_back": {
            "$ref": "#/components/schemas/KycScorecardEntry"
          },
          "selfie": {
            "$ref": "#/components/schemas/KycScorecardEntry"
          },
          "liveness": {
            "$ref": "#/components/schemas/KycScorecardEntry"
          },
          "voice": {
            "$ref": "#/components/schemas/KycScorecardEntry"
          },
          "cross_validation": {
            "$ref": "#/components/schemas/KycScorecardEntry"
          },
          "overall": {
            "$ref": "#/components/schemas/KycScorecardEntry"
          }
        }
      },
      "KycTenantSessionEnvelope": {
        "type": "object",
        "description": "The wire envelope of `GET /v1/kyc/sessions/{sessionId}`. The session fields are always nested under `session`, never flattened onto the envelope.",
        "required": [
          "ok",
          "session",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "session": {
            "$ref": "#/components/schemas/KycTenantSessionStatus"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycTenantSessionStatus": {
        "type": "object",
        "description": "The tenant-backend view of a hosted session. Every timestamp is Unix time in **milliseconds**. Not exposed on this route: the session URL or token, evidence bytes or URLs, the stored `hostedOptions`, `cancelledAt` and `cancelReason` (those are delivered only in the `kyc.session.cancelled` webhook), model provenance, and the address a person may have given to receive their Noxtica Verification ID.",
        "required": [
          "id",
          "status",
          "idType",
          "subjectId",
          "captureStep",
          "createdAt",
          "openedAt",
          "expiresAt",
          "completedAt",
          "verificationId",
          "reasonCodes",
          "purpose",
          "returnUrl",
          "selectedIdType",
          "selectedIssuingCountry",
          "consentVersion",
          "consentAcceptedAt",
          "speechProfilingConsentAcceptedAt",
          "ageGenderConsentAcceptedAt",
          "evidence",
          "stages",
          "scorecard",
          "domainId"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The session id returned by the mint call."
          },
          "domainId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The domain the invitation was created for (see the `domainId`, `siteKey` and `Origin` rules of the mint call), or `null` when it uses your organisation default. Fixed at mint."
          },
          "status": {
            "$ref": "#/components/schemas/KycSessionStatus"
          },
          "idType": {
            "type": "string",
            "description": "The document type hint given at mint (default `national_id`)."
          },
          "subjectId": {
            "type": "string",
            "description": "Your own `subjectRef` from the mint call, returned unchanged. It is never sent in webhooks."
          },
          "captureStep": {
            "$ref": "#/components/schemas/KycSessionCaptureStep"
          },
          "createdAt": {
            "type": "integer",
            "format": "int64"
          },
          "openedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "When the customer's first recorded action on the hosted page happened (the session left `pending`); `null` until then."
          },
          "expiresAt": {
            "type": "integer",
            "format": "int64",
            "description": "The effective deadline. At mint it is `now + expiresInSeconds` (24 hours by default, 5 minutes to 7 days); once the customer opens the session it is tightened to `min(expiresAt, openedAt + 1800000)` (the 30-minute capture window). It only moves earlier and is never extended. A session in `review_pending` does not expire; a session past this instant that was never submitted reads `status: expired`."
          },
          "completedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "When the decision was made (pipeline outcome, or the reviewer's decision for a session that was `review_pending`); `null` before."
          },
          "verificationId": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 128,
            "description": "The verification the submit ran (also `data.verificationId` of `kyc.verification.completed`); `null` before the customer submits. For a hosted session it is `hosted:` followed by 64 lowercase hexadecimal characters (71 characters in all); see `KycVerificationId` for the formats and the 128-character limit."
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Closed reason-code catalog. The reasons of the automated verification; a review does not rewrite them, so `provider_manual_review` stays after a reviewer approved the case. Read `verification` and `review` for the current outcome."
          },
          "purpose": {
            "type": [
              "string",
              "null"
            ],
            "description": "The mint-time `purpose`."
          },
          "returnUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The mint-time `returnUrl`."
          },
          "selectedIdType": {
            "type": [
              "string",
              "null"
            ],
            "description": "The document type the customer chose on the page: `passport`, `national_id`, `driving_license` or `residence_permit`; `null` until the customer chooses."
          },
          "selectedIssuingCountry": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[A-Z]{2}$",
            "description": "The issuing country the customer chose, as an ISO 3166-1 alpha-2 code (one of the `CountryCode` values); `null` until the customer chooses."
          },
          "consentVersion": {
            "type": [
              "string",
              "null"
            ],
            "description": "Version of the privacy notice the customer acknowledged."
          },
          "consentAcceptedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "speechProfilingConsentAcceptedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "ageGenderConsentAcceptedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "evidence": {
            "$ref": "#/components/schemas/KycSessionEvidenceRefs"
          },
          "decision": {
            "$ref": "#/components/schemas/KycSessionDecision"
          },
          "verification": {
            "allOf": [
              {
                "$ref": "#/components/schemas/KycTenantVerificationOutcome"
              }
            ],
            "description": "The reported outcome of the verification. Present only once the session has a `verificationId`; absent before that."
          },
          "review": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycTenantReview"
              },
              {
                "type": "null"
              }
            ],
            "description": "The reviewer's work on the verification: `null` while no reviewer has acted. Present (as `null` or an object) exactly when `verification` is; absent before the session has a `verificationId`."
          },
          "crossValidation": {
            "type": "object",
            "description": "The OCR / MRZ / barcode cross-validation advisory block, when the pipeline attached one. Absent for sessions without back-of-document evidence. Verdict-neutral."
          },
          "stages": {
            "$ref": "#/components/schemas/KycTenantStages"
          },
          "scorecard": {
            "$ref": "#/components/schemas/KycTenantScorecard"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present only when hosted email verification is enabled for your organisation AND (the session's effective `emailVerification` mode is not `off` OR an address is stored). The normalised address the customer entered, or `null`. It is the customer's own typed value until `emailVerified` is `true`."
          },
          "emailVerified": {
            "type": "boolean",
            "description": "Present under the same condition as `email`. `true` only after the customer clicked the confirmation link for that exact address."
          },
          "emailVerifiedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Present under the same condition as `email`. When the confirmation link was clicked, or `null`."
          },
          "emailVerification": {
            "$ref": "#/components/schemas/KycEmailVerificationMode"
          },
          "nvid": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present only when the Noxtica Verification ID is enabled for your organisation. The session's NVID (`NVID-XXXX-XXXX-XXXX-XXXX`), or `null` when none was issued (an NVID is issued when the submit ends `completed` or `review_pending`). The public status of an NVID is read from `GET /v1/kyc/verification-status`, which only the hosted verification site's browser origin may call; a tenant backend takes the NVID from this field or from `data.nvid` of `kyc.session.completed`."
          }
        }
      },
      "KycHostedSessionEnvelope": {
        "type": "object",
        "description": "The wire envelope of the hosted page's snapshot routes (`GET /v1/kyc/session-view` and `POST /v1/kyc/session-progress`). The session fields are never flattened onto the envelope. The tenant-backend status read has its own shape, `KycTenantSessionEnvelope`.",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "session": {
            "$ref": "#/components/schemas/KycHostedSessionSnapshot"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycHostedSessionSnapshot": {
        "type": "object",
        "description": "The customer-facing snapshot the hosted verification page renders from. Called by the page itself, never by a tenant backend; it is not a stable integration surface. All timestamps are Unix milliseconds. Optional members appear only when the matching capability is active for the session.",
        "required": [
          "status",
          "idType",
          "captureStep",
          "expiresAt",
          "openedAt",
          "steps",
          "configuration",
          "selection",
          "consent",
          "evidence",
          "resume",
          "failureBudget",
          "serverNow",
          "sessionExpiresAt"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/KycSessionStatus"
          },
          "idType": {
            "type": "string"
          },
          "captureStep": {
            "$ref": "#/components/schemas/KycSessionCaptureStep"
          },
          "expiresAt": {
            "type": "integer",
            "format": "int64",
            "description": "Effective session deadline: the original mint-time deadline while the link is unopened, tightened in place to `openedAt + 1800000` (the 30-minute capture window) once opened, whichever is sooner."
          },
          "openedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "When the customer's first hosted-flow action landed (the `pending` to `opened` transition); `null` until then."
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KycSessionCaptureStep"
            },
            "description": "The full step vocabulary, in order."
          },
          "configuration": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycHostedSessionConfiguration"
              },
              {
                "type": "null"
              }
            ],
            "description": "The hosted setup that applies to this session (the configuration of the domain it was created for, with every field the domain leaves unset taken from your organisation default; see `GET /v1/kyc/config`), filtered by this session's options; `null` when no hosted configuration applies (the page shows \"Verification isn't ready yet\")."
          },
          "selection": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "idType",
                  "issuingCountry"
                ],
                "properties": {
                  "idType": {
                    "type": "string"
                  },
                  "issuingCountry": {
                    "type": "string",
                    "pattern": "^[A-Z]{2}$",
                    "description": "The issuing country the customer chose, as an ISO 3166-1 alpha-2 code (one of the `CountryCode` values)."
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "The customer's document choice, once made."
          },
          "consent": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycHostedSessionConsent"
              },
              {
                "type": "null"
              }
            ],
            "description": "The privacy-notice receipt; immutable once set."
          },
          "biometricConsent": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycHostedSessionConsent"
              },
              {
                "type": "null"
              }
            ],
            "description": "The biometric-processing consent receipt. Present only when biometric consent is enabled for the platform."
          },
          "speechProfilingConsent": {
            "type": "object",
            "required": [
              "acceptedAt"
            ],
            "properties": {
              "acceptedAt": {
                "type": "integer",
                "format": "int64"
              }
            }
          },
          "ageGenderConsent": {
            "type": "object",
            "required": [
              "acceptedAt"
            ],
            "properties": {
              "acceptedAt": {
                "type": "integer",
                "format": "int64"
              }
            }
          },
          "evidence": {
            "$ref": "#/components/schemas/KycSessionEvidenceRefs"
          },
          "resume": {
            "$ref": "#/components/schemas/KycHostedSessionResume"
          },
          "decision": {
            "$ref": "#/components/schemas/KycSessionDecision"
          },
          "processing": {
            "type": "object",
            "description": "Coarse \"what is happening now\" for the status page; absent while the customer is still capturing.",
            "required": [
              "stage",
              "updatedAt"
            ],
            "properties": {
              "stage": {
                "type": "string",
                "enum": [
                  "uploading",
                  "queued",
                  "evaluating",
                  "review_pending",
                  "decided"
                ]
              },
              "updatedAt": {
                "type": "integer",
                "format": "int64"
              }
            }
          },
          "evidenceQuality": {
            "type": "object",
            "description": "Per-role structural quality receipt; absent until a role's evidence has been inspected."
          },
          "livenessSteps": {
            "type": "array",
            "description": "The configured liveness challenge with per-step completion.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "kind",
                "completed"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "kind": {
                  "type": "string"
                },
                "completed": {
                  "type": "boolean"
                }
              }
            }
          },
          "voiceChallenge": {
            "type": "object",
            "description": "The anti-replay digit phrase; present only when the tenant requires the voice step.",
            "required": [
              "phrase"
            ],
            "properties": {
              "phrase": {
                "type": "string"
              }
            }
          },
          "failureBudget": {
            "type": "object",
            "description": "Attempt counters per retryable step (`front`, `back`, `selfie`, `liveness`, `voice`, `upload`).",
            "additionalProperties": {
              "type": "object",
              "required": [
                "attempts",
                "max"
              ],
              "properties": {
                "attempts": {
                  "type": "integer"
                },
                "max": {
                  "type": "integer"
                }
              }
            }
          },
          "deviceChoice": {
            "type": "string",
            "enum": [
              "desktop",
              "phone"
            ],
            "description": "A previously recorded cross-device continuation choice; absent when none was recorded."
          },
          "serverNow": {
            "type": "integer",
            "format": "int64",
            "description": "The server clock when the snapshot was built; the page computes remaining time against it, not against its own clock."
          },
          "sessionExpiresAt": {
            "type": "integer",
            "format": "int64",
            "description": "The same deadline as `expiresAt`, named for the two-timer UI."
          },
          "currentStep": {
            "type": [
              "string",
              "null"
            ],
            "description": "The step the customer most recently entered. Present only when per-step timers are enabled for the platform."
          },
          "stepStartedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "stepExpiresAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "`min(stepStartedAt + 10 minutes, sessionExpiresAt)`; `null` for an untimed step."
          },
          "capabilities": {
            "$ref": "#/components/schemas/KycHostedSessionCapabilities"
          },
          "emailVerification": {
            "$ref": "#/components/schemas/KycEmailVerificationState"
          },
          "verificationIdentifier": {
            "description": "Present only while the NVID is enabled for the platform; `null` when the session has none.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycVerificationIdentifier"
              },
              {
                "type": "null"
              }
            ]
          },
          "evidenceUpload": {
            "type": "object",
            "description": "Capability marker for step-wise evidence upload; present only when that capability is enabled for the platform."
          },
          "evidenceReceipts": {
            "type": "object",
            "description": "Server-authoritative per-step evidence receipts; present with `evidenceUpload`."
          },
          "scorecard": {
            "type": "object",
            "description": "The customer-masked per-step scorecard (`GET /v1/kyc/session-view` only): a status per step and nothing else; no score, check or reason code reaches a browser. The full numeric version is `session.scorecard` on the tenant status read.",
            "properties": {
              "document_front": {
                "$ref": "#/components/schemas/KycCustomerScorecardEntry"
              },
              "document_back": {
                "$ref": "#/components/schemas/KycCustomerScorecardEntry"
              },
              "selfie": {
                "$ref": "#/components/schemas/KycCustomerScorecardEntry"
              },
              "liveness": {
                "$ref": "#/components/schemas/KycCustomerScorecardEntry"
              },
              "voice": {
                "$ref": "#/components/schemas/KycCustomerScorecardEntry"
              },
              "cross_validation": {
                "$ref": "#/components/schemas/KycCustomerScorecardEntry"
              },
              "overall": {
                "$ref": "#/components/schemas/KycCustomerScorecardEntry"
              }
            }
          }
        }
      },
      "KycCustomerScorecardEntry": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pass",
              "needs_review",
              "failed",
              "skipped",
              "pending"
            ]
          }
        }
      },
      "KycHostedSessionConsent": {
        "type": "object",
        "required": [
          "version",
          "acceptedAt"
        ],
        "properties": {
          "version": {
            "type": "string"
          },
          "acceptedAt": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "KycHostedSessionConfiguration": {
        "type": "object",
        "required": [
          "businessName",
          "purpose",
          "privacyUrl",
          "supportUrl",
          "returnUrl",
          "privacyVersion",
          "documents",
          "requireSelfie",
          "requireVoice",
          "livenessMode",
          "options"
        ],
        "properties": {
          "businessName": {
            "type": "string"
          },
          "purpose": {
            "type": "string"
          },
          "privacyUrl": {
            "type": "string"
          },
          "supportUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "returnUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The mint-time `returnUrl`. The page renders it only as a \"Return to <business>\" link (completed outcome, or the screen shown when a step cannot continue); nothing is appended and nothing redirects automatically."
          },
          "privacyVersion": {
            "type": "string"
          },
          "biometricConsentVersion": {
            "type": "string",
            "description": "Present only when biometric consent is enabled for the platform."
          },
          "documents": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "idType",
                "issuingCountries",
                "requireBack"
              ],
              "properties": {
                "idType": {
                  "type": "string",
                  "enum": [
                    "national_id",
                    "passport",
                    "driving_license",
                    "residence_permit"
                  ]
                },
                "issuingCountries": {
                  "type": "array",
                  "maxItems": 249,
                  "items": {
                    "type": "string",
                    "pattern": "^[A-Z]{2}$"
                  },
                  "description": "The issuing countries the customer can pick for this document type, as ISO 3166-1 alpha-2 codes (`CountryCode` values), after the invitation's `hostedOptions.allowedIssuingCountries` narrowing."
                },
                "requireBack": {
                  "type": "boolean"
                },
                "requiredIdentityFields": {
                  "type": "array",
                  "uniqueItems": true,
                  "items": {
                    "type": "string",
                    "enum": [
                      "surname",
                      "givenNames",
                      "dateOfBirth",
                      "personalNumber",
                      "documentNumber"
                    ]
                  },
                  "description": "Present only when your configuration sets it. The identity fields that must be read from this document type for the extraction to count as complete (`extraction` on the identity read); an empty list requires none. Absent means the default (see `GET /v1/kyc/config`)."
                }
              }
            }
          },
          "requireSelfie": {
            "type": "boolean"
          },
          "requireVoice": {
            "type": "boolean"
          },
          "livenessMode": {
            "type": "string",
            "enum": [
              "passive",
              "assisted_review",
              "full_challenge"
            ]
          },
          "options": {
            "$ref": "#/components/schemas/KycHostedSessionOptions"
          }
        }
      },
      "KycHostedConfigField": {
        "type": "string",
        "enum": [
          "businessName",
          "defaultPurpose",
          "privacyUrl",
          "supportUrl",
          "privacyVersion",
          "documents",
          "requireSelfie",
          "requireVoice",
          "allowDeviceUpload",
          "livenessMode",
          "allowedReturnUrlHosts",
          "allowedEmbedOrigins",
          "emailVerification"
        ],
        "description": "A field of the hosted configuration that a domain can override, as it is named in `sources` and `overriddenFields`. `defaultPurpose` is the field that configuration objects call `purpose`."
      },
      "KycHostedConfigSource": {
        "type": "string",
        "enum": [
          "domain",
          "tenant",
          "platform"
        ],
        "description": "The layer an effective value comes from. `domain` — the domain's own override. `tenant` — your organisation default. `platform` — the built-in default Noxtica applies to a field neither the domain nor your organisation sets."
      },
      "KycHostedConfigSources": {
        "type": "object",
        "description": "For every overridable field, the layer its effective value comes from. All thirteen members are always present.",
        "required": [
          "businessName",
          "defaultPurpose",
          "privacyUrl",
          "supportUrl",
          "privacyVersion",
          "documents",
          "requireSelfie",
          "requireVoice",
          "allowDeviceUpload",
          "livenessMode",
          "allowedReturnUrlHosts",
          "allowedEmbedOrigins",
          "emailVerification"
        ],
        "properties": {
          "businessName": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "defaultPurpose": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "privacyUrl": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "supportUrl": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "privacyVersion": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "documents": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "requireSelfie": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "requireVoice": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "allowDeviceUpload": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "livenessMode": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "allowedReturnUrlHosts": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "allowedEmbedOrigins": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          },
          "emailVerification": {
            "$ref": "#/components/schemas/KycHostedConfigSource"
          }
        }
      },
      "KycHostedConfigDocument": {
        "type": "object",
        "description": "One document type the hosted flow accepts, with the issuing countries it accepts it from.",
        "required": [
          "idType",
          "issuingCountries",
          "requireBack"
        ],
        "properties": {
          "idType": {
            "type": "string",
            "enum": [
              "national_id",
              "passport",
              "driving_license",
              "residence_permit"
            ]
          },
          "issuingCountries": {
            "type": "array",
            "maxItems": 249,
            "items": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            },
            "description": "Issuing countries the customer can pick for this document type, as ISO 3166-1 alpha-2 codes (`CountryCode` values; any of the 249 assigned codes)."
          },
          "requireBack": {
            "type": "boolean",
            "description": "Whether the back of the document is captured."
          },
          "requiredIdentityFields": {
            "type": "array",
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "surname",
                "givenNames",
                "dateOfBirth",
                "personalNumber",
                "documentNumber"
              ]
            },
            "description": "The identity fields that must be read from this document type for the extraction to count as complete (see `extraction` on `GET /v1/kyc/verifications/{verificationId}/identity`). Present only when set. Absent means the default: surname, given names, date of birth and document number, plus the personal number on a Turkish national ID card or driving licence, where a read T.C. Kimlik No that passes its check digits makes the document number optional. A non-empty list replaces that default exactly; an empty list requires none. Edited in the Backoffice hosted verification settings, not through this API."
          }
        }
      },
      "KycHostedConfig": {
        "type": "object",
        "description": "One hosted verification configuration: your organisation default, or the effective configuration of one domain (see `GET /v1/kyc/config` for how a domain's own values and your organisation default combine). Timestamps are Unix time in milliseconds.",
        "required": [
          "businessName",
          "purpose",
          "privacyUrl",
          "supportUrl",
          "privacyVersion",
          "documents",
          "requireSelfie",
          "requireVoice",
          "allowDeviceUpload",
          "livenessMode",
          "emailVerification",
          "allowedReturnUrlHosts",
          "allowedEmbedOrigins",
          "updatedAt"
        ],
        "properties": {
          "businessName": {
            "type": "string",
            "description": "The business name the hosted page shows to the customer."
          },
          "purpose": {
            "type": "string",
            "description": "The default purpose shown on the consent screen when the mint call sends none."
          },
          "privacyUrl": {
            "type": "string",
            "format": "uri",
            "description": "The privacy page the hosted page links to (`https`)."
          },
          "supportUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "The support page the hosted page links to (`https`), or `null` when there is none."
          },
          "privacyVersion": {
            "type": "string",
            "description": "The version label of the privacy notice at `privacyUrl`; the customer's consent receipt records it."
          },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KycHostedConfigDocument"
            },
            "description": "The document types and issuing countries the customer can choose from."
          },
          "requireSelfie": {
            "type": "boolean"
          },
          "requireVoice": {
            "type": "boolean"
          },
          "allowDeviceUpload": {
            "type": "boolean",
            "description": "Whether the customer may upload a file from their device instead of using the camera."
          },
          "livenessMode": {
            "type": "string",
            "enum": [
              "passive",
              "assisted_review",
              "full_challenge"
            ]
          },
          "emailVerification": {
            "$ref": "#/components/schemas/KycEmailVerificationMode"
          },
          "allowedReturnUrlHosts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Host names a `returnUrl` may point to at mint."
          },
          "allowedEmbedOrigins": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Origins (scheme, host and optional port, for example `https://shop.example.com`) that may embed the hosted page in a frame. An empty list means it cannot be embedded."
          },
          "updatedAt": {
            "type": "integer",
            "format": "int64",
            "description": "When the configuration last changed. For the effective configuration of a domain, the later of the domain's change and your organisation's change."
          }
        }
      },
      "KycHostedConfigDomainRef": {
        "type": "object",
        "description": "A domain of your organisation.",
        "required": [
          "id",
          "origin"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The domain id (`d_...`)."
          },
          "origin": {
            "type": "string",
            "description": "The domain's origin, for example `https://shop.example.com`."
          }
        }
      },
      "KycHostedConfigDomainSummary": {
        "type": "object",
        "description": "One active domain that carries an override, with the hosted configuration fields it overrides.",
        "required": [
          "domainId",
          "origin",
          "overriddenFields"
        ],
        "properties": {
          "domainId": {
            "type": "string"
          },
          "origin": {
            "type": "string"
          },
          "overriddenFields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KycHostedConfigField"
            },
            "description": "The fields this domain sets itself; every other field inherits from your organisation default. Never empty."
          }
        }
      },
      "KycHostedConfigResponse": {
        "type": "object",
        "description": "The wire body of `GET /v1/kyc/config`.",
        "required": [
          "ok",
          "domainId",
          "domain",
          "effective",
          "sources",
          "tenantDefault"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "domainId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The requested domain, or `null` when the call named none."
          },
          "domain": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycHostedConfigDomainRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The requested domain, or `null` when the call named none."
          },
          "effective": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycHostedConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "The configuration invitations use. `null` when a required field (`businessName`, `purpose`, `privacyUrl`, `privacyVersion`, `documents`) is set by no layer; the hosted page then shows \"Verification isn't ready yet\"."
          },
          "sources": {
            "$ref": "#/components/schemas/KycHostedConfigSources"
          },
          "tenantDefault": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycHostedConfig"
              },
              {
                "type": "null"
              }
            ],
            "description": "Your organisation default, or `null` when you have not configured one."
          },
          "domains": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KycHostedConfigDomainSummary"
            },
            "description": "Present only when the call named no `domainId`. The active domains that carry an override."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycGatedOrDomainNotFoundError": {
        "type": "object",
        "description": "The body of a `404` on a route that accepts a `domainId` or `siteKey`. `V1 endpoint not found` is the byte-identical gated answer (see `GatedNotFound`; it carries `path`). `domain_not_found` means the `domainId` or `siteKey` is unknown, names a disabled domain, or belongs to another organisation; the three causes get the same answer.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Either `V1 endpoint not found` or `domain_not_found`."
          },
          "path": {
            "type": "string",
            "description": "Present only on the `V1 endpoint not found` answer."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycHostedSessionResume": {
        "type": "object",
        "description": "Whether the customer link this snapshot answers for is still usable for further capture.",
        "required": [
          "eligible"
        ],
        "properties": {
          "eligible": {
            "type": "boolean"
          },
          "reason": {
            "type": "string",
            "enum": [
              "expired",
              "completed",
              "consumed",
              "cancelled"
            ],
            "description": "Present when `eligible` is `false`. `cancelled` means the session is in the terminal `cancelled_by_user` status (defensive — every customer route already answers `410 session_cancelled` for such a session, so a snapshot of a cancelled row is not normally served to the hosted page)."
          }
        }
      },
      "KycHostedSessionCapabilities": {
        "type": "object",
        "description": "Server capabilities the hosted verification page may rely on. The object lists only the capabilities that are active for this session and is omitted entirely when none is. `cancel` is advertised when the collector serves `POST /v1/kyc/session-cancel` AND has cancel armed; `emailVerification` is advertised when hosted email verification is enabled for the platform and the session's effective mode is not `off`. The hosted page treats \"field present\" as the only capability signal (it never probes a route, since a probe could itself create state) and, when a capability is absent, falls back to the behaviour of an older collector (for cancel: a client-only end screen with no network call; for email verification: no email step).",
        "properties": {
          "cancel": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "The collector accepts `POST /v1/kyc/session-cancel` for this session."
          },
          "emailVerification": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "The hosted flow includes the email-verification step for this session."
          }
        }
      },
      "KycEmailVerificationMode": {
        "type": "string",
        "enum": [
          "off",
          "optional",
          "required"
        ],
        "description": "Hosted email-verification mode. `off` — no email step. `optional` — the customer is offered the step and may skip it. `required` — the customer must verify an address before the session can be submitted (`409 email_verification_required` until then). The mode is resolved once at mint and fixed for the session. On the tenant-facing `GET /v1/kyc/sessions/{sessionId}` this key is the effective mode string; the hosted page's own session routes carry the customer-facing state object `KycEmailVerificationState` under the same key."
      },
      "KycEmailVerificationState": {
        "type": "object",
        "description": "The customer-facing email-verification state returned by the hosted page's email routes and embedded in the snapshot as `emailVerification`. The address is always masked (`j***@example.com`).",
        "required": [
          "mode",
          "email",
          "verified",
          "verifiedAt",
          "expiresAt",
          "resendAvailableAt",
          "sendsRemaining"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "optional",
              "required"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The masked address; `null` before one was entered."
          },
          "verified": {
            "type": "boolean"
          },
          "verifiedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "expiresAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Deadline of the live confirmation link (30 minutes or the session end, whichever is sooner); `null` when none is live."
          },
          "resendAvailableAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Earliest next send while the 60 second cooldown runs; `null` when a resend is allowed now or the cap is exhausted."
          },
          "sendsRemaining": {
            "type": "integer",
            "description": "Sends left of the 5 allowed per session."
          }
        }
      },
      "KycEmailVerificationStateEnvelope": {
        "type": "object",
        "description": "The body of the hosted page's email routes (`POST /v1/kyc/session-email/send`, `GET /v1/kyc/session-email/status`).",
        "required": [
          "emailVerification",
          "requestId"
        ],
        "properties": {
          "emailVerification": {
            "$ref": "#/components/schemas/KycEmailVerificationState"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycVerificationIdStatus": {
        "type": "string",
        "enum": [
          "verified",
          "pending",
          "revoked",
          "expired"
        ],
        "description": "Public status of a Noxtica Verification ID. `revoked` wins over everything; `pending` = the verification awaits a review decision; `expired` = 365 days after completion; otherwise `verified`."
      },
      "KycVerificationIdentifier": {
        "type": "object",
        "description": "The session's Noxtica Verification ID as shown on the hosted page's final screen (`session.verificationIdentifier` on `GET /v1/kyc/session-view`). Customer-facing; never carries an address.",
        "required": [
          "nvid",
          "status",
          "verifiedAt",
          "expiresAt",
          "emailOnFile"
        ],
        "properties": {
          "nvid": {
            "type": "string",
            "description": "Canonical NVID, NVID-XXXX-XXXX-XXXX-XXXX."
          },
          "status": {
            "$ref": "#/components/schemas/KycVerificationIdStatus"
          },
          "verifiedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Unix ms of completion; `null` while pending."
          },
          "expiresAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Unix ms, completion + 365 days; `null` while pending."
          },
          "emailOnFile": {
            "type": "boolean",
            "description": "Whether the person gave an address to receive the NVID. The address itself is never returned."
          }
        }
      },
      "KycVerificationIdEmailRequest": {
        "type": "object",
        "required": [
          "email",
          "locale",
          "consent"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "The address to send the NVID to (normalised server-side).",
            "maxLength": 254
          },
          "locale": {
            "type": "string",
            "description": "One of the 36 supported UI locales; the email is rendered in it (unsupported falls back to `en`)."
          },
          "consent": {
            "type": "object",
            "required": [
              "version",
              "accepted"
            ],
            "properties": {
              "version": {
                "type": "string",
                "enum": [
                  "nvid-email-v1"
                ]
              },
              "accepted": {
                "type": "boolean",
                "enum": [
                  true
                ]
              }
            }
          }
        }
      },
      "KycVerificationIdStatusResponse": {
        "type": "object",
        "required": [
          "ok",
          "verification"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "verification": {
            "type": "object",
            "required": [
              "id",
              "status",
              "verifiedAt",
              "expiresAt",
              "revokedAt",
              "requestedBy"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "Canonical NVID."
              },
              "status": {
                "$ref": "#/components/schemas/KycVerificationIdStatus"
              },
              "verifiedAt": {
                "type": [
                  "integer",
                  "null"
                ],
                "format": "int64",
                "description": "Unix ms of completion; `null` while pending."
              },
              "expiresAt": {
                "type": [
                  "integer",
                  "null"
                ],
                "format": "int64",
                "description": "Unix ms, completion + 365 days; `null` while pending."
              },
              "revokedAt": {
                "type": [
                  "integer",
                  "null"
                ],
                "format": "int64",
                "description": "Unix ms of the revocation; `null` unless revoked."
              },
              "requestedBy": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The requesting business's display name; `null` when none is configured."
              }
            }
          }
        }
      },
      "KycHostedSessionOptions": {
        "type": "object",
        "description": "Invitation-scoped hosted-flow switches, frozen into the session at mint. When you send `hostedOptions` it must be the complete object (every required key below, no merging with defaults); omit it to use the defaults (all four document types, `allowDeviceUpload` false, `documentBack` `auto`, `documentNearFace` false, `activeLiveness` true, `speechAnalysis` false, `ageGenderEstimation` false, `emailVerification` `off`). Inside `steps`, `ageGenderEstimation` and `emailVerification` are optional. `allowedIssuingCountries` is optional too.",
        "required": [
          "allowedDocumentTypes",
          "allowDeviceUpload",
          "steps"
        ],
        "properties": {
          "allowedDocumentTypes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 4,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "national_id",
                "passport",
                "driving_license",
                "residence_permit"
              ]
            }
          },
          "allowedIssuingCountries": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CountryCodeList"
              }
            ],
            "description": "Optional. Narrows the invitation to these issuing countries: each document type your hosted configuration accepts keeps only the issuing countries that are also listed, a document type left with none is dropped, and a mint where no document type remains is refused with `400 invalid_hosted_options` (`field` `allowedIssuingCountries`, `reason` `no_matching_document_country`). Absent means no narrowing. Any ISO 3166-1 alpha-2 code is accepted, including codes your configuration does not offer."
          },
          "allowDeviceUpload": {
            "type": "boolean"
          },
          "steps": {
            "type": "object",
            "required": [
              "documentBack",
              "documentNearFace",
              "activeLiveness",
              "speechAnalysis"
            ],
            "properties": {
              "documentBack": {
                "type": "string",
                "enum": [
                  "auto"
                ]
              },
              "documentNearFace": {
                "type": "boolean"
              },
              "activeLiveness": {
                "type": "boolean"
              },
              "speechAnalysis": {
                "type": "boolean"
              },
              "ageGenderEstimation": {
                "type": "boolean",
                "description": "Optional; default `false`. Adds an advisory age and gender estimate from the selfie frames (biometric, special-category data); the customer gives a separate consent on the page. Setting `true` is refused with `403 age_gender_estimation_not_enabled` (`reasonCode` `age_gender_platform_disabled` or `age_gender_tenant_acknowledgement_missing`) unless the platform has enabled the feature AND your organisation has recorded the matching legal-basis acknowledgement in the Backoffice. It never fails a verification."
              },
              "emailVerification": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/KycEmailVerificationMode"
                  }
                ],
                "description": "Optional (added 2026-10-01; default `off` when the key is absent). When omitted from a mint request your organisation's default applies; an explicit value wins in both directions. An explicit `optional` or `required` is refused with `400 email_verification_unavailable` until the platform has enabled hosted email verification. The mint response echoes the effective mode."
              }
            }
          }
        }
      },
      "KycSessionLifecycleWebhookData": {
        "type": "object",
        "description": "The `data` member of every `kyc.session.created`, `.opened`, `.verifying`, `.review_pending`, `.unverified`, `.error` and `.expired` event (`schemaVersion` `\"1\"`). A closed, non-personal field set: no subject reference, digest, e-mail, name or evidence. Deduplicate by `deliveryId`.",
        "required": [
          "schemaVersion",
          "sessionId",
          "status",
          "previousStatus",
          "verificationId",
          "reasonCodes",
          "domainId",
          "occurredAt",
          "deliveryId"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "description": "The id the mint call returned."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "opened",
              "verifying",
              "review_pending",
              "unverified",
              "error",
              "expired"
            ],
            "description": "The status the session entered; the event type names it."
          },
          "previousStatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "opened",
              "verifying",
              "review_pending",
              "unverified",
              "error",
              "expired",
              "completed",
              "cancelled_by_user",
              null
            ],
            "description": "The status the session left, or `null` when there is none (the `created` event, or a transition that was not recorded)."
          },
          "verificationId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The verification id once a verification ran, otherwise `null`. Format and limits of a verification id are in `KycVerificationId`."
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Closed reason codes for this status (empty for the non-terminal statuses). Never free text."
          },
          "domainId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The domain the invitation was created for (`d_...`), or `null` for your organisation default."
          },
          "occurredAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the session entered the status."
          },
          "deliveryId": {
            "type": "string",
            "description": "The delivery id, `<sessionId>:<event type>:<webhookId>`. Identical on every retry; equals `X-Noxtica-Delivery`."
          }
        }
      },
      "KycSessionLifecycleWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a `kyc.session.created`, `.opened`, `.verifying`, `.review_pending`, `.unverified`, `.error` or `.expired` delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id, `<sessionId>:<event type>:<webhookId>`; equals `X-Noxtica-Delivery` and `data.deliveryId`."
          },
          "type": {
            "type": "string",
            "enum": [
              "kyc.session.created",
              "kyc.session.opened",
              "kyc.session.verifying",
              "kyc.session.review_pending",
              "kyc.session.unverified",
              "kyc.session.error",
              "kyc.session.expired"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event occurred."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/KycSessionLifecycleWebhookData"
          }
        }
      },
      "KycReviewDecidedWebhookData": {
        "type": "object",
        "description": "The `data` member of `kyc.review.decided` (`schemaVersion` `\"1\"`). No reviewer identity, no API key and no note. Deduplicate by `deliveryId`.",
        "required": [
          "schemaVersion",
          "verificationId",
          "sessionId",
          "decision",
          "reviewState",
          "decisionVersion",
          "reasonCodes",
          "occurredAt",
          "deliveryId"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "verificationId": {
            "type": "string",
            "description": "The reviewed verification. Format and limits of a verification id are in `KycVerificationId`."
          },
          "sessionId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The hosted session the verification belongs to, or `null` for a verification created through the API."
          },
          "decision": {
            "type": "string",
            "enum": [
              "approve",
              "override"
            ]
          },
          "reviewState": {
            "type": "string",
            "description": "The review state after the decision: `approved` for `approve`, `overridden` for `override`."
          },
          "decisionVersion": {
            "type": "integer",
            "minimum": 0,
            "description": "The review version after the decision: 1 for the first review write of the verification, then one more with every write (an escalation counts). A changed decision is a new delivery."
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Closed reason codes the reviewer attached. Never free text."
          },
          "actor": {
            "type": "object",
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "panel",
                  "api"
                ],
                "description": "`panel`: a Backoffice reviewer wrote the decision. `api`: your own back office wrote it through `POST /v1/kyc/verifications/{verificationId}/review-decision`."
              }
            },
            "description": "Who wrote the decision. Absent on a delivery created before this member existed; read an absent `actor` as `panel`."
          },
          "reviewReasonCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "The closed reason code of an `api` decision (see `KycReviewDecisionReasonCode`); `null` for a `panel` decision. Absent exactly when `actor` is absent."
          },
          "occurredAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the decision was written."
          },
          "deliveryId": {
            "type": "string"
          }
        }
      },
      "KycReviewDecidedWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a `kyc.review.decided` delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id; equals `X-Noxtica-Delivery` and `data.deliveryId`."
          },
          "type": {
            "type": "string",
            "enum": [
              "kyc.review.decided"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event occurred."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/KycReviewDecidedWebhookData"
          }
        }
      },
      "KycSessionCompletedWebhookData": {
        "type": "object",
        "description": "The `data` member of `kyc.session.completed` (`schemaVersion` `\"1\"`). Delivery is at-least-once: deduplicate by `deliveryId`. A webhook is a notification, not authority; read `GET /v1/kyc/sessions/{sessionId}` for the outcome. There is no subject reference, digest, evidence id or scorecard. The event does not fire when a session lands in `review_pending`; it fires when a reviewer's decision moves it to `completed`. `email` and `emailVerified` (added 2026-10-01, additive) are present only when hosted email verification is enabled for the platform AND the session has a stored address; otherwise both keys are absent.",
        "required": [
          "schemaVersion",
          "sessionId",
          "status",
          "verificationId",
          "reasonCodes",
          "occurredAt",
          "deliveryId"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "description": "The id the mint call returned."
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "unverified",
              "error"
            ],
            "description": "The terminal session status. `expired` and `cancelled_by_user` sessions never emit this event."
          },
          "verificationId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The verification the submit ran (also on the status read); `null` when none ran. Format and limits are in `KycVerificationId`."
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "occurredAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the session reached this status (its `completedAt`)."
          },
          "deliveryId": {
            "type": "string"
          },
          "nvid": {
            "type": "string",
            "description": "The session's Noxtica Verification ID (`NVID-XXXX-XXXX-XXXX-XXXX`). Present only when the session has one (the feature is enabled for your organisation and the outcome was positive); otherwise absent. Never accompanied by the address the person may have given to receive it."
          },
          "email": {
            "type": "string",
            "description": "Present only when the session has a stored address (and hosted email verification is enabled for the platform). The normalised address the end customer entered in the hosted flow. Unverified unless `emailVerified` is `true`."
          },
          "emailVerified": {
            "type": "boolean",
            "description": "Present together with `email`. `true` only if the customer clicked the confirmation link for `email`."
          }
        }
      },
      "KycSessionCancelledWebhookData": {
        "type": "object",
        "description": "The `data` member of `kyc.session.cancelled` (`schemaVersion` `\"1\"`). Same delivery semantics as `KycSessionCompletedWebhookData`. `email` and `emailVerified` (added 2026-10-01, additive) are present only when hosted email verification is enabled for the platform AND the session has a stored address; otherwise both keys are absent.",
        "required": [
          "schemaVersion",
          "sessionId",
          "status",
          "cancelReason",
          "cancelledAt",
          "occurredAt",
          "deliveryId"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "cancelled_by_user"
            ]
          },
          "cancelReason": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycSessionCancelReason"
              },
              {
                "type": "null"
              }
            ]
          },
          "cancelledAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms."
          },
          "occurredAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms; equal to `cancelledAt`."
          },
          "deliveryId": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "description": "Present only when the session has a stored address (and hosted email verification is enabled for the platform). The normalised address the end customer entered before cancelling. Unverified unless `emailVerified` is `true`."
          },
          "emailVerified": {
            "type": "boolean",
            "description": "Present together with `email`. `true` only if the customer clicked the confirmation link for `email`."
          }
        }
      },
      "KycVerificationCompletedWebhookData": {
        "type": "object",
        "description": "The `data` member of `kyc.verification.completed` (`schemaVersion` `\"1\"`). A closed, non-personal field set: no subject id, digest or document value. Deduplicate by `deliveryId`; read the verification through the API before acting.",
        "required": [
          "schemaVersion",
          "verificationId",
          "verdict",
          "reasonCodes",
          "idType",
          "occurredAt",
          "deliveryId"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "verificationId": {
            "type": "string",
            "description": "The verification. Format and limits of a verification id are in `KycVerificationId`."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "verified",
              "unverified",
              "error"
            ]
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Closed reason-code catalog; may be empty."
          },
          "idType": {
            "type": "string",
            "description": "The document type asserted on the request, e.g. `passport`."
          },
          "occurredAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the verification reached its terminal state."
          },
          "deliveryId": {
            "type": "string"
          }
        }
      },
      "KycErasureCompletedWebhookData": {
        "type": "object",
        "description": "The `data` member of `kyc.erasure.completed` (`schemaVersion` `\"1\"`). Emitted only when the erasure request reaches `completed`; a `partial` erasure emits nothing. No subject identifier is carried.",
        "required": [
          "schemaVersion",
          "requestId",
          "outcome",
          "occurredAt",
          "deliveryId"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "requestId": {
            "type": "string",
            "description": "The erasure request's own id; not a subject id."
          },
          "outcome": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "occurredAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the erasure request reached its terminal state."
          },
          "deliveryId": {
            "type": "string"
          }
        }
      },
      "KycDocumentExpiryWarningWebhookData": {
        "type": "object",
        "description": "The `data` member of `kyc.document.expiry_warning` (`schemaVersion` `\"1\"`). No subject id and no document number: correlate by `verificationId`.",
        "required": [
          "schemaVersion",
          "verificationId",
          "alertType",
          "documentExpiresAt",
          "occurredAt",
          "deliveryId"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "verificationId": {
            "type": "string",
            "description": "The verification. Format and limits of a verification id are in `KycVerificationId`."
          },
          "alertType": {
            "type": "string",
            "enum": [
              "expiry_90d",
              "expiry_60d",
              "expiry_30d",
              "expiry_expired"
            ]
          },
          "documentExpiresAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the monitored document expires (or expired)."
          },
          "occurredAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the threshold crossing was detected."
          },
          "deliveryId": {
            "type": "string"
          }
        }
      },
      "KycReverificationDueWebhookData": {
        "type": "object",
        "description": "The `data` member of `kyc.reverification.due` (`schemaVersion` `\"1\"`). No subject id.",
        "required": [
          "schemaVersion",
          "verificationId",
          "dueAt",
          "occurredAt",
          "deliveryId"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "verificationId": {
            "type": "string",
            "description": "The verification. Format and limits of a verification id are in `KycVerificationId`."
          },
          "dueAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the re-verification became due."
          },
          "occurredAt": {
            "type": "integer",
            "format": "int64"
          },
          "deliveryId": {
            "type": "string"
          }
        }
      },
      "KycSessionCompletedWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a `kyc.session.completed` delivery. Keys are in the order shown.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id, `<sessionId>:<webhookId>`. Identical on every retry; equals `X-Noxtica-Delivery` and `data.deliveryId`."
          },
          "type": {
            "type": "string",
            "enum": [
              "kyc.session.completed"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event occurred."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/KycSessionCompletedWebhookData"
          }
        }
      },
      "KycSessionCancelledWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a `kyc.session.cancelled` delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id, `<sessionId>:<webhookId>`."
          },
          "type": {
            "type": "string",
            "enum": [
              "kyc.session.cancelled"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event occurred."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/KycSessionCancelledWebhookData"
          }
        }
      },
      "KycVerificationCompletedWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a `kyc.verification.completed` delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id, `<verificationId>:<webhookId>`."
          },
          "type": {
            "type": "string",
            "enum": [
              "kyc.verification.completed"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event occurred."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/KycVerificationCompletedWebhookData"
          }
        }
      },
      "KycErasureCompletedWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a `kyc.erasure.completed` delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id; equals `data.deliveryId`."
          },
          "type": {
            "type": "string",
            "enum": [
              "kyc.erasure.completed"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event occurred."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/KycErasureCompletedWebhookData"
          }
        }
      },
      "KycDocumentExpiryWarningWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a `kyc.document.expiry_warning` delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id; equals `data.deliveryId`."
          },
          "type": {
            "type": "string",
            "enum": [
              "kyc.document.expiry_warning"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event occurred."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/KycDocumentExpiryWarningWebhookData"
          }
        }
      },
      "KycReverificationDueWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a `kyc.reverification.due` delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id; equals `data.deliveryId`."
          },
          "type": {
            "type": "string",
            "enum": [
              "kyc.reverification.due"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event occurred."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/KycReverificationDueWebhookData"
          }
        }
      },
      "KycSessionCancelReason": {
        "type": "string",
        "description": "The closed set of reasons an end customer may give when cancelling a hosted session. There is deliberately no free-text reason.",
        "enum": [
          "not_now",
          "privacy_concern",
          "no_document",
          "technical_problem",
          "other"
        ]
      },
      "KycSessionCancelRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "reason": {
            "$ref": "#/components/schemas/KycSessionCancelReason"
          }
        }
      },
      "KycSessionCancelResponse": {
        "type": "object",
        "required": [
          "ok",
          "status",
          "cancelledAt",
          "cancelReason",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "cancelled_by_user"
            ]
          },
          "cancelledAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms of the cancel. An idempotent replay returns the ORIGINAL value, never a fresh timestamp."
          },
          "cancelReason": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycSessionCancelReason"
              },
              {
                "type": "null"
              }
            ],
            "description": "The reason recorded by the FIRST successful call (`null` if none was given). A replay returns the original reason."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycSessionNotCancellableError": {
        "type": "object",
        "required": [
          "error",
          "status",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "session_not_cancellable"
            ]
          },
          "status": {
            "type": "string",
            "description": "The session's current status, which is why it cannot be cancelled (`verifying`, `completed`, `unverified`, `error`, or `review_pending`)."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycLoadStatus": {
        "type": "object",
        "description": "The body of `GET /v1/kyc/load`: the platform's verification capacity (an aggregate shared by all customers), a recommendation derived from it, and your own in-flight count. When `status` is `unknown`, `capacity`, `inUse`, `queueDepth`, `estimatedWaitMs` and `processingMs` are all `null` and `reasons` is `[\"load_unavailable\"]`.",
        "required": [
          "ok",
          "status",
          "recommendation",
          "reasons",
          "capacity",
          "inUse",
          "queueDepth",
          "estimatedWaitMs",
          "processingMs",
          "yours",
          "asOf",
          "ttlSeconds",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "unknown"
            ],
            "description": "`ok`: the load was read and the numbers below are real. `unknown`: the load could not be read; the numbers are `null` and `recommendation` is `degraded`. Both are HTTP `200`."
          },
          "recommendation": {
            "$ref": "#/components/schemas/KycLoadRecommendation"
          },
          "reasons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KycLoadReason"
            },
            "description": "Why `recommendation` is not `accept`; empty when it is `accept`. Ordered with the `defer` reasons first; no value repeats. Treat an unrecognised value as informational."
          },
          "capacity": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Concurrent inference slots the platform has ready right now. One slot runs one inference job at a time and a verification uses a slot for each of its steps, so this is **not** a number of sessions. `0` when no capacity is ready; `null` when `status` is `unknown`."
          },
          "inUse": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Slots that are running a job right now, platform-wide (your jobs and everyone else's). `null` when `status` is `unknown`."
          },
          "queueDepth": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Jobs, platform-wide, that are waiting for a slot. `null` when `status` is `unknown`."
          },
          "estimatedWaitMs": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Estimated milliseconds a job that arrives now would wait before it starts running (not including its own processing time). `0` when a slot is free. `null` when `status` is `unknown`, or when no capacity is ready."
          },
          "processingMs": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/KycLoadProcessingMs"
              },
              {
                "type": "null"
              }
            ],
            "description": "How long recently completed inference jobs took, in milliseconds (the job itself, not the queue wait). `null` when `status` is `unknown` or no job has completed recently."
          },
          "yours": {
            "type": "object",
            "required": [
              "inFlight"
            ],
            "description": "The only values that are specific to your organisation.",
            "properties": {
              "inFlight": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "Verifications of your own organisation that are running right now (submitted to the verification service within the last 90 seconds and not yet finished). Computed on each request, never cached. `null` when it could not be counted; this never fails the request."
              }
            }
          },
          "asOf": {
            "type": "string",
            "format": "date-time",
            "description": "The instant, in ISO 8601 UTC, the platform snapshot was taken (for `unknown`, when the failed read was recorded)."
          },
          "ttlSeconds": {
            "type": "integer",
            "description": "How long Noxtica keeps the platform snapshot before reading the load again: 8 seconds, or 5 for an `unknown` answer. Polling more often than this only returns the same snapshot."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycLoadRecommendation": {
        "type": "string",
        "enum": [
          "accept",
          "degraded",
          "defer"
        ],
        "description": "`accept`: start the verification. `degraded`: it will be accepted but may be slower than usual. `defer`: the platform is saturated; queue the customer and ask again later."
      },
      "KycLoadReason": {
        "type": "string",
        "enum": [
          "no_ready_instance",
          "queue_wait_high",
          "queue_full",
          "recent_overload",
          "high_utilization",
          "queue_building",
          "wait_elevated",
          "reduced_capacity",
          "escalation_unavailable",
          "escalation_saturated",
          "vram_pressure",
          "slow_processing",
          "load_unavailable"
        ],
        "description": "Why a `KycLoadStatus.recommendation` is not `accept`. The first four values (`no_ready_instance`, `queue_wait_high`, `queue_full`, `recent_overload`) give `defer`; the others give `degraded`. The thresholds are listed in the description of `GET /v1/kyc/load`."
      },
      "KycLoadProcessingMs": {
        "type": "object",
        "required": [
          "p50",
          "p95",
          "samples"
        ],
        "description": "Percentiles of the duration of recently completed inference jobs. Each value is the upper bound of the duration bucket that holds the percentile (25, 50, 100, 200, 400, 800, 1500, 3000, 6000, 12000 or 20000 ms), so it is a conservative figure; durations above 20000 ms read as 20000.",
        "properties": {
          "p50": {
            "type": "integer",
            "minimum": 0,
            "description": "Median job duration in milliseconds."
          },
          "p95": {
            "type": "integer",
            "minimum": 0,
            "description": "95th-percentile job duration in milliseconds."
          },
          "samples": {
            "type": "integer",
            "minimum": 1,
            "description": "How many recently completed jobs the percentiles are computed from."
          }
        }
      },
      "KycSessionCancelledError": {
        "type": "object",
        "description": "The byte-identical 410 envelope every session-scoped KYC customer route (session-view/session-progress/session-submit/session-evidence/handle/ session-evidence/finalize) returns for a session the end customer cancelled (`cancelled_by_user`). The mirror of `KycSessionExpiredError`; never returned by the tenant-facing `GET /v1/kyc/sessions/{sessionId}` status read, which stays 200 with the `cancelled_by_user` status.",
        "required": [
          "error",
          "code",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "session_cancelled"
            ]
          },
          "code": {
            "type": "string",
            "enum": [
              "kyc_session_cancelled"
            ]
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycSessionExpiredError": {
        "type": "object",
        "description": "The byte-identical 410 envelope every session-scoped KYC customer route (session-view/session-progress/session-submit/session-evidence/handle/ session-evidence/finalize) returns for an expired, non-terminal session (KycSessionTtl, 2026-09-27 owner directive) — never returned by the tenant-facing `GET /v1/kyc/sessions/{sessionId}` status read, which stays 200 with an effective `status` of `expired` instead.",
        "required": [
          "error",
          "code",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "session_expired"
            ]
          },
          "code": {
            "type": "string",
            "enum": [
              "kyc_session_expired"
            ]
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "IdentityRedaction": {
        "type": "object",
        "description": "One item the response masked, left out or could not provide. It tells \"not there\" apart from \"left out on purpose\".",
        "required": [
          "field",
          "reason"
        ],
        "properties": {
          "field": {
            "type": "string",
            "description": "Path of the item in the response, for example `document.number`, `person.surname`, `person.givenNames`, `person.dateOfBirth`, `person.personalNumber`, `mrz`, `mrz.lines`, `address` or `documents[].extractions`."
          },
          "reason": {
            "type": "string",
            "enum": [
              "masked_by_default",
              "omitted_by_default",
              "not_extracted",
              "not_available"
            ],
            "description": "`masked_by_default` — the value is returned masked (its last characters only). `omitted_by_default` — the value is left out. `not_extracted` — Noxtica does not extract this item, or did not for this record. `not_available` — the item is not available for this record."
          },
          "include": {
            "type": "string",
            "description": "The `fields` value that returns the full item. Absent when no `fields` value does."
          }
        }
      },
      "IdentityInvalidFieldsError": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_fields",
              "invalid_request"
            ]
          },
          "allowed": {
            "type": "array",
            "description": "The `fields` values this route accepts. Present with `invalid_fields`.",
            "items": {
              "type": "string"
            }
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycReviewDecisionError": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "The error code: `idempotency_key_invalid`, `invalid_body`, `invalid_decision`, `invalid_reason_code`, `invalid_reviewer`, `invalid_note`, `review_decision_not_enabled`, `review_not_pending`, `review_closed`, `conflict`, `legal_basis_missing`, `idempotency_key_reuse`, `review_decision_unavailable`, `tenant_module_gate_unavailable`, `api_key_auth_unavailable`, or `API key does not have write:kyc_review_decision scope`."
          },
          "message": {
            "type": "string",
            "description": "What to fix or do next; never repeats a value you sent."
          },
          "reason": {
            "type": "string",
            "enum": [
              "pg_unavailable",
              "backend_unavailable"
            ],
            "description": "Only on `review_decision_unavailable`: which side could not answer. Retry after the `Retry-After` delay."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "IdentityForbiddenError": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "`identity_read_not_enabled`, or `API key does not have read:identity_details scope`."
          },
          "message": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "IdentityNotAvailableError": {
        "type": "object",
        "required": [
          "error",
          "reason",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "identity_not_available"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "not_completed",
              "not_extracted"
            ],
            "description": "`not_completed` — the verification has not finished. `not_extracted` — nothing was extracted or stored for it."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "KycIdentityCheckVerdict": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "pass",
          "review",
          "fail",
          "skipped",
          "pending",
          null
        ],
        "description": "One check family's verdict, or `null` when the verification has no such check."
      },
      "KycIdentityChecks": {
        "type": "object",
        "description": "A summary of the verification checks as verdicts. No scores or provider details are included.",
        "required": [
          "documentAuthenticity",
          "faceMatch",
          "liveness",
          "selfie",
          "crossValidation"
        ],
        "properties": {
          "documentAuthenticity": {
            "$ref": "#/components/schemas/KycIdentityCheckVerdict"
          },
          "faceMatch": {
            "$ref": "#/components/schemas/KycIdentityCheckVerdict"
          },
          "liveness": {
            "$ref": "#/components/schemas/KycIdentityCheckVerdict"
          },
          "selfie": {
            "$ref": "#/components/schemas/KycIdentityCheckVerdict"
          },
          "crossValidation": {
            "type": [
              "object",
              "null"
            ],
            "description": "Whether the values read from the different parts of the document agree. The agreement is returned, not the values compared; `null` when no cross-check ran.",
            "required": [
              "overall",
              "fields"
            ],
            "properties": {
              "overall": {
                "type": "string",
                "enum": [
                  "match",
                  "mismatch",
                  "unavailable"
                ]
              },
              "fields": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "field",
                    "agreement"
                  ],
                  "properties": {
                    "field": {
                      "type": "string",
                      "example": "dateOfBirth",
                      "description": "The name of the compared value."
                    },
                    "agreement": {
                      "type": "string",
                      "enum": [
                        "match",
                        "mismatch",
                        "unavailable"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "KycIdentityFieldRead": {
        "type": "object",
        "description": "The outcome of reading one identity field from the document: whether it was read and, when it was not, why.",
        "required": [
          "field",
          "status",
          "reason",
          "source",
          "required"
        ],
        "properties": {
          "field": {
            "type": "string",
            "enum": [
              "person.surname",
              "person.givenNames",
              "person.dateOfBirth",
              "person.personalNumber",
              "document.number"
            ],
            "description": "The path of the field in this response."
          },
          "status": {
            "type": "string",
            "enum": [
              "read",
              "unreadable",
              "not_expected"
            ],
            "description": "`read` — the field has a value (released, or withheld by the masking rules; `read` does not mean the value is in the response). `unreadable` — no value, see `reason`. `not_expected` — this document does not carry the field and your configuration does not require it."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "label_not_found",
              "value_not_found",
              "value_invalid",
              "checksum_invalid",
              "layout_unrecognised",
              "low_confidence",
              "not_recorded",
              null
            ],
            "description": "Only with `unreadable`, otherwise `null`. `label_not_found`, `value_not_found`, `value_invalid`, `checksum_invalid` (the personal number's check digits fail; the value is withheld), `layout_unrecognised`, `low_confidence` (a date read below the recognition confidence floor is withheld) or `not_recorded` (a record written before reasons were kept)."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "viz",
              "mrz",
              "barcode",
              null
            ],
            "description": "Where the value came from, or `null` unless `status` is `read`."
          },
          "uncertainty": {
            "type": "array",
            "description": "Only on `person.surname` and `person.givenNames`, and only when it applies (the key is absent otherwise). Advisory caveats on a `read` value. `diacritic_uncertain` — the value is the best reading, but a letter in it may carry a mark that the reading did not show, or the reverse (a Turkish `Ş` shown as `S`): the reader saw the marked and the plain letter as close candidates, two readers of the printed text differed only in a mark, or the value comes from the machine-readable zone or a barcode, which cannot print marks. `mrz_transliteration_conflict` — the printed name and the machine-readable zone fit together only if a plain letter of the printed name carries a mark (printed `MULLER`, zone `MUELLER`); it can come with or without `diacritic_uncertain`. `status` stays `read`; check `alternatives` and `foldedValue` as described under \"Names, diacritics and alphabets\" on `GET /v1/kyc/verifications/{verificationId}/identity`. A field without this key can still contain a wrong letter.",
            "items": {
              "type": "string",
              "enum": [
                "diacritic_uncertain",
                "mrz_transliteration_conflict"
              ]
            }
          },
          "uncertainPositions": {
            "type": "array",
            "description": "Only with `diacritic_uncertain`. 0-based Unicode code point positions in the name (`person.surname` or `person.givenNames`, spaces counted), ascending, of the letters that may carry a different mark. For a name read from the printed text: the letters the reader had doubt about. For a name taken from the machine-readable zone or a barcode: every letter that has a marked form in the alphabet of the issuing country.",
            "items": {
              "type": "integer",
              "minimum": 0
            }
          },
          "alternatives": {
            "type": "array",
            "description": "Up to four complete spellings of the name, most likely first, made only by swapping the flagged letters for their marked or plain forms. None equals the value and none repeats. With `mrz_transliteration_conflict` the list includes the spelling the machine-readable zone points to. Absent when there is no reading-based doubt, in particular for a name taken from the machine-readable zone or a barcode. The value is never replaced by an alternative; do not store one as the legal name without a confirmation.",
            "maxItems": 4,
            "items": {
              "type": "string"
            }
          },
          "foldedValue": {
            "type": "string",
            "description": "On every `read` `person.surname` and `person.givenNames`, with or without an uncertainty: the value with its marks removed as ICAO Doc 9303 Part 3 section 6.A lists them (one base letter such as `Ş` to `S`; the expansions `ß` to `SS`, `Æ` to `AE`, `Œ` to `OE`, `Ø` to `OE`, `Þ` to `TH`, `Ĳ` to `IJ`). The case of the value is kept. Use it as a key for tolerant comparison, not as the spelling of the name."
          },
          "script": {
            "type": "string",
            "enum": [
              "Latn",
              "Cyrl",
              "Grek",
              "Arab",
              "Hebr",
              "Geor",
              "Armn",
              "Deva",
              "Thai",
              "Hani",
              "Jpan",
              "Kore"
            ],
            "description": "The writing system the name was read in, as an ISO 15924 code. Absent when it was not determined."
          },
          "latinForm": {
            "type": "object",
            "description": "A Latin-letter form of the name, when one is available.",
            "required": [
              "value",
              "source"
            ],
            "properties": {
              "value": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "enum": [
                  "viz",
                  "mrz"
                ],
                "description": "`viz` — printed on the document. `mrz` — the name in the machine-readable zone, which uses the ICAO transliteration."
              }
            }
          },
          "required": {
            "type": "boolean",
            "description": "`true` when the field counts towards `extraction.status` and the reason code `identity_fields_unreadable`; `false` when it is reported but does not. An `unreadable` field with `required: false` keeps its `reason`."
          }
        },
        "examples": [
          {
            "field": "person.surname",
            "status": "read",
            "reason": null,
            "source": "viz",
            "required": true,
            "uncertainty": [
              "diacritic_uncertain"
            ],
            "uncertainPositions": [
              0,
              7
            ],
            "alternatives": [
              "ORNEKTAŞ",
              "ÖRNEKTAŞ",
              "ÖRNEKTAS"
            ],
            "foldedValue": "ORNEKTAS",
            "script": "Latn"
          },
          {
            "field": "person.surname",
            "status": "read",
            "reason": null,
            "source": "viz",
            "required": true,
            "uncertainty": [
              "mrz_transliteration_conflict"
            ],
            "alternatives": [
              "MÜLLER"
            ],
            "foldedValue": "MULLER",
            "script": "Latn"
          },
          {
            "field": "person.surname",
            "status": "read",
            "reason": null,
            "source": "mrz",
            "required": true,
            "uncertainty": [
              "diacritic_uncertain"
            ],
            "uncertainPositions": [
              0,
              3
            ],
            "foldedValue": "SAHIN",
            "script": "Latn"
          }
        ]
      },
      "KycIdentityExtraction": {
        "type": "object",
        "description": "Whether the identity fields a relying party needs were read from the document, field by field, with the reason for every field that was not.",
        "required": [
          "status",
          "reasonCodes",
          "fields",
          "documentType",
          "issuingCountry"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "complete",
              "partial",
              "unreadable"
            ],
            "description": "`complete` — every required field was read (also when none is required). `partial` — some were. `unreadable` — none was."
          },
          "reasonCodes": {
            "type": "array",
            "description": "`identity_fields_unreadable` when a required field is unreadable; `document_type_mismatch_selection` when `documentType.contradictsSelection` is `true`; `issuing_country_mismatch_selection` when `issuingCountry.contradictsSelection` is `true`; `diacritic_uncertain` or `mrz_transliteration_conflict` when `person.surname` or `person.givenNames` carries that `uncertainty` (informational; unlike the other three, not added to the `reasonCodes` of the verification and of the session).",
            "items": {
              "type": "string",
              "enum": [
                "identity_fields_unreadable",
                "document_type_mismatch_selection",
                "issuing_country_mismatch_selection",
                "diacritic_uncertain",
                "mrz_transliteration_conflict"
              ]
            }
          },
          "fields": {
            "type": "array",
            "description": "Always five entries, in this order: `person.surname`, `person.givenNames`, `person.dateOfBirth`, `person.personalNumber`, `document.number`.",
            "minItems": 5,
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/KycIdentityFieldRead"
            }
          },
          "documentType": {
            "type": "object",
            "required": [
              "selected",
              "classified",
              "contradictsSelection"
            ],
            "properties": {
              "selected": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "national_id",
                  "passport",
                  "driving_license",
                  "residence_permit",
                  null
                ],
                "description": "The document type the person chose in the hosted flow (the same value as `document.selectedType`), or `null`."
              },
              "classified": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "national_id",
                  "driving_license",
                  "passport",
                  "residence_permit",
                  null
                ],
                "description": "The document type the classifier named, only when it is confident (0.72 or higher); otherwise `null`."
              },
              "contradictsSelection": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "`true` when the classified type is a different identity document type than the selection, `false` when they agree, `null` when they cannot be compared."
              }
            }
          },
          "issuingCountry": {
            "type": "object",
            "description": "Whether the country the person selected agrees with the country the document shows.",
            "required": [
              "selected",
              "detected",
              "source",
              "contradictsSelection"
            ],
            "properties": {
              "selected": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^[A-Z]{2}$",
                "description": "The issuing country the person chose in the hosted flow, as an ISO 3166-1 alpha-2 code (a `CountryCode` value), or `null` for a verification created through the API, a session that recorded no choice, or a record written before the selection was kept."
              },
              "detected": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^[A-Z]{2}$",
                "description": "The country the document shows, as an ISO 3166-1 alpha-2 code, or `null` when nothing readable named one."
              },
              "source": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "mrz",
                  "viz",
                  null
                ],
                "description": "Where `detected` came from: `mrz` (the issuing state of the machine-readable zone, used only when none of its check digits failed) or `viz` (the printed text of the front). `null` exactly when `detected` is `null`."
              },
              "contradictsSelection": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "`true` when both are known and differ, `false` when they are equal, `null` when they cannot be compared (no selection, nothing read, or an older record). An unknown is never reported as a contradiction; a dependent territory against its sovereign state is not one."
              }
            }
          }
        }
      },
      "KycVerificationIdentity": {
        "type": "object",
        "description": "The identity details of one verification. Values appear as read from the document; a value that could not be read is `null`. Masked or left-out items are listed in `redactions`.",
        "required": [
          "ok",
          "verificationId",
          "sourceEvidenceGroupId",
          "extractedAt",
          "verification",
          "document",
          "person",
          "address",
          "mrz",
          "checks",
          "extraction",
          "redactions",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "verificationId": {
            "$ref": "#/components/schemas/KycVerificationId"
          },
          "sourceEvidenceGroupId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `evidenceGroupId` the verification was made from, or `null` when it did not come from your own capture screens."
          },
          "extractedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the details were extracted and stored."
          },
          "verification": {
            "type": "object",
            "required": [
              "status",
              "source",
              "level",
              "review"
            ],
            "properties": {
              "status": {
                "$ref": "#/components/schemas/KycVerificationStatus"
              },
              "source": {
                "$ref": "#/components/schemas/KycVerificationStatusSource"
              },
              "level": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "verified",
                  "partial",
                  "unverified",
                  "inconclusive",
                  null
                ],
                "description": "The automated verification level, or `null` when none was assigned. A review does not change it."
              },
              "review": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/KycTenantReview"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The reviewer's work on the verification; `null` while no reviewer has acted."
              }
            }
          },
          "document": {
            "type": "object",
            "required": [
              "type",
              "selectedType",
              "side",
              "issuingCountry",
              "number",
              "expiryDate",
              "idType"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "national_id",
                  "driving_license",
                  "passport",
                  "residence_permit",
                  "unknown"
                ],
                "description": "The document type the stored extraction is for."
              },
              "selectedType": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "national_id",
                  "passport",
                  "driving_license",
                  "residence_permit",
                  null
                ],
                "description": "The document type the person chose in the hosted flow, or `null` for a verification created through the API or a session that recorded no choice. Differs from `type` when the extraction ran against another document type than the person presented."
              },
              "side": {
                "type": "string",
                "enum": [
                  "front",
                  "back",
                  "data_page",
                  "unknown"
                ]
              },
              "issuingCountry": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The country the document shows when one was read (`extraction.issuingCountry`), otherwise the country the person selected in the hosted flow, otherwise `null`."
              },
              "number": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The document number, masked (its last characters only) unless `fields` names `documentNumber`."
              },
              "expiryDate": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date"
              },
              "idType": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "person": {
            "type": "object",
            "required": [
              "fullName",
              "surname",
              "givenNames",
              "dateOfBirth",
              "sex",
              "nationality",
              "personalNumber"
            ],
            "properties": {
              "fullName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "surname": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "givenNames": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "dateOfBirth": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date"
              },
              "sex": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "As read from the document, usually `M`, `F` or `X`."
              },
              "nationality": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "personalNumber": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Left out (`null`) unless `fields` names `personalNumber`."
              }
            }
          },
          "address": {
            "type": [
              "object",
              "null"
            ],
            "description": "Always `null` today: Noxtica does not extract an address from identity documents. The key is present so a later release can fill it without changing the shape."
          },
          "mrz": {
            "type": [
              "object",
              "null"
            ],
            "description": "The machine-readable zone of the document, or `null` when none was found.",
            "required": [
              "format",
              "fieldChecks"
            ],
            "properties": {
              "format": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "TD1",
                  "TD2",
                  "TD3",
                  null
                ]
              },
              "fieldChecks": {
                "type": "array",
                "description": "The check-digit result of each MRZ field. Structural results only.",
                "items": {
                  "type": "object",
                  "required": [
                    "field",
                    "checkDigitValid"
                  ],
                  "properties": {
                    "field": {
                      "type": "string",
                      "example": "document_number"
                    },
                    "checkDigitValid": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "lines": {
                "type": "array",
                "description": "The raw MRZ lines. Present only when `fields` names `mrzLines`.",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "checks": {
            "$ref": "#/components/schemas/KycIdentityChecks"
          },
          "extraction": {
            "$ref": "#/components/schemas/KycIdentityExtraction"
          },
          "redactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IdentityRedaction"
            }
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "enum": [
              "2.0"
            ]
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ],
            "description": "Echoed in the response. Omit only for requests where you do not need the answer correlated."
          },
          "method": {
            "type": "string",
            "description": "One of `initialize`, `tools/list`, `tools/call`, `resources/list`, `resources/templates/list`, `resources/read`."
          },
          "params": {
            "type": "object",
            "description": "Method parameters. `tools/call` takes `{name, arguments}`; `resources/read` takes `{uri}`; `initialize` may carry `protocolVersion`."
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc",
          "id"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "enum": [
              "2.0"
            ]
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "result": {
            "description": "Present on success. For `tools/call`: `{content: [{type: \"text\", text: \"<JSON>\"}], isError: false}`. For `tools/list`: `{tools: [...]}`. For `initialize`: `{protocolVersion, serverInfo, capabilities}`."
          },
          "error": {
            "$ref": "#/components/schemas/JsonRpcError"
          }
        }
      },
      "JsonRpcError": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "description": "`code` values: `-32700` the body is not valid JSON; `-32600` no `method`; `-32601` unknown\nmethod; `-32602` invalid parameters, or the result exceeds 200 KB; `-32603` the tool or\nresource failed; `-32001` the token's scopes or tool list do not allow the tool\n(`data.requiredScope` names the missing scope); `-32002` tool not found for this token;\n`-32003` the monthly query allowance is used up; `-32004` too many tool calls\n(`data.retryAfterSeconds`, `data.limit`, `data.windowSeconds`); `-32005` MCP is not\nincluded in the plan; `-32006` resource not found.",
        "properties": {
          "code": {
            "type": "integer",
            "enum": [
              -32700,
              -32600,
              -32601,
              -32602,
              -32603,
              -32001,
              -32002,
              -32003,
              -32004,
              -32005,
              -32006
            ]
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "description": "Optional structured detail, for example `requiredScope` or `retryAfterSeconds`.",
            "properties": {
              "requiredScope": {
                "type": "string"
              },
              "retryAfterSeconds": {
                "type": "integer"
              },
              "limit": {
                "type": "integer"
              },
              "windowSeconds": {
                "type": "integer"
              },
              "code": {
                "type": "string"
              }
            }
          }
        }
      },
      "McpHttpError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "unauthorized",
              "not_found",
              "rate_limited",
              "internal_error"
            ]
          }
        }
      },
      "ApiError": {
        "type": "object",
        "required": [
          "code",
          "error"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable machine-readable identifier."
          },
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "AttestationReasonCode": {
        "type": "string",
        "enum": [
          "ok",
          "ek_chain_unverified",
          "no_policy",
          "tpm_unavailable",
          "base_level_below_l2",
          "platform_unsupported",
          "quote_signature_invalid",
          "quote_not_a_quote",
          "quote_nonce_mismatch",
          "ak_attributes_invalid",
          "pcr_select_mismatch",
          "pcr_digest_mismatch",
          "pcr_not_in_policy",
          "certify_signature_invalid",
          "certify_not_a_certify",
          "certify_nonce_mismatch",
          "certify_name_mismatch",
          "posture_secure_boot_required",
          "posture_lockdown_below_min",
          "posture_ima_required",
          "posture_dm_verity_required",
          "attestation_stale",
          "attestation_withdrawn",
          "policy_revised"
        ],
        "description": "Closed reason codes of an attestation verdict. `ok` + `ek_chain_unverified` accompany every L3."
      },
      "TenantAttestation": {
        "type": "object",
        "required": [
          "level",
          "reasonCodes",
          "attestedAt",
          "expiresAt",
          "policyRevision"
        ],
        "description": "DET-1, additive. The attestation facts of the active enrolment with the highest EFFECTIVE level (ties: most recent statement). No posture values and no PCR values. The level is the effective one: an L3 past its expiry reads as its base level with `attestation_stale`.",
        "properties": {
          "level": {
            "type": "string",
            "enum": [
              "L0",
              "L1",
              "L2",
              "L3"
            ]
          },
          "reasonCodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AttestationReasonCode"
            }
          },
          "attestedAt": {
            "oneOf": [
              {
                "type": "integer",
                "format": "int64"
              },
              {
                "type": "null"
              }
            ]
          },
          "expiresAt": {
            "oneOf": [
              {
                "type": "integer",
                "format": "int64"
              },
              {
                "type": "null"
              }
            ]
          },
          "policyRevision": {
            "oneOf": [
              {
                "type": "integer",
                "minimum": 1
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "TenantSubjectStatus": {
        "type": "object",
        "description": "A `psub_…` subject always carries every member. A subject resolved through a LINK carries `scopes` and only the members of the scopes the player granted (`status`: `enrolled` + `revokedAt`/`reasonCode`; `attestation_level`: `deviceAttestationLevel` + `attestation`; `last_seen`: `lastSeen`; `open_signals`: `openSignals`).",
        "required": [
          "schemaVersion",
          "subject"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "pulse.tenant.subject-status/v1"
          },
          "scopes": {
            "type": "array",
            "description": "Present only for a subject resolved through a link: the scopes the player granted.",
            "items": {
              "$ref": "#/components/schemas/LinkScope"
            }
          },
          "subject": {
            "type": "string",
            "description": "The subject exactly as asked."
          },
          "enrolled": {
            "type": "boolean",
            "description": "At least one active enrolment."
          },
          "deviceAttestationLevel": {
            "description": "Highest attestation level among the active enrolments; `null` when not enrolled.",
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "L0",
                  "L1",
                  "L2",
                  "L3"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "attestation": {
            "description": "DET-1, additive (same `attestation_level` link scope as `deviceAttestationLevel`). The attestation facts of the best active enrolment; `null` when it never attested; absent when the subject is not enrolled.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/TenantAttestation"
              },
              {
                "type": "null"
              }
            ]
          },
          "lastSeen": {
            "description": "Latest control-plane activity (epoch ms) of any enrolment of the subject, `null` when never.",
            "oneOf": [
              {
                "type": "integer",
                "format": "int64"
              },
              {
                "type": "null"
              }
            ]
          },
          "openSignals": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/TenantOpenSignal"
            }
          },
          "responseHints": {
            "type": "array",
            "description": "Additive. The subject's ACTIVE response hints (an executed `suspend_session_hint` that passed the server's Art. 22 gate, not reversed, not expired), newest first; `[]` when there are none. Never a dry run, a suppressed action or a notice. A hint is a signal for the tenant's own decision. For a link subject present only when the player granted `open_signals`.",
            "items": {
              "$ref": "#/components/schemas/TenantResponseHint"
            }
          },
          "revokedAt": {
            "type": "integer",
            "format": "int64",
            "description": "Present only when `enrolled` is false."
          },
          "reasonCode": {
            "type": "string",
            "description": "Present only when `enrolled` is false; the closed revocation reason (e.g. `player_removed`, `tenant_revoked`, `refresh_reuse`)."
          }
        }
      },
      "ResponseAction": {
        "type": "string",
        "enum": [
          "notify_user",
          "suspend_session_hint"
        ],
        "description": "The closed local-action allow-list of the response executor. Nothing else is representable."
      },
      "ResponseOutcome": {
        "type": "string",
        "enum": [
          "executed",
          "dry_run",
          "suppressed_rate_limit",
          "suppressed_cooldown",
          "suppressed_kill_switch",
          "suppressed_requires_human"
        ]
      },
      "TenantDecisionList": {
        "type": "object",
        "required": [
          "schemaVersion",
          "decisions",
          "nextCursor"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "pulse.tenant.decisions/v1"
          },
          "decisions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TenantDecision"
            }
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque; `null` on the last page."
          }
        }
      },
      "TenantDecision": {
        "type": "object",
        "required": [
          "schemaVersion",
          "id",
          "appId",
          "subject",
          "action",
          "outcome",
          "reason",
          "reasonCode",
          "severity",
          "evidenceTier",
          "ruleId",
          "ladderId",
          "bundleRevision",
          "observedAt",
          "recordedAt",
          "detection",
          "hint"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "pulse.tenant.decision/v1"
          },
          "id": {
            "type": "string",
            "pattern": "^pact_[0-7][0-9A-HJKMNP-TV-Z]{25}$"
          },
          "appId": {
            "type": "string"
          },
          "subject": {
            "type": "object",
            "required": [
              "psub",
              "deviceRef"
            ],
            "properties": {
              "psub": {
                "type": "string"
              },
              "deviceRef": {
                "type": "string"
              }
            }
          },
          "action": {
            "$ref": "#/components/schemas/ResponseAction"
          },
          "outcome": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ResponseOutcome"
              }
            ],
            "description": "The outcome of record after the server's gates (a report the server downgraded reads `suppressed_kill_switch`, `suppressed_requires_human` or `dry_run`)."
          },
          "reason": {
            "type": "string"
          },
          "reasonCode": {
            "type": "string"
          },
          "severity": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "critical"
            ]
          },
          "evidenceTier": {
            "type": "string",
            "enum": [
              "t0",
              "t1"
            ]
          },
          "ruleId": {
            "type": "string"
          },
          "ladderId": {
            "type": "string"
          },
          "bundleRevision": {
            "type": "integer"
          },
          "observedAt": {
            "type": "integer",
            "format": "int64"
          },
          "recordedAt": {
            "type": "integer",
            "format": "int64"
          },
          "detection": {
            "description": "The detection the decision answers; `status` and `appealState` are `null` while its event has not been ingested.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "id",
                  "status",
                  "appealState"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "open",
                      "under_review",
                      "confirmed",
                      "dismissed",
                      "overturned",
                      "suppressed",
                      null
                    ]
                  },
                  "appealState": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "none",
                      "open",
                      "upheld",
                      "overturned",
                      "withdrawn",
                      null
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "hint": {
            "description": "`null` when the decision produced no hint.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "state",
                  "issuedAt",
                  "expiresAt",
                  "reversedAt",
                  "reversalSource",
                  "reversalReasonCode"
                ],
                "properties": {
                  "state": {
                    "type": "string",
                    "enum": [
                      "active",
                      "expired",
                      "reversed"
                    ]
                  },
                  "issuedAt": {
                    "type": "integer",
                    "format": "int64",
                    "description": "When the hint began: `min(observed_at, recorded_at)` — never later than the server saw the report. Equals `responseHints[].issuedAt` of the subject status."
                  },
                  "expiresAt": {
                    "type": "integer",
                    "format": "int64"
                  },
                  "reversedAt": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "format": "int64"
                  },
                  "reversalSource": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "staff",
                      "tenant_api",
                      "appeal_overturned",
                      "detection_dismissed",
                      "detection_overturned",
                      "detection_suppressed",
                      null
                    ]
                  },
                  "reversalReasonCode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "handled",
                      "false_positive",
                      "account_verified",
                      "policy_exception",
                      "other",
                      null
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "TenantResponseHint": {
        "type": "object",
        "required": [
          "decisionId",
          "action",
          "reasonCode",
          "severity",
          "issuedAt",
          "expiresAt"
        ],
        "properties": {
          "decisionId": {
            "type": "string",
            "description": "The `pact_…` id of the decision (`GET /pulse/v1/tenant/decisions/{id}`)."
          },
          "action": {
            "type": "string",
            "const": "suspend_session_hint"
          },
          "reasonCode": {
            "type": "string"
          },
          "severity": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "critical"
            ]
          },
          "issuedAt": {
            "type": "integer",
            "format": "int64",
            "description": "When the hint began: `min(observed_at, recorded_at)` of the decision (equals `hint.issuedAt` of `GET /pulse/v1/tenant/decisions/{id}`)."
          },
          "expiresAt": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "LinkScope": {
        "type": "string",
        "enum": [
          "status",
          "attestation_level",
          "last_seen",
          "open_signals"
        ],
        "description": "What a tenant may see of a linked player. The consent screen requests all four."
      },
      "LinkUnlinkedBy": {
        "type": "string",
        "enum": [
          "player",
          "tenant",
          "platform",
          "account_deleted"
        ]
      },
      "TenantLinkCodeRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "externalUserId",
          "appId"
        ],
        "properties": {
          "externalUserId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "The tenant's own user id. No leading/trailing whitespace, no control characters."
          },
          "appId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
            "description": "An ACTIVE application of this tenant."
          },
          "locale": {
            "type": "string",
            "maxLength": 35,
            "description": "BCP 47 tag appended to the `url` so the consent screen opens in the person's language."
          }
        }
      },
      "TenantLinkCode": {
        "type": "object",
        "required": [
          "code",
          "expiresAt",
          "url"
        ],
        "properties": {
          "code": {
            "type": "string",
            "pattern": "^pl_[A-Za-z0-9_-]{32}$",
            "description": "Shown once; single use."
          },
          "expiresAt": {
            "type": "integer",
            "format": "int64",
            "description": "Epoch ms; 10 minutes after issue."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "`{app origin}/link?code=…[&locale=…]` — the page the person opens."
          }
        }
      },
      "TenantLink": {
        "type": "object",
        "required": [
          "schemaVersion",
          "subject",
          "externalUserId",
          "applicationId",
          "state",
          "scopes",
          "linkedAt",
          "unlinkedAt",
          "unlinkedBy"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "pulse.tenant.link/v1"
          },
          "subject": {
            "type": "string",
            "pattern": "^plnk_[0-9A-HJKMNP-TV-Z]{26}$",
            "description": "The pairwise subject: random per link, never derived from the account."
          },
          "externalUserId": {
            "type": "string"
          },
          "applicationId": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "unlinked"
            ]
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LinkScope"
            }
          },
          "linkedAt": {
            "type": "integer",
            "format": "int64"
          },
          "unlinkedAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "unlinkedBy": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/LinkUnlinkedBy"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "TenantLinkUnlinked": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TenantLink"
          },
          {
            "type": "object",
            "required": [
              "changed"
            ],
            "properties": {
              "changed": {
                "type": "boolean",
                "description": "`false` when the link was already unlinked (nothing written)."
              }
            }
          }
        ]
      },
      "TenantOpenSignal": {
        "type": "object",
        "required": [
          "reasonCode",
          "severity",
          "tier",
          "status",
          "detectedAt"
        ],
        "properties": {
          "reasonCode": {
            "type": "string",
            "pattern": "^[A-Z]{3}-[0-9]{3}$"
          },
          "severity": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "critical"
            ]
          },
          "tier": {
            "type": "string",
            "enum": [
              "t0",
              "t1",
              "t2"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "under_review"
            ]
          },
          "detectedAt": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "ProtectedActionVerifyError": {
        "type": "object",
        "required": [
          "ok",
          "error",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string",
            "enum": [
              "invalid_body",
              "invalid_request_id",
              "invalid_action",
              "invalid_expected_domain",
              "payload_too_large",
              "request_not_found",
              "already_consumed",
              "action_mismatch",
              "domain_mismatch"
            ]
          },
          "consumed": {
            "type": "boolean",
            "description": "Present on `already_consumed`."
          },
          "consumedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Present on `already_consumed`."
          },
          "requestId": {
            "type": "string",
            "description": "The collector's own correlation id for this call."
          }
        }
      },
      "ApmScopedId": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128,
        "pattern": "^[A-Za-z0-9._:-]+$"
      },
      "ApmError": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "ApmRateLimitError": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error",
          "retryAfter",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "retryAfter": {
            "type": "integer",
            "minimum": 1
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "ApmUnavailable": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "available",
          "reason",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "available": {
            "type": "boolean",
            "const": false
          },
          "reason": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "ApmConflict": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "error",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "enum": [
              "capture_unavailable",
              "authority_epoch_conflict"
            ]
          },
          "available": {
            "type": "boolean",
            "const": false
          },
          "reason": {
            "type": "string",
            "enum": [
              "capture_rollout_disabled"
            ]
          },
          "expected_authority_epoch": {
            "type": "integer",
            "minimum": 1
          },
          "actual_authority_epoch": {
            "type": "integer",
            "minimum": 1
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "ApmGrantUnavailable": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "available",
          "reason",
          "state",
          "requestId"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "available": {
            "type": "boolean",
            "const": false
          },
          "reason": {
            "type": "string",
            "enum": [
              "export_not_ready",
              "export_expired",
              "export_failed",
              "download_signing_unavailable"
            ]
          },
          "state": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "ApmProject": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "project_id",
          "display_name",
          "state",
          "region_id",
          "data_plane_id",
          "capture_enabled",
          "enabled_signals",
          "authority_epoch",
          "erasure_epoch",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "display_name": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "region_id": {
            "type": "string"
          },
          "data_plane_id": {
            "type": "string"
          },
          "capture_enabled": {
            "type": "boolean"
          },
          "enabled_signals": {
            "type": "array",
            "maxItems": 2,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "traces",
                "rum_events"
              ]
            }
          },
          "authority_epoch": {
            "type": "integer",
            "minimum": 0
          },
          "erasure_epoch": {
            "type": "integer",
            "minimum": 0
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApmEnvironmentPolicy": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "project_id",
          "environment_id",
          "display_name",
          "environment_kind",
          "state",
          "region_id",
          "data_plane_id",
          "capture_enabled",
          "allowed_signals",
          "sampling_policy_id",
          "retention_policy_version",
          "privacy_policy_version",
          "authority_epoch",
          "erasure_epoch",
          "quota_manifest_revision",
          "parser_manifest_revision",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "display_name": {
            "type": "string"
          },
          "environment_kind": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "region_id": {
            "type": "string"
          },
          "data_plane_id": {
            "type": "string"
          },
          "capture_enabled": {
            "type": "boolean"
          },
          "allowed_signals": {
            "type": "array",
            "maxItems": 2,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "traces",
                "rum_events"
              ]
            }
          },
          "sampling_policy_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ApmScopedId"
              },
              {
                "type": "null"
              }
            ]
          },
          "retention_policy_version": {
            "type": "integer",
            "minimum": 1
          },
          "privacy_policy_version": {
            "type": "integer",
            "minimum": 1
          },
          "authority_epoch": {
            "type": "integer",
            "minimum": 0
          },
          "erasure_epoch": {
            "type": "integer",
            "minimum": 0
          },
          "quota_manifest_revision": {
            "type": "integer",
            "minimum": 0
          },
          "parser_manifest_revision": {
            "type": "integer",
            "minimum": 0
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApmProjectList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "items",
          "count",
          "truncated",
          "next_cursor",
          "freshness",
          "completeness"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApmProject"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "truncated": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "freshness": {
            "type": "string",
            "const": "postgres_authority"
          },
          "completeness": {
            "type": "string",
            "enum": [
              "complete",
              "truncated"
            ]
          }
        }
      },
      "ApmEnvironmentList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "project_id",
          "items",
          "count",
          "truncated",
          "next_cursor",
          "freshness",
          "completeness"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApmEnvironmentPolicy"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "truncated": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "freshness": {
            "type": "string",
            "const": "postgres_authority"
          },
          "completeness": {
            "type": "string",
            "enum": [
              "complete",
              "truncated"
            ]
          }
        }
      },
      "ApmSamplingPolicy": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "policy_id",
          "policy_version",
          "signal",
          "schema_version",
          "sampling_class",
          "mode",
          "enabled",
          "rate_ppm",
          "tail_window_seconds",
          "tail_budget_events",
          "effective_from",
          "effective_until"
        ],
        "properties": {
          "policy_id": {
            "type": "string"
          },
          "policy_version": {
            "type": "integer"
          },
          "signal": {
            "type": "string"
          },
          "schema_version": {
            "type": "integer"
          },
          "sampling_class": {
            "type": "string"
          },
          "mode": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "rate_ppm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "tail_window_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "tail_budget_events": {
            "type": [
              "integer",
              "null"
            ]
          },
          "effective_from": {
            "type": "string",
            "format": "date-time"
          },
          "effective_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmRetentionPolicy": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "signal",
          "retention_class",
          "policy_version",
          "duration_seconds",
          "minimum_seconds",
          "maximum_seconds",
          "region_id",
          "deletion_mode",
          "legal_hold_state",
          "enabled",
          "effective_from",
          "effective_until"
        ],
        "properties": {
          "signal": {
            "type": "string"
          },
          "retention_class": {
            "type": "string"
          },
          "policy_version": {
            "type": "integer"
          },
          "duration_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "minimum_seconds": {
            "type": "integer"
          },
          "maximum_seconds": {
            "type": "integer"
          },
          "region_id": {
            "type": "string"
          },
          "deletion_mode": {
            "type": "string"
          },
          "legal_hold_state": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "effective_from": {
            "type": "string",
            "format": "date-time"
          },
          "effective_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmAuthorityFreshness": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "source",
          "updated_at"
        ],
        "properties": {
          "source": {
            "type": "string",
            "const": "postgres_authority"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApmPolicySnapshot": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "project_id",
          "environment_id",
          "policy",
          "sampling_policies",
          "sampling_policy_count",
          "sampling_policies_truncated",
          "retention_policies",
          "retention_policy_count",
          "retention_policies_truncated",
          "freshness",
          "completeness"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "policy": {
            "$ref": "#/components/schemas/ApmEnvironmentPolicy"
          },
          "sampling_policies": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/ApmSamplingPolicy"
            }
          },
          "sampling_policy_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "sampling_policies_truncated": {
            "type": "boolean"
          },
          "retention_policies": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/ApmRetentionPolicy"
            }
          },
          "retention_policy_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "retention_policies_truncated": {
            "type": "boolean"
          },
          "freshness": {
            "$ref": "#/components/schemas/ApmAuthorityFreshness"
          },
          "completeness": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "sampling_policies",
              "retention_policies"
            ],
            "properties": {
              "sampling_policies": {
                "type": "string",
                "enum": [
                  "complete",
                  "truncated"
                ]
              },
              "retention_policies": {
                "type": "string",
                "enum": [
                  "complete",
                  "truncated"
                ]
              }
            }
          }
        }
      },
      "ApmPolicyUpdate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "expected_authority_epoch"
        ],
        "minProperties": 2,
        "properties": {
          "expected_authority_epoch": {
            "type": "integer",
            "minimum": 1
          },
          "capture_enabled": {
            "type": "boolean",
            "const": false,
            "description": "`true` is unavailable while capture rollout is OFF."
          },
          "allowed_signals": {
            "type": "array",
            "maxItems": 2,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "traces",
                "rum_events"
              ]
            }
          },
          "sampling_policy_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ApmScopedId"
              },
              {
                "type": "null"
              }
            ]
          },
          "retention_policy_version": {
            "type": "integer",
            "minimum": 1
          },
          "privacy_policy_version": {
            "type": "integer",
            "minimum": 1
          },
          "region_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "data_plane_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          }
        }
      },
      "ApmPolicyUpdateResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "project_id",
          "environment_id",
          "policy",
          "count",
          "truncated",
          "freshness",
          "completeness"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "policy": {
            "$ref": "#/components/schemas/ApmEnvironmentPolicy"
          },
          "count": {
            "type": "integer",
            "const": 1
          },
          "truncated": {
            "type": "boolean",
            "const": false
          },
          "freshness": {
            "$ref": "#/components/schemas/ApmAuthorityFreshness"
          },
          "completeness": {
            "type": "string",
            "const": "complete"
          }
        }
      },
      "ApmFreshness": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "watermark",
          "ingested_at"
        ],
        "properties": {
          "watermark": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ingested_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmTraceSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "traceId",
          "completeness"
        ],
        "properties": {
          "traceId": {
            "type": "string",
            "pattern": "^(?!0{32}$)[0-9a-f]{32}$"
          },
          "rootSpanName": {
            "type": [
              "string",
              "null"
            ]
          },
          "rootServiceName": {
            "type": [
              "string",
              "null"
            ]
          },
          "startTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "endTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "durationMs": {
            "type": [
              "number",
              "null"
            ]
          },
          "spanCount": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "errorCount": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ok",
              "error",
              null
            ]
          },
          "releaseId": {
            "type": [
              "string",
              "null"
            ]
          },
          "buildId": {
            "type": [
              "string",
              "null"
            ]
          },
          "samplingClass": {
            "type": [
              "string",
              "null"
            ]
          },
          "headSamplingDecision": {
            "type": [
              "string",
              "null"
            ]
          },
          "tailSamplingDecision": {
            "type": [
              "string",
              "null"
            ]
          },
          "samplingReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "retentionDecision": {
            "type": [
              "string",
              "null"
            ]
          },
          "headState": {
            "type": [
              "string",
              "null"
            ]
          },
          "tailState": {
            "type": [
              "string",
              "null"
            ]
          },
          "partial": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "late": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "completeness": {
            "type": [
              "string",
              "null"
            ]
          },
          "samplingPolicyId": {
            "type": [
              "string",
              "null"
            ]
          },
          "retentionPolicyVersion": {
            "type": [
              "integer",
              "null"
            ]
          },
          "freshnessWatermark": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ingestedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "schemaVersion": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "ApmTraceSpan": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "traceId",
          "spanId",
          "completeness"
        ],
        "properties": {
          "traceId": {
            "type": "string",
            "pattern": "^(?!0{32}$)[0-9a-f]{32}$"
          },
          "spanId": {
            "type": "string",
            "pattern": "^(?!0{16}$)[0-9a-f]{16}$"
          },
          "parentSpanId": {
            "type": [
              "string",
              "null"
            ]
          },
          "spanName": {
            "type": [
              "string",
              "null"
            ]
          },
          "serviceName": {
            "type": [
              "string",
              "null"
            ]
          },
          "spanKind": {
            "type": [
              "string",
              "null"
            ]
          },
          "startTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "endTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "durationMs": {
            "type": [
              "number",
              "null"
            ]
          },
          "statusCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "statusMessage": {
            "type": [
              "string",
              "null"
            ]
          },
          "releaseId": {
            "type": [
              "string",
              "null"
            ]
          },
          "buildId": {
            "type": [
              "string",
              "null"
            ]
          },
          "claimedRumLinkId": {
            "type": [
              "string",
              "null"
            ]
          },
          "confirmedRumLinkId": {
            "type": [
              "string",
              "null"
            ]
          },
          "correlationState": {
            "type": "string",
            "enum": [
              "confirmed",
              "claimed",
              "unavailable"
            ]
          },
          "completeness": {
            "type": [
              "string",
              "null"
            ]
          },
          "eventTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ingestedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmTraceSearchResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "available",
          "project_id",
          "environment_id",
          "items",
          "count",
          "truncated",
          "next_cursor",
          "partial",
          "completeness",
          "freshness"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "available": {
            "type": "boolean",
            "const": true
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "items": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/ApmTraceSummary"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "truncated": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "partial": {
            "type": "boolean"
          },
          "completeness": {
            "type": "string"
          },
          "freshness": {
            "$ref": "#/components/schemas/ApmFreshness"
          }
        }
      },
      "ApmTraceDetailResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "available",
          "project_id",
          "environment_id",
          "summary",
          "timeline",
          "count",
          "truncated",
          "partial",
          "completeness",
          "freshness",
          "parent_edges_complete"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "available": {
            "type": "boolean",
            "const": true
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "summary": {
            "$ref": "#/components/schemas/ApmTraceSummary"
          },
          "timeline": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/ApmTraceSpan"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "truncated": {
            "type": "boolean"
          },
          "partial": {
            "type": "boolean"
          },
          "completeness": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "summary",
              "timeline"
            ],
            "properties": {
              "summary": {
                "type": "string"
              },
              "timeline": {
                "type": "string"
              }
            }
          },
          "freshness": {
            "$ref": "#/components/schemas/ApmFreshness"
          },
          "parent_edges_complete": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "ApmServiceHealth": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "service_name",
          "trace_count",
          "error_trace_count",
          "error_rate",
          "p50_duration_ms",
          "p95_duration_ms",
          "completeness",
          "freshness_watermark"
        ],
        "properties": {
          "service_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "trace_count": {
            "type": [
              "number",
              "null"
            ]
          },
          "error_trace_count": {
            "type": [
              "number",
              "null"
            ]
          },
          "error_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "p50_duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "p95_duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "completeness": {
            "type": "string",
            "enum": [
              "partial",
              "unknown"
            ]
          },
          "freshness_watermark": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmDependencyHealth": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "source_service",
          "target_service",
          "protocol",
          "request_count",
          "error_count",
          "p50_duration_ms",
          "p95_duration_ms",
          "p99_duration_ms",
          "freshness_watermark"
        ],
        "properties": {
          "source_service": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_service": {
            "type": [
              "string",
              "null"
            ]
          },
          "protocol": {
            "type": [
              "string",
              "null"
            ]
          },
          "request_count": {
            "type": [
              "number",
              "null"
            ]
          },
          "error_count": {
            "type": [
              "number",
              "null"
            ]
          },
          "p50_duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "p95_duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "p99_duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "freshness_watermark": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmReleaseHealth": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "release_id",
          "build_id",
          "trace_count",
          "error_trace_count",
          "error_rate",
          "p50_duration_ms",
          "p95_duration_ms",
          "completeness",
          "freshness_watermark"
        ],
        "properties": {
          "release_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "build_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "trace_count": {
            "type": [
              "number",
              "null"
            ]
          },
          "error_trace_count": {
            "type": [
              "number",
              "null"
            ]
          },
          "error_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "p50_duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "p95_duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "completeness": {
            "type": "string",
            "enum": [
              "partial",
              "unknown"
            ]
          },
          "freshness_watermark": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmServiceListResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "available",
          "project_id",
          "environment_id",
          "items",
          "count",
          "truncated",
          "partial",
          "completeness",
          "freshness_watermark"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "available": {
            "type": "boolean",
            "const": true
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "items": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/ApmServiceHealth"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "truncated": {
            "type": "boolean"
          },
          "partial": {
            "type": "boolean"
          },
          "completeness": {
            "type": "string",
            "enum": [
              "partial",
              "unknown"
            ]
          },
          "freshness_watermark": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmServiceHealthResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "available",
          "partial",
          "reason",
          "project_id",
          "environment_id",
          "service_name",
          "health",
          "health_available",
          "items",
          "count",
          "dependencies_available",
          "truncated",
          "completeness",
          "freshness_watermark"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "available": {
            "type": "boolean",
            "const": true
          },
          "partial": {
            "type": "boolean"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "partial_warehouse_unavailable",
              "source_incomplete",
              null
            ]
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "service_name": {
            "type": "string"
          },
          "health": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ApmServiceHealth"
              },
              {
                "type": "null"
              }
            ]
          },
          "health_available": {
            "type": "boolean"
          },
          "items": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/ApmDependencyHealth"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "dependencies_available": {
            "type": "boolean"
          },
          "truncated": {
            "type": "boolean"
          },
          "completeness": {
            "type": "string",
            "enum": [
              "partial",
              "unknown"
            ]
          },
          "freshness_watermark": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmReleaseHealthResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "available",
          "project_id",
          "environment_id",
          "release_id",
          "items",
          "count",
          "truncated",
          "partial",
          "completeness",
          "freshness_watermark"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "available": {
            "type": "boolean",
            "const": true
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "release_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "items": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/ApmReleaseHealth"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "truncated": {
            "type": "boolean"
          },
          "partial": {
            "type": "boolean"
          },
          "completeness": {
            "type": "string",
            "enum": [
              "partial",
              "unknown"
            ]
          },
          "freshness_watermark": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmExportCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "project_id",
          "environment_id",
          "from",
          "to",
          "fact_names",
          "purpose"
        ],
        "properties": {
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "from": {
            "type": "string",
            "format": "date-time",
            "description": "Range start; interval must not exceed 31 days."
          },
          "to": {
            "type": "string",
            "format": "date-time",
            "description": "Range end after `from`; interval must not exceed 31 days."
          },
          "fact_names": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "apm_spans",
                "apm_trace_summaries",
                "rum_events",
                "rum_vital_facts",
                "rum_session_summaries",
                "apm_metric_points",
                "apm_log_records",
                "apm_service_edge_facts",
                "apm_error_occurrences",
                "apm_error_group_facts"
              ]
            }
          },
          "purpose": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._:/-]{0,127}$"
          },
          "expires_in_seconds": {
            "type": "integer",
            "minimum": 60,
            "maximum": 604800,
            "default": 3600
          }
        }
      },
      "ApmExportStatus": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "project_id",
          "environment_id",
          "export_manifest_id",
          "purpose",
          "fact_names",
          "state",
          "row_count",
          "byte_count",
          "created_at",
          "expires_at",
          "completed_at"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "export_manifest_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "purpose": {
            "type": "string"
          },
          "fact_names": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "state": {
            "type": "string",
            "description": "Asynchronous manifest state reported by the runtime; consumers must tolerate future states."
          },
          "row_count": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "byte_count": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmDownloadGrantRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "expires_in_seconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 300,
            "default": 60
          }
        }
      },
      "ApmPrivacyExportStatus": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "privacy_request_id",
          "project_id",
          "environment_id",
          "export_manifest_id",
          "purpose",
          "fact_names",
          "state",
          "row_count",
          "byte_count",
          "created_at",
          "expires_at",
          "completed_at"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "privacy_request_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "export_manifest_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "purpose": {
            "type": "string"
          },
          "fact_names": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "state": {
            "type": "string",
            "description": "Asynchronous manifest state reported by the runtime; consumers must tolerate future states."
          },
          "row_count": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "byte_count": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApmErasureStatus": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "privacy_request_id",
          "project_id",
          "environment_id",
          "state",
          "attempts",
          "created_at",
          "updated_at",
          "completed_at",
          "stores",
          "deleted"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "privacy_request_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "project_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "environment_id": {
            "$ref": "#/components/schemas/ApmScopedId"
          },
          "state": {
            "type": "string"
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "stores": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "count",
              "pending",
              "running",
              "succeeded",
              "failed"
            ],
            "properties": {
              "count": {
                "type": "integer",
                "minimum": 0
              },
              "pending": {
                "type": "integer",
                "minimum": 0
              },
              "running": {
                "type": "integer",
                "minimum": 0
              },
              "succeeded": {
                "type": "integer",
                "minimum": 0
              },
              "failed": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "deleted": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "rows",
              "objects",
              "bytes"
            ],
            "properties": {
              "rows": {
                "type": "integer",
                "minimum": 0
              },
              "objects": {
                "type": "integer",
                "minimum": 0
              },
              "bytes": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      },
      "OtlpAnyValue": {
        "type": "object",
        "description": "Proto3-JSON mapping of `opentelemetry.proto.common.v1.AnyValue` — exactly one property is present per the proto3 JSON oneof convention.",
        "properties": {
          "stringValue": {
            "type": "string"
          },
          "boolValue": {
            "type": "boolean"
          },
          "intValue": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Proto3-JSON int64 mapping; a decimal string once the value exceeds Number.isSafeInteger range."
          },
          "doubleValue": {
            "type": "number"
          },
          "arrayValue": {
            "type": "object",
            "properties": {
              "values": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OtlpAnyValue"
                }
              }
            }
          },
          "kvlistValue": {
            "type": "object",
            "properties": {
              "values": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OtlpKeyValue"
                }
              }
            }
          },
          "bytesValue": {
            "type": "string",
            "format": "byte",
            "description": "Base64-encoded (proto3-JSON `bytes` mapping)."
          }
        }
      },
      "OtlpKeyValue": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "value": {
            "$ref": "#/components/schemas/OtlpAnyValue"
          }
        }
      },
      "OtlpSpanEvent": {
        "type": "object",
        "properties": {
          "timeUnixNano": {
            "type": "string",
            "description": "Proto3-JSON int64 mapping (nanoseconds since the Unix epoch), encoded as a decimal string."
          },
          "name": {
            "type": "string"
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OtlpKeyValue"
            }
          },
          "droppedAttributesCount": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "OtlpSpanLink": {
        "type": "object",
        "properties": {
          "traceId": {
            "type": "string",
            "format": "byte",
            "description": "Base64-encoded 16-byte trace id (proto3-JSON `bytes` mapping)."
          },
          "spanId": {
            "type": "string",
            "format": "byte",
            "description": "Base64-encoded 8-byte span id (proto3-JSON `bytes` mapping)."
          },
          "traceState": {
            "type": "string"
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OtlpKeyValue"
            }
          },
          "droppedAttributesCount": {
            "type": "integer",
            "minimum": 0
          },
          "flags": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "OtlpSpan": {
        "type": "object",
        "description": "A single decoded span may still be dropped during post-decode validation — see `partialSuccess` on `OtlpTraceIngestResult` — so no property is `required` here; the wire format itself never rejects a well-formed but incomplete span.",
        "properties": {
          "traceId": {
            "type": "string",
            "format": "byte",
            "description": "Base64-encoded 16-byte trace id."
          },
          "spanId": {
            "type": "string",
            "format": "byte",
            "description": "Base64-encoded 8-byte span id."
          },
          "traceState": {
            "type": "string"
          },
          "parentSpanId": {
            "type": "string",
            "format": "byte",
            "description": "Base64-encoded 8-byte parent span id; absent for a root span."
          },
          "flags": {
            "type": "integer",
            "minimum": 0
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Proto3-JSON enum mapping — the OTLP SpanKind name (e.g. SPAN_KIND_SERVER) or its raw numeric value."
          },
          "startTimeUnixNano": {
            "type": "string"
          },
          "endTimeUnixNano": {
            "type": "string"
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OtlpKeyValue"
            }
          },
          "droppedAttributesCount": {
            "type": "integer",
            "minimum": 0
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OtlpSpanEvent"
            }
          },
          "droppedEventsCount": {
            "type": "integer",
            "minimum": 0
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OtlpSpanLink"
            }
          },
          "droppedLinksCount": {
            "type": "integer",
            "minimum": 0
          },
          "status": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "code": {
                "type": [
                  "string",
                  "integer"
                ],
                "description": "Proto3-JSON enum mapping — STATUS_CODE_UNSET / STATUS_CODE_OK / STATUS_CODE_ERROR, or its raw numeric value."
              }
            }
          }
        }
      },
      "OtlpScopeSpans": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              },
              "attributes": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OtlpKeyValue"
                }
              },
              "droppedAttributesCount": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "spans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OtlpSpan"
            }
          },
          "schemaUrl": {
            "type": "string"
          }
        }
      },
      "OtlpResourceSpans": {
        "type": "object",
        "properties": {
          "resource": {
            "type": "object",
            "properties": {
              "attributes": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OtlpKeyValue"
                }
              },
              "droppedAttributesCount": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "scopeSpans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OtlpScopeSpans"
            }
          },
          "schemaUrl": {
            "type": "string"
          }
        }
      },
      "OtlpExportTraceServiceRequest": {
        "type": "object",
        "description": "Proto3-JSON mapping of OTLP's `opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest` — the exact JSON shape an OpenTelemetry SDK or Collector's OTLP/HTTP JSON exporter already sends. The 500-span cap (`POST /v1/traces`'s 413 `apm_batch_too_large`) is counted across every `resourceSpans[].scopeSpans[].spans[]` in the whole request and is therefore not expressible as a single array's `maxItems`.",
        "properties": {
          "resourceSpans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OtlpResourceSpans"
            }
          }
        }
      },
      "OtlpTraceIngestResult": {
        "type": "object",
        "additionalProperties": false,
        "description": "Proto3-JSON mapping of OTLP's `ExportTraceServiceResponse`. An empty object means every span was accepted; `partialSuccess` is present only when one or more spans failed validation and were dropped — the whole request is never rejected once past authentication, decoding, and the batch-size cap.",
        "properties": {
          "partialSuccess": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "rejectedSpans"
            ],
            "properties": {
              "rejectedSpans": {
                "type": "string",
                "description": "Decimal count of rejected spans, encoded as a string (proto3-JSON int64 mapping)."
              },
              "errorMessage": {
                "type": "string"
              }
            }
          }
        }
      },
      "OtlpErrorDetail": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "@type",
          "reason",
          "domain",
          "metadata"
        ],
        "properties": {
          "@type": {
            "type": "string",
            "const": "type.googleapis.com/google.rpc.ErrorInfo"
          },
          "reason": {
            "type": "string",
            "enum": [
              "apm_auth_missing",
              "apm_auth_invalid",
              "apm_auth_expired",
              "apm_scope_denied",
              "apm_purpose_disabled",
              "apm_authority_unavailable",
              "apm_rate_limited",
              "apm_unsupported_media_type",
              "apm_body_too_large",
              "apm_malformed_otlp_json",
              "apm_malformed_otlp_protobuf",
              "apm_malformed_metrics_payload",
              "apm_malformed_logs_payload",
              "apm_malformed_profile_json",
              "apm_profile_storage_unavailable",
              "apm_batch_too_large",
              "apm_queue_unavailable"
            ],
            "description": "Union of every reason code any of the four OTLP-flavoured ingest routes (`POST /v1/traces`, `/v1/metrics`, `/v1/logs`, `/v1/profiles`) can return — not every operation returns every value (e.g. only `/v1/profiles` returns `apm_profile_storage_unavailable`; only `/v1/traces` returns `apm_malformed_otlp_json`/`apm_malformed_otlp_protobuf`)."
          },
          "domain": {
            "type": "string",
            "const": "ingest.noxtica.com"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "requestId",
              "retryable"
            ],
            "properties": {
              "requestId": {
                "type": "string"
              },
              "retryable": {
                "type": "string",
                "enum": [
                  "true",
                  "false"
                ]
              }
            }
          }
        }
      },
      "OtlpStatus": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "description": "The error body of the OTLP ingestion operations (`POST /v1/traces`, `/v1/metrics`, `/v1/logs` and `/v1/profiles`), distinct from the `{error, requestId}` envelope of every other operation. Two shapes exist and either can be returned: the collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason), and the dedicated ingest hosts answer `{code, message, error}` (`code` is the HTTP status and `error` the reason). The reason strings are the same. Branch on the HTTP status and the reason, never on `code`. `POST /v1/deployments` uses its own envelope (`ApmDeploymentError`).",
        "properties": {
          "code": {
            "type": "integer",
            "description": "On the collector hosts the gRPC status code mapped from the HTTP status (400->3 INVALID_ARGUMENT, 401->16 UNAUTHENTICATED, 403->7 PERMISSION_DENIED, 413->8 RESOURCE_EXHAUSTED, 415->3 INVALID_ARGUMENT, 429->8 RESOURCE_EXHAUSTED, 502 and 503->14 UNAVAILABLE); on the ingest hosts the HTTP status itself."
          },
          "message": {
            "type": "string"
          },
          "error": {
            "type": "string",
            "description": "The reason string on the ingest hosts (the same value as `details[0].reason` on the collector hosts)."
          },
          "details": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1,
            "items": {
              "$ref": "#/components/schemas/OtlpErrorDetail"
            },
            "description": "Present on the collector hosts only."
          }
        }
      },
      "OtlpExportMetricsServiceRequest": {
        "type": "object",
        "description": "Proto3-JSON mapping of OTLP's `opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest`. Modeled at the top-level `resourceMetrics` shape only, per this spec's OTLP-payload convention (see `OtlpExportTraceServiceRequest`'s own note) — each metric's own `gauge`/`sum`/`histogram`/ `exponentialHistogram`/`summary` data-point union is real proto3-JSON the collector decodes but is not exhaustively re-typed here. The 500-data-point cap (`POST /v1/metrics`'s 413 `apm_batch_too_large`) is counted across every data-point array in the whole request and is therefore not expressible as a single array's `maxItems`.",
        "properties": {
          "resourceMetrics": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "description": "One proto3-JSON `ResourceMetrics` entry (`resource`, `scopeMetrics[]`, `schemaUrl`) — see the OTLP metrics proto3-JSON schema for the full nested shape."
            }
          }
        }
      },
      "OtlpMetricsIngestResult": {
        "type": "object",
        "additionalProperties": false,
        "description": "Proto3-JSON mapping of OTLP's `ExportMetricsServiceResponse`. An empty object means every data point was accepted; `partialSuccess` is present only when one or more data points failed validation and were dropped — the whole request is never rejected once past authentication, decoding, and the batch-size cap.",
        "properties": {
          "partialSuccess": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "rejectedDataPoints"
            ],
            "properties": {
              "rejectedDataPoints": {
                "type": "string",
                "description": "Decimal count of rejected data points, encoded as a string (proto3-JSON int64 mapping)."
              },
              "errorMessage": {
                "type": "string"
              }
            }
          }
        }
      },
      "OtlpExportLogsServiceRequest": {
        "type": "object",
        "description": "Proto3-JSON mapping of OTLP's `opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest`. Modeled at the top-level `resourceLogs` shape only, per this spec's OTLP-payload convention (see `OtlpExportTraceServiceRequest`'s own note). The 500-log-record cap (`POST /v1/logs`'s 413 `apm_batch_too_large`) is counted across every `resourceLogs[].scopeLogs[].logRecords[]` in the whole request and is therefore not expressible as a single array's `maxItems`.",
        "properties": {
          "resourceLogs": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "description": "One proto3-JSON `ResourceLogs` entry (`resource`, `scopeLogs[]`, `schemaUrl`) — see the OTLP logs proto3-JSON schema for the full nested shape."
            }
          }
        }
      },
      "OtlpLogsIngestResult": {
        "type": "object",
        "additionalProperties": false,
        "description": "Proto3-JSON mapping of OTLP's `ExportLogsServiceResponse`. An empty object means every log record was accepted; `partialSuccess` is present only when one or more log records failed validation and were dropped — the whole request is never rejected once past authentication, decoding, and the batch-size cap.",
        "properties": {
          "partialSuccess": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "rejectedLogRecords"
            ],
            "properties": {
              "rejectedLogRecords": {
                "type": "string",
                "description": "Decimal count of rejected log records, encoded as a string (proto3-JSON int64 mapping)."
              },
              "errorMessage": {
                "type": "string"
              }
            }
          }
        }
      },
      "OtlpProfilesData": {
        "type": "object",
        "description": "Proto3-JSON mapping of OTLP profiling's `ProfilesData` container (`opentelemetry.proto.profiles.v1development.ProfilesData`) — profiling is still an OTel \"development\" signal, hence the bare data-container shape rather than an `ExportXServiceRequest` envelope like traces/metrics/logs use. Modeled at the top-level `resourceProfiles` shape only, per this spec's OTLP-payload convention (see `OtlpExportTraceServiceRequest`'s own note). The 20-profile cap (`POST /v1/profiles`'s 413 `apm_batch_too_large`) is counted across every `resourceProfiles[].scopeProfiles[].profiles[]` in the whole request and is therefore not expressible as a single array's `maxItems`.",
        "properties": {
          "resourceProfiles": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "description": "One proto3-JSON `ResourceProfiles` entry (`resource`, `scopeProfiles[]`) — see the OTLP profiles proto3-JSON schema for the full nested shape."
            }
          }
        }
      },
      "OtlpProfilesIngestResult": {
        "type": "object",
        "additionalProperties": false,
        "description": "An empty object means every profile was accepted and persisted; `partialSuccess` is present only when one or more profiles failed validation, or were stored with an unrecognized `profileType`, without failing the whole request.",
        "properties": {
          "partialSuccess": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "rejectedProfiles"
            ],
            "properties": {
              "rejectedProfiles": {
                "type": "string",
                "description": "Decimal count of rejected profiles, encoded as a string."
              },
              "errorMessage": {
                "type": "string"
              }
            }
          }
        }
      },
      "ApmDeploymentMarkerRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "service",
          "version",
          "environment"
        ],
        "description": "`POST /v1/deployments`'s own single-row REST body — never an OTLP batch.",
        "properties": {
          "service": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._:/-]{1,128}$"
          },
          "version": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_.:/-]{1,128}$",
            "description": "Also becomes the canonical spine's `release_id` — `release_id` equals the request `version`, byte-for-byte."
          },
          "environment": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._:-]{1,256}$",
            "description": "MUST equal the authenticated credential's own `environmentId` scope — a mismatch is rejected with 403 `apm_environment_mismatch` rather than redirecting the marker."
          },
          "commit": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9a-f]{7,64}$",
            "description": "Git commit SHA, 7-64 lowercase hex characters. Omit or send `null` if not applicable."
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Defaults to the server's receipt time if omitted. MUST be less than or equal to the receipt time — a deployment marker can never be timestamped in the future."
          }
        }
      },
      "ApmDeploymentMarkerAccepted": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "deployment_id",
          "service",
          "version",
          "environment",
          "commit",
          "timestamp"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "deployment_id": {
            "type": "string",
            "description": "Server-generated `dep_<uuid>` identifier."
          },
          "service": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "environment": {
            "type": "string"
          },
          "commit": {
            "type": [
              "string",
              "null"
            ]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApmDeploymentError": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "error",
          "message",
          "requestId"
        ],
        "description": "`POST /v1/deployments`'s own plain-JSON error envelope — distinct from the `google.rpc.Status`-shaped `OtlpStatus` the other three OTLP ingest routes plus `/v1/traces` share.",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "enum": [
              "apm_auth_missing",
              "apm_auth_invalid",
              "apm_auth_expired",
              "apm_scope_denied",
              "apm_purpose_disabled",
              "apm_authority_unavailable",
              "apm_rate_limited",
              "apm_unsupported_media_type",
              "apm_body_too_large",
              "apm_malformed_json",
              "apm_invalid_payload",
              "apm_environment_mismatch",
              "apm_queue_unavailable"
            ]
          },
          "message": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "description": "A webhook event type, exactly as it appears in the envelope `type`, the `X-Noxtica-Event` header and an endpoint's `events`.",
        "enum": [
          "kyc.session.created",
          "kyc.session.opened",
          "kyc.session.verifying",
          "kyc.session.review_pending",
          "kyc.session.completed",
          "kyc.session.unverified",
          "kyc.session.error",
          "kyc.session.expired",
          "kyc.session.cancelled",
          "kyc.review.decided",
          "kyc.verification.completed",
          "kyc.erasure.completed",
          "kyc.document.expiry_warning",
          "kyc.reverification.due",
          "kyb.session.created",
          "kyb.session.opened",
          "kyb.session.submitted",
          "kyb.session.under_review",
          "kyb.session.completed",
          "kyb.session.expired",
          "kyb.session.abandoned",
          "kyb.entity.created",
          "kyb.entity.under_review",
          "kyb.entity.closed",
          "kym.session.created",
          "kym.session.opened",
          "kym.session.submitted",
          "kym.session.under_review",
          "kym.session.completed",
          "kym.session.expired",
          "kym.session.abandoned",
          "kym.case.status_changed",
          "kym.alert.created",
          "submission.scored",
          "risk.flagged",
          "risk.high",
          "risk.cluster_flagged",
          "account.agent_linked",
          "pulse.detection.v1",
          "pulse.enrolment.linked.v1",
          "pulse.response.v1"
        ]
      },
      "WebhookApiError": {
        "type": "object",
        "description": "The error body of the webhook endpoint API. `error` is a closed machine code; `message` accompanies some of them.",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "retryAfter": {
            "type": "integer",
            "description": "Present on a `429`; equals the `Retry-After` header."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "description": "One webhook endpoint. The signing secret is never part of it.",
        "required": [
          "id",
          "url",
          "events",
          "description",
          "enabled",
          "secretSealed",
          "createdAt",
          "updatedAt",
          "lastDeliveryAt",
          "lastStatus"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The HTTPS endpoint Noxtica POSTs to."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "description": "The event types the endpoint receives. Exact match, no wildcard."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "enabled": {
            "type": "boolean",
            "description": "A disabled endpoint is kept but receives nothing."
          },
          "secretSealed": {
            "type": "boolean",
            "description": "`true` when the signing secret is stored encrypted, which the session and review events (`identity` events) require. `false` for an endpoint created before encrypted storage; rotate its secret once."
          },
          "createdAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms."
          },
          "updatedAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms."
          },
          "lastDeliveryAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Unix ms of the last delivery attempt, or `null`."
          },
          "lastStatus": {
            "type": [
              "string",
              "null"
            ],
            "description": "The result of the last attempt: `ok`, `http_<status>`, `blocked` (the URL was refused), `error`, `secret_plaintext_ineligible`; `null` before the first attempt."
          }
        }
      },
      "WebhookEndpointList": {
        "type": "object",
        "required": [
          "ok",
          "webhooks",
          "limit"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "webhooks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEndpoint"
            }
          },
          "limit": {
            "type": "integer",
            "description": "The most endpoints an organisation can register.",
            "example": 25
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "WebhookEndpointEnvelope": {
        "type": "object",
        "required": [
          "ok",
          "webhook"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "webhook": {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "WebhookEndpointWithSecret": {
        "type": "object",
        "description": "An endpoint with its signing secret, returned by `createWebhookEndpoint` and `rotateWebhookSecret` only.",
        "required": [
          "ok",
          "webhook",
          "secret"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "webhook": {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          "secret": {
            "type": "string",
            "description": "The signing secret. Shown once; store it now. The HMAC key is its UTF-8 bytes."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "WebhookEndpointDeleted": {
        "type": "object",
        "required": [
          "ok",
          "id",
          "removed"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "id": {
            "type": "string"
          },
          "removed": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "WebhookEndpointCreate": {
        "type": "object",
        "required": [
          "url",
          "events"
        ],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "HTTPS, no embedded credentials, not a private, loopback or internal address; not already registered."
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "description": "The event types to receive. Duplicates are ignored; the order you send is kept."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "A label for you."
          }
        }
      },
      "WebhookEndpointUpdate": {
        "type": "object",
        "description": "At least one member is required.",
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://"
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "description": "Replaces the whole list."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "WebhookTestRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "eventType": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookEventType"
              }
            ],
            "description": "The event to send a sample of. Default is the first event of the endpoint (`submission.scored` when it has none)."
          }
        }
      },
      "WebhookTestResult": {
        "type": "object",
        "required": [
          "ok",
          "delivered",
          "eventType",
          "deliveryId",
          "status",
          "result",
          "ms"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "The attempt was made."
          },
          "delivered": {
            "type": "boolean",
            "description": "`true` when your receiver answered with a `2xx`."
          },
          "eventType": {
            "type": "string"
          },
          "deliveryId": {
            "type": "string",
            "description": "The delivery id sent in `X-Noxtica-Delivery`; starts with `test_`."
          },
          "status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The HTTP status your receiver answered, or `null` when there was none (timeout, network error, refused URL)."
          },
          "result": {
            "type": "string",
            "description": "The outcome in the `lastStatus` vocabulary: `ok`, `http_<status>`, `blocked`, `error`, `secret_plaintext_ineligible`."
          },
          "ms": {
            "type": "integer",
            "description": "How long the attempt took, in milliseconds."
          },
          "error": {
            "type": "string",
            "enum": [
              "secret_plaintext_ineligible"
            ],
            "description": "Present when nothing was sent because the endpoint's secret is not sealed and the event needs one."
          },
          "message": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "description": "The outcome of one delivery of a KYC, KYB or KYM event to one endpoint.",
        "required": [
          "id",
          "eventType",
          "state",
          "attempts",
          "httpStatus",
          "lastError",
          "createdAt",
          "deliveredAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id; equals the `X-Noxtica-Delivery` header and the envelope `id`."
          },
          "eventType": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "state": {
            "type": "string",
            "enum": [
              "retrying",
              "delivered",
              "terminal",
              "dead"
            ],
            "description": "`retrying`: the last attempt failed and another is scheduled. `delivered`: your receiver answered with a `2xx`. `terminal`: refused for good (a `4xx`, a refused URL, or a secret that cannot sign this event). `dead`: the retry budget is spent."
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "httpStatus": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The HTTP status of the last attempt, or `null`."
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ],
            "description": "A closed code, `null` for a delivered row: `http_<status>`, `timeout`, `network_error`, `blocked_url`, `secret_plaintext_ineligible`, `retries_exhausted`."
          },
          "createdAt": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the delivery was first recorded."
          },
          "deliveredAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Unix ms of the successful attempt, or `null`."
          }
        }
      },
      "WebhookDeliveryList": {
        "type": "object",
        "required": [
          "ok",
          "deliveries",
          "nextCursor"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass it as `cursor` for the next page; `null` on the last page."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "WebhookEventTypeEntry": {
        "type": "object",
        "required": [
          "type",
          "product",
          "group",
          "label",
          "description",
          "schemaVersion",
          "dataType",
          "identity"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "product": {
            "type": "string",
            "enum": [
              "kyc",
              "kyb",
              "kym",
              "risk",
              "account",
              "pulse"
            ]
          },
          "group": {
            "type": "string",
            "description": "What the event is about inside its product: `session`, `verification`, `review`, `erasure`, `monitoring`, `entity`, `case`, `alert`, `scoring`, `agent`, `detection`, `link` or `response`."
          },
          "label": {
            "type": "string",
            "description": "A short English name."
          },
          "description": {
            "type": "string",
            "description": "One sentence saying when the event fires."
          },
          "schemaVersion": {
            "type": "string",
            "enum": [
              "1"
            ],
            "description": "The version of the `data` member."
          },
          "dataType": {
            "type": "string",
            "description": "The name of the schema of the `data` member."
          },
          "identity": {
            "type": "boolean",
            "description": "`true` when the event is delivered only to an endpoint with a sealed signing secret."
          }
        }
      },
      "WebhookEventTypesResponse": {
        "type": "object",
        "required": [
          "ok",
          "products",
          "eventTypes"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "products": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "kyc",
                "kyb",
                "kym",
                "risk",
                "account",
                "pulse"
              ]
            }
          },
          "eventTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventTypeEntry"
            }
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "v1_ErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error string; route-specific values are listed on each operation."
          },
          "requestId": {
            "type": "string",
            "description": "Correlation id for this call; quote it when you contact support."
          }
        }
      },
      "InvalidApiKeyError": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_api_key",
              "server_misconfigured"
            ]
          },
          "message": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "ScopeForbiddenError": {
        "type": "object",
        "required": [
          "error",
          "requestId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Always \"API key does not have <scope> scope\", naming the missing scope."
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "NotFoundError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "Not found"
            ]
          }
        }
      },
      "HealthStatus": {
        "type": "object",
        "required": [
          "status",
          "version",
          "timestamp"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded"
            ]
          },
          "version": {
            "type": "string",
            "description": "The deployed collector version, or `unknown`."
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Server time of the response."
          }
        }
      },
      "Jwks": {
        "type": "object",
        "required": [
          "keys"
        ],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "kty",
                "crv",
                "x",
                "y",
                "kid",
                "use",
                "alg"
              ],
              "properties": {
                "kty": {
                  "type": "string",
                  "enum": [
                    "EC"
                  ]
                },
                "crv": {
                  "type": "string",
                  "enum": [
                    "P-256"
                  ]
                },
                "x": {
                  "type": "string",
                  "description": "base64url X coordinate."
                },
                "y": {
                  "type": "string",
                  "description": "base64url Y coordinate."
                },
                "kid": {
                  "type": "string",
                  "description": "Key id; matches the `kid` of the envelopes signed with it."
                },
                "use": {
                  "type": "string",
                  "enum": [
                    "sig"
                  ]
                },
                "alg": {
                  "type": "string",
                  "enum": [
                    "ES256"
                  ]
                }
              }
            }
          }
        }
      },
      "CodingAgentError": {
        "type": "object",
        "required": [
          "code",
          "message",
          "error"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "description": "The HTTP status code."
          },
          "message": {
            "type": "string"
          },
          "error": {
            "type": "string",
            "description": "Machine-readable reason; branch on it, not on `message`."
          }
        }
      },
      "CodingAgentSignal": {
        "type": "object",
        "required": [
          "signal",
          "reference_id",
          "excerpt",
          "observed_at"
        ],
        "properties": {
          "signal": {
            "type": "string",
            "description": "The key scope the row came from, for example `rum_errors` or `apm_traces`."
          },
          "reference_id": {
            "type": "string",
            "description": "The id of the source record (event, trace, error group, metric series)."
          },
          "excerpt": {
            "type": "string",
            "description": "A short human-readable summary of the record."
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          },
          "metric_value": {
            "type": "number",
            "description": "Present when the record carries a numeric value."
          }
        }
      },
      "CodingAgentSpan": {
        "type": "object",
        "required": [
          "span_id",
          "service_name",
          "operation_name",
          "start_time",
          "end_time",
          "duration_ns",
          "status_code"
        ],
        "properties": {
          "span_id": {
            "type": "string"
          },
          "parent_span_id": {
            "type": "string"
          },
          "service_name": {
            "type": "string"
          },
          "operation_name": {
            "type": "string"
          },
          "start_time": {
            "type": "string",
            "format": "date-time"
          },
          "end_time": {
            "type": "string",
            "format": "date-time"
          },
          "duration_ns": {
            "type": "integer",
            "format": "int64"
          },
          "status_code": {
            "type": "string"
          },
          "status_message": {
            "type": "string"
          }
        }
      },
      "CodingAgentTrace": {
        "type": "object",
        "required": [
          "trace_id",
          "spans"
        ],
        "properties": {
          "trace_id": {
            "type": "string"
          },
          "spans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CodingAgentSpan"
            }
          }
        }
      },
      "CodingAgentErrorOccurrence": {
        "type": "object",
        "required": [
          "occurrence_id",
          "exception_type",
          "observed_at"
        ],
        "properties": {
          "occurrence_id": {
            "type": "string"
          },
          "exception_type": {
            "type": "string"
          },
          "exception_message": {
            "type": "string"
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          },
          "release_id": {
            "type": "string"
          }
        }
      },
      "CodingAgentErrorGroup": {
        "type": "object",
        "required": [
          "error_group_id",
          "first_seen",
          "last_seen",
          "occurrence_count",
          "affected_release_ids",
          "occurrences"
        ],
        "properties": {
          "error_group_id": {
            "type": "string"
          },
          "first_seen": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen": {
            "type": "string",
            "format": "date-time"
          },
          "occurrence_count": {
            "type": "integer",
            "format": "int64"
          },
          "affected_release_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "occurrences": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CodingAgentErrorOccurrence"
            }
          }
        }
      },
      "VerdictWebhookData": {
        "type": "object",
        "description": "The verdict of one scored submission. Never carries a raw IP address or personal data.",
        "required": [
          "device_id",
          "domain_id",
          "event_id"
        ],
        "properties": {
          "score": {
            "type": "number",
            "description": "The numeric risk score."
          },
          "risk_level": {
            "type": "string",
            "description": "The verdict band, for example `low`, `medium`, `high` or `critical`."
          },
          "confidence": {
            "type": "number",
            "description": "Model confidence from 0 to 1."
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The risk flags that fired."
          },
          "reason_codes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The published reason-code projection of `flags`; empty unless reason-code output is enabled for your organisation."
          },
          "device_id": {
            "type": "string",
            "description": "The opaque device id. An empty string means that no stable per-device identity is available for this event."
          },
          "domain_id": {
            "type": "string",
            "description": "Your domain id."
          },
          "event_id": {
            "type": "string",
            "description": "The request id of the submission or attestation; always present."
          }
        }
      },
      "VerdictWebhookEvent": {
        "type": "object",
        "description": "The JSON body of a verdict webhook delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id; equals `X-Noxtica-Delivery`."
          },
          "type": {
            "type": "string",
            "enum": [
              "submission.scored",
              "risk.high",
              "risk.flagged",
              "risk.cluster_flagged"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event was produced."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/VerdictWebhookData"
          }
        }
      },
      "AgentLinkedWebhookData": {
        "type": "object",
        "description": "The first link between a verified agent and one of your accounts. Never carries a raw account id, IP address or personal data.",
        "required": [
          "account_hash",
          "agent_keyid",
          "agent_host",
          "device_id",
          "domain_id",
          "event_id"
        ],
        "properties": {
          "account_hash": {
            "type": "string",
            "description": "The keyed 16-character lowercase-hex account pseudonym."
          },
          "agent_keyid": {
            "type": "string",
            "description": "The verified agent's `Signature-Input` key id."
          },
          "agent_host": {
            "type": [
              "string",
              "null"
            ],
            "description": "The verified agent's directory host, or `null`."
          },
          "device_id": {
            "type": "string"
          },
          "domain_id": {
            "type": "string"
          },
          "event_id": {
            "type": "string"
          }
        }
      },
      "AgentLinkedWebhookEvent": {
        "type": "object",
        "description": "The JSON body of an `account.agent_linked` delivery.",
        "required": [
          "id",
          "type",
          "created_at",
          "tenant_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The delivery id; equals `X-Noxtica-Delivery`."
          },
          "type": {
            "type": "string",
            "enum": [
              "account.agent_linked"
            ]
          },
          "created_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix ms the event was produced."
          },
          "tenant_id": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/AgentLinkedWebhookData"
          }
        }
      }
    }
  }
}
