{
  "openapi": "3.1.0",
  "info": {
    "title": "TrustWix API",
    "version": "0.1.0",
    "description": "API-first, webhook-driven identity-verification platform. Composable checks (liveness, face match, document, age, AML) composed into flows. Authenticate with a secret API key (Bearer). Test vs live keys select sandbox vs live mode."
  },
  "servers": [
    {
      "url": "https://api.trustwix.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "secretKey": []
    }
  ],
  "tags": [
    {
      "name": "Verifications",
      "description": "Create verification sessions, read their status and verdict, and upload the captured artefacts."
    },
    {
      "name": "Flows",
      "description": "Saved compositions of checks that a verification can reference by id."
    },
    {
      "name": "Webhooks",
      "description": "Endpoints that verification events are delivered to, signed per endpoint."
    },
    {
      "name": "API keys",
      "x-displayName": "API keys",
      "description": "The secret keys your servers authenticate with, and their permissions."
    },
    {
      "name": "Applicants",
      "description": "Read-only applicant records. Non-sensitive metadata only, never applicant PII."
    },
    {
      "name": "Manual review",
      "description": "The queue of sessions a human needs to decide, and the decisions taken on them."
    },
    {
      "name": "Consent",
      "description": "Record the end user consent captured before biometric processing begins."
    },
    {
      "name": "Account",
      "description": "Your own merchant profile, the entitlements of the calling key, and usage."
    },
    {
      "name": "System",
      "description": "Health and meta."
    }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "tags": [
          "System"
        ],
        "operationId": "healthCheck",
        "summary": "Health check",
        "description": "Unauthenticated liveness probe. Returns as soon as the service can serve traffic.",
        "security": [],
        "responses": {
          "200": {
            "description": "Service healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok"
                      ]
                    },
                    "service": {
                      "type": "string"
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "status",
                    "service",
                    "time"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/verifications": {
      "post": {
        "tags": [
          "Verifications"
        ],
        "operationId": "createVerification",
        "summary": "Create a verification session",
        "description": "Start a verification. Provide exactly one of `flow` (a saved flow id) or an inline `checks` array. Returns a session with a short-lived `client_token` and a `hosted_verify_url`. In sandbox mode, magic applicant inputs (approved/rejected/review/pending) force the verdict.\n\nRequires the `verification:create` permission on the calling key. A key without it gets a 403. This is the one endpoint that honours `Idempotency-Key`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/RequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateVerificationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotency-Replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                },
                "description": "true when the response was replayed from a prior identical request."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "tags": [
          "Verifications"
        ],
        "operationId": "listVerifications",
        "summary": "List verification sessions",
        "description": "Cursor-paginated, newest first. `limit` accepts 1 to 100 and defaults to 20. To walk further, set `starting_after` to the id of the last record on the page you just read. `has_more` tells you another page exists and `next_cursor` is the value to pass back; on the last page `has_more` is false and `next_cursor` is null. An unknown cursor is ignored and you get the first page. Requires the `verification:read` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^vs_"
            },
            "description": "A verification id; returns the page after it."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/VerificationStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/verifications/{id}": {
      "get": {
        "tags": [
          "Verifications"
        ],
        "operationId": "retrieveVerification",
        "summary": "Retrieve a verification session",
        "description": "Fetch the current status, verdict and per-check results for one session. `client_token` is only populated on create, so it is null here. Requires the `verification:read` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/VerificationId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/verifications/{id}/uploads": {
      "post": {
        "tags": [
          "Verifications"
        ],
        "operationId": "createVerificationUpload",
        "summary": "Create an upload URL",
        "description": "Step one of the two-step upload. Reserves an upload against the session and returns a short-lived presigned `upload_url`. PUT the raw bytes straight to that URL with the same `Content-Type` you declared, then call the complete endpoint. The bytes never pass through this API.\n\nSupported `content_type` values are `image/jpeg`, `image/jpg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `application/pdf`, `video/mp4`, `video/webm` and `video/quicktime`. Anything else is rejected with 400 `UNSUPPORTED_CONTENT_TYPE`.\n\nRequires the `verification:create` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/VerificationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUploadRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload reserved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadUrl"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/verifications/{id}/uploads/{uploadId}/complete": {
      "post": {
        "tags": [
          "Verifications"
        ],
        "operationId": "completeVerificationUpload",
        "summary": "Complete an upload",
        "description": "Step two of the two-step upload. Confirms the presigned PUT finished and moves the upload from `pending` to `uploaded`. The object must already be in storage, otherwise you get 400 `UPLOAD_NOT_RECEIVED`. Calling it again on an already completed upload is safe and returns the same record.\n\nTakes no request body. Requires the `verification:create` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/VerificationId"
          },
          {
            "name": "uploadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(doc|bio)_"
            },
            "description": "The `upload_id` returned when the upload URL was created."
          }
        ],
        "responses": {
          "200": {
            "description": "Upload completed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/flows": {
      "get": {
        "tags": [
          "Flows"
        ],
        "operationId": "listFlows",
        "summary": "List flows",
        "description": "Your saved flows. Cursor-paginated, newest first. `limit` accepts 1 to 100 and defaults to 20. To walk further, set `starting_after` to the id of the last record on the page you just read. `has_more` tells you another page exists and `next_cursor` is the value to pass back; on the last page `has_more` is false and `next_cursor` is null. An unknown cursor is ignored and you get the first page. Requires the `settings:read` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^flw_"
            },
            "description": "A flow id; returns the page after it."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Flows"
        ],
        "operationId": "createFlow",
        "summary": "Create a flow",
        "description": "Save an ordered composition of checks so verifications can reference it by id instead of repeating an inline `checks` array. Requires the `settings:write` permission on the calling key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFlowRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Flow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/flows/{id}": {
      "get": {
        "tags": [
          "Flows"
        ],
        "operationId": "retrieveFlow",
        "summary": "Retrieve a flow",
        "description": "Requires the `settings:read` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/FlowId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Flow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Flows"
        ],
        "operationId": "updateFlow",
        "summary": "Update a flow",
        "description": "Update the name, the steps or the default marker. Send at least one field. `steps` is replaced wholesale, not merged. Sessions already created keep the steps that were snapshotted when they were created. Requires the `settings:write` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/FlowId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateFlowRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Flow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Flows"
        ],
        "operationId": "deleteFlow",
        "summary": "Delete a flow",
        "description": "Permanently removes the flow. Verifications that already referenced it are unaffected. Requires the `settings:write` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/FlowId"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedFlow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookEndpoints",
        "summary": "List webhook endpoints",
        "description": "Your endpoints. The signing secret is never returned here, only once at creation. Cursor-paginated, newest first. `limit` accepts 1 to 100 and defaults to 20. To walk further, set `starting_after` to the id of the last record on the page you just read. `has_more` tells you another page exists and `next_cursor` is the value to pass back; on the last page `has_more` is false and `next_cursor` is null. An unknown cursor is ignored and you get the first page. Requires the `webhook:manage` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^whe_"
            },
            "description": "A webhook endpoint id; returns the page after it."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhookEndpoint",
        "summary": "Create a webhook endpoint",
        "description": "Register a URL to receive events. The response carries `signing_secret` exactly once: store it now, because it is not recoverable afterwards and no later call will return it. Use it to verify the signature on every delivery.\n\nRequires the `webhook:manage` permission on the calling key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookEndpointRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created, with the signing secret",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhookEndpoint",
        "summary": "Delete a webhook endpoint",
        "description": "Permanently removes the endpoint and stops future deliveries to it. Requires the `webhook:manage` permission on the calling key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^whe_"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedWebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/api-keys": {
      "get": {
        "tags": [
          "API keys"
        ],
        "operationId": "listApiKeys",
        "summary": "List API keys",
        "description": "Metadata for your keys. Only the display prefix is returned, never the secret. Cursor-paginated, newest first. `limit` accepts 1 to 100 and defaults to 20. To walk further, set `starting_after` to the id of the last record on the page you just read. `has_more` tells you another page exists and `next_cursor` is the value to pass back; on the last page `has_more` is false and `next_cursor` is null. An unknown cursor is ignored and you get the first page. Key ids are UUIDs, so `starting_after` takes a UUID here rather than a prefixed id. Requires the `settings:read` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "An API key id; returns the page after it."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "API keys"
        ],
        "operationId": "createApiKey",
        "summary": "Create an API key",
        "description": "Mint a new secret key. The response carries `secret_key` exactly once: it is stored hashed and is not recoverable, so capture it now. `environment` decides whether the key runs in sandbox (`test`) or live. Omitting `permissions` gives the key the default permission set.\n\nRequires the `settings:write` permission on the calling key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created, with the secret key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/api-keys/{id}": {
      "delete": {
        "tags": [
          "API keys"
        ],
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key",
        "description": "Revokes the key immediately. The row is kept for audit, so the response is the key resource with `status` set to `revoked` and `revoked_at` populated, not a deletion marker. A revoked key can never be reactivated. Revoking the key you are calling with will lock you out.\n\nRequires the `settings:write` permission on the calling key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The key id. API key ids are UUIDs, not prefixed ids."
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKey"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/applicants": {
      "get": {
        "tags": [
          "Applicants"
        ],
        "operationId": "listApplicants",
        "summary": "List applicants",
        "description": "Your applicant records. Applicant data is not readable through this API: only non-sensitive metadata is returned. Cursor-paginated, newest first. `limit` accepts 1 to 100 and defaults to 20. To walk further, set `starting_after` to the id of the last record on the page you just read. `has_more` tells you another page exists and `next_cursor` is the value to pass back; on the last page `has_more` is false and `next_cursor` is null. An unknown cursor is ignored and you get the first page. Requires the `verification:read` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^app_"
            },
            "description": "An applicant id; returns the page after it."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApplicantList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/applicants/{id}": {
      "get": {
        "tags": [
          "Applicants"
        ],
        "operationId": "retrieveApplicant",
        "summary": "Retrieve an applicant",
        "description": "Non-sensitive metadata only. Requires the `verification:read` permission on the calling key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^app_"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Applicant"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/manual-reviews": {
      "get": {
        "tags": [
          "Manual review"
        ],
        "operationId": "listManualReviews",
        "summary": "List manual reviews",
        "description": "Reviews raised on your sessions. Filter with `status` to see only what is still open. The response carries ids and decisions, never applicant data. Cursor-paginated, newest first. `limit` accepts 1 to 100 and defaults to 20. To walk further, set `starting_after` to the id of the last record on the page you just read. `has_more` tells you another page exists and `next_cursor` is the value to pass back; on the last page `has_more` is false and `next_cursor` is null. An unknown cursor is ignored and you get the first page. Review ids are UUIDs, so `starting_after` takes a UUID here rather than a prefixed id. Requires the `verification:read` permission on the calling key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "A review id; returns the page after it."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ManualReviewStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManualReviewList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/manual-reviews/{id}/decision": {
      "post": {
        "tags": [
          "Manual review"
        ],
        "operationId": "decideManualReview",
        "summary": "Decide a manual review",
        "description": "Record where a reviewer landed. `approve` and `reject` close the review: `status` becomes `resolved` and `reviewed_at` is stamped. `review` is not an outcome, it means the reviewer wants another look, so the item stays open at `status` `in_review` with `reviewed_at` still null and keeps appearing in the queue. Submitting a decision again overwrites the previous one, including reopening a resolved review by deciding `review` on it.\n\nRequires the `verification:create` permission on the calling key, the same write capability that starts a verification, not `verification:read` as the listing does.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The review id. Review ids are UUIDs, not prefixed ids."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ManualReviewDecisionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decision recorded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManualReview"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/consent": {
      "post": {
        "tags": [
          "Consent"
        ],
        "operationId": "createConsentRecord",
        "summary": "Record consent",
        "description": "Write an audit record of the consent an end user gave for a session you own. Capture it before any biometric processing begins. The record stores the granted scopes, the version of the policy that was shown, and the request context. The caller IP is taken from the request itself and cannot be supplied in the body.\n\nRequires the `verification:create` permission on the calling key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateConsentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConsentRecord"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/merchant": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "retrieveMerchant",
        "summary": "Retrieve your merchant profile",
        "description": "Your own account. There is no way to read another account on this API. `mode` is the go-live entitlement on the account, which is separate from the sandbox or live mode a given key runs in. Requires the `settings:read` permission on the calling key.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Merchant"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/entitlements": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "retrieveEntitlements",
        "summary": "Retrieve the calling key entitlements",
        "description": "What the key in the Authorization header can actually do: which mode it runs in, which engine tier it is entitled to, and which permissions it carries. Use it to predict a 403 before you hit one. Requires the `settings:read` permission on the calling key.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Entitlements"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "retrieveUsage",
        "summary": "Retrieve a usage summary",
        "description": "Counts of metered operations, grouped by operation type, for your account. Pass `since` to bound the window; without it the summary covers everything on record. Requires the `analytics:read` permission on the calling key, which is not part of the default permission set.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339 lower bound for the summary window."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageSummary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "secretKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Secret API key, e.g. `kyc_live_...` (server-side) or `kyc_test_...` (sandbox)."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "description": "Retries with the same key + body replay the original response."
      },
      "RequestId": {
        "name": "X-Request-Id",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Optional client-supplied correlation id; echoed back."
      },
      "PageLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        },
        "description": "How many records to return. 1 to 100, defaults to 20."
      },
      "VerificationId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^vs_"
        },
        "description": "The verification session id."
      },
      "FlowId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^flw_"
        },
        "description": "The flow id."
      }
    },
    "headers": {
      "XRequestId": {
        "schema": {
          "type": "string"
        },
        "description": "Per-request correlation id."
      }
    },
    "schemas": {
      "CheckType": {
        "type": "string",
        "enum": [
          "liveness",
          "face_match",
          "document",
          "age_estimation",
          "aml_screening",
          "proof_of_address"
        ]
      },
      "CheckStatus": {
        "type": "string",
        "enum": [
          "pending",
          "processing",
          "completed",
          "failed"
        ]
      },
      "Verdict": {
        "type": "string",
        "enum": [
          "approve",
          "reject",
          "review"
        ]
      },
      "VerificationStatus": {
        "type": "string",
        "enum": [
          "pending",
          "processing",
          "approved",
          "rejected",
          "manual_review",
          "expired"
        ],
        "description": "Session lifecycle. pending → processing → terminal (approved/rejected/manual_review) or expired. Roll-up: any required check reject ⇒ rejected; else any review ⇒ manual_review; else approved."
      },
      "Mode": {
        "type": "string",
        "enum": [
          "sandbox",
          "live"
        ],
        "description": "Sandbox for test keys, live for live keys."
      },
      "Environment": {
        "type": "string",
        "enum": [
          "live",
          "test"
        ],
        "description": "The key environment. `test` keys run in sandbox mode."
      },
      "EngineTier": {
        "type": "string",
        "enum": [
          "standard",
          "pro",
          "pro_max"
        ],
        "description": "The verification engine tier your account is entitled to."
      },
      "ApiKeyPermission": {
        "type": "string",
        "enum": [
          "verification:create",
          "verification:read",
          "verification:delete",
          "webhook:manage",
          "analytics:read",
          "settings:read",
          "settings:write"
        ],
        "description": "A capability carried by a key. Calling an endpoint without the permission it requires returns 403 `INSUFFICIENT_PERMISSIONS`."
      },
      "FlowStep": {
        "type": "object",
        "description": "One composable check in a flow.",
        "properties": {
          "type": {
            "$ref": "#/components/schemas/CheckType"
          },
          "required": {
            "type": "boolean",
            "default": true,
            "description": "When false, a reject on this check alone will not reject the verification."
          },
          "threshold": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Sensitivity; clamped to platform guardrails server-side."
          },
          "config": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "type"
        ]
      },
      "CreateVerificationRequest": {
        "type": "object",
        "description": "Provide exactly one of `flow` or `checks`.",
        "properties": {
          "flow": {
            "type": "string",
            "pattern": "^flw_",
            "description": "A saved flow id."
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowStep"
            },
            "minItems": 1
          },
          "locale": {
            "type": "string",
            "minLength": 2,
            "maxLength": 10,
            "description": "BCP-47 locale for the hosted flow."
          },
          "return_url": {
            "type": "string",
            "format": "uri"
          },
          "applicant": {
            "type": "object",
            "properties": {
              "reference_id": {
                "type": "string",
                "maxLength": 200
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Check": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^chk_"
          },
          "object": {
            "type": "string",
            "enum": [
              "check"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/CheckType"
          },
          "status": {
            "$ref": "#/components/schemas/CheckStatus"
          },
          "verdict": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Verdict"
              },
              {
                "type": "null"
              }
            ]
          },
          "provider_tier": {
            "$ref": "#/components/schemas/EngineTier"
          },
          "reason_codes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "UPPER_SNAKE codes, e.g. LIVENESS_FAILED, MRZ_CHECKSUM_INVALID."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "status",
          "provider_tier",
          "reason_codes",
          "created_at",
          "updated_at"
        ]
      },
      "Verification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^vs_"
          },
          "object": {
            "type": "string",
            "enum": [
              "verification"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/VerificationStatus"
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          },
          "flow": {
            "type": [
              "string",
              "null"
            ],
            "description": "The flow id this session referenced, or null for inline checks."
          },
          "verdict": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Verdict"
              },
              {
                "type": "null"
              }
            ]
          },
          "reason_codes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Check"
            }
          },
          "client_token": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short-lived browser/SDK token; present only on create."
          },
          "hosted_verify_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "locale": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "status",
          "mode",
          "verdict",
          "reason_codes",
          "checks",
          "created_at",
          "updated_at"
        ]
      },
      "VerificationList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Verification"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "CreateUploadRequest": {
        "type": "object",
        "description": "Reserve one captured artefact against the session. `side` is only accepted when `kind` is `document`.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "document",
              "selfie",
              "liveness"
            ]
          },
          "side": {
            "type": "string",
            "enum": [
              "front",
              "back"
            ]
          },
          "content_type": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "The MIME type you will PUT. It is baked into the presigned URL."
          }
        },
        "required": [
          "kind",
          "content_type"
        ]
      },
      "UploadUrl": {
        "type": "object",
        "description": "The presigned target for a single artefact.",
        "properties": {
          "upload_id": {
            "type": "string",
            "pattern": "^(doc|bio)_"
          },
          "key": {
            "type": "string",
            "description": "The storage object key the artefact lands under."
          },
          "upload_url": {
            "type": "string",
            "format": "uri",
            "description": "Presigned PUT target. Short-lived, single artefact, do not log it."
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds until `upload_url` stops working."
          }
        },
        "required": [
          "upload_id",
          "key",
          "upload_url",
          "expires_in"
        ]
      },
      "Upload": {
        "type": "object",
        "description": "A captured artefact attached to a verification session.",
        "properties": {
          "upload_id": {
            "type": "string",
            "pattern": "^(doc|bio)_"
          },
          "object": {
            "type": "string",
            "enum": [
              "upload"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "document",
              "selfie",
              "liveness"
            ]
          },
          "side": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "front",
              "back",
              null
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "uploaded"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "upload_id",
          "object",
          "kind",
          "side",
          "status",
          "created_at"
        ]
      },
      "CreateFlowRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowStep"
            },
            "minItems": 1
          },
          "is_default": {
            "type": "boolean",
            "default": false,
            "description": "Marks this flow as your default. Purely a marker, it is not auto-applied."
          }
        },
        "required": [
          "name",
          "steps"
        ]
      },
      "UpdateFlowRequest": {
        "type": "object",
        "description": "Send at least one field. `steps` replaces the stored array wholesale.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowStep"
            },
            "minItems": 1
          },
          "is_default": {
            "type": "boolean"
          }
        },
        "minProperties": 1
      },
      "Flow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^flw_"
          },
          "object": {
            "type": "string",
            "enum": [
              "flow"
            ]
          },
          "name": {
            "type": "string"
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowStep"
            }
          },
          "is_default": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "steps",
          "is_default",
          "created_at",
          "updated_at"
        ]
      },
      "FlowList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Flow"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "DeletedFlow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^flw_"
          },
          "object": {
            "type": "string",
            "enum": [
              "flow"
            ]
          },
          "deleted": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "id",
          "object",
          "deleted"
        ]
      },
      "CreateWebhookEndpointRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS target for deliveries."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "minItems": 1,
            "description": "Event names to subscribe to, e.g. `verification.completed`. Unknown names are accepted, so check for typos."
          }
        },
        "required": [
          "url",
          "events"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^whe_"
          },
          "object": {
            "type": "string",
            "enum": [
              "webhook_endpoint"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "url",
          "events",
          "status",
          "created_at"
        ]
      },
      "WebhookEndpointCreated": {
        "type": "object",
        "description": "The create response, and the only place `signing_secret` ever appears.",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^whe_"
          },
          "object": {
            "type": "string",
            "enum": [
              "webhook_endpoint"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "signing_secret": {
            "type": "string",
            "description": "Returned once, never again. Verify every delivery signature with it."
          }
        },
        "required": [
          "id",
          "object",
          "url",
          "events",
          "status",
          "created_at",
          "signing_secret"
        ]
      },
      "WebhookEndpointList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEndpoint"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "DeletedWebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^whe_"
          },
          "object": {
            "type": "string",
            "enum": [
              "webhook_endpoint"
            ]
          },
          "deleted": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "id",
          "object",
          "deleted"
        ]
      },
      "CreateApiKeyRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "environment": {
            "$ref": "#/components/schemas/Environment"
          },
          "permissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKeyPermission"
            },
            "description": "Omit to get the default permission set."
          }
        },
        "required": [
          "name",
          "environment"
        ]
      },
      "ApiKey": {
        "type": "object",
        "description": "Key metadata. The secret itself is stored hashed and is never returned here.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "api_key"
            ]
          },
          "name": {
            "type": "string"
          },
          "environment": {
            "$ref": "#/components/schemas/Environment"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked",
              "expired"
            ]
          },
          "key_prefix": {
            "type": "string",
            "description": "The non-secret display prefix, safe to show in a dashboard or a log."
          },
          "permissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKeyPermission"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "environment",
          "status",
          "key_prefix",
          "permissions",
          "created_at",
          "last_used_at",
          "expires_at",
          "revoked_at"
        ]
      },
      "ApiKeyCreated": {
        "type": "object",
        "description": "The create response, and the only place `secret_key` ever appears.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "api_key"
            ]
          },
          "name": {
            "type": "string"
          },
          "environment": {
            "$ref": "#/components/schemas/Environment"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked",
              "expired"
            ]
          },
          "key_prefix": {
            "type": "string"
          },
          "permissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKeyPermission"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "secret_key": {
            "type": "string",
            "description": "The full secret. Returned once, never recoverable afterwards."
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "environment",
          "status",
          "key_prefix",
          "permissions",
          "created_at",
          "last_used_at",
          "expires_at",
          "revoked_at",
          "secret_key"
        ]
      },
      "ApiKeyList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "Applicant": {
        "type": "object",
        "description": "Non-sensitive applicant metadata. Applicant data itself is never returned by this API.",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^app_"
          },
          "object": {
            "type": "string",
            "enum": [
              "applicant"
            ]
          },
          "has_identity_link": {
            "type": "boolean",
            "description": "Whether this applicant has been linked to a known identity. Presence only, the link itself is not exposed."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "has_identity_link",
          "created_at",
          "updated_at"
        ]
      },
      "ApplicantList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Applicant"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "ManualReviewStatus": {
        "type": "string",
        "enum": [
          "queued",
          "in_review",
          "resolved"
        ],
        "description": "Where the review sits. `queued` is waiting to be picked up, `in_review` is open with a reviewer on it, `resolved` is closed by an approve or a reject."
      },
      "ManualReviewDecisionRequest": {
        "type": "object",
        "properties": {
          "decision": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Verdict"
              }
            ],
            "description": "`approve` and `reject` resolve the review. `review` records the note and leaves it open as `in_review`."
          },
          "notes": {
            "type": "string",
            "maxLength": 2000
          }
        },
        "required": [
          "decision"
        ]
      },
      "ManualReview": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "manual_review"
            ]
          },
          "session_id": {
            "type": "string",
            "pattern": "^vs_"
          },
          "status": {
            "$ref": "#/components/schemas/ManualReviewStatus"
          },
          "decision": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Verdict"
              },
              {
                "type": "null"
              }
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the review was closed. Null while it is still open, including after a `review` decision."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "session_id",
          "status",
          "decision",
          "notes",
          "reviewed_at",
          "created_at"
        ]
      },
      "ManualReviewList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ManualReview"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "ConsentType": {
        "type": "string",
        "enum": [
          "biometric",
          "fraud_network",
          "model_improvement"
        ],
        "description": "The scope the end user consented to."
      },
      "CreateConsentRequest": {
        "type": "object",
        "properties": {
          "session_id": {
            "type": "string",
            "pattern": "^vs_"
          },
          "consent_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConsentType"
            },
            "minItems": 1
          },
          "policy_version": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "The version of the policy text that was shown to the user."
          },
          "locale": {
            "type": "string",
            "maxLength": 35,
            "description": "The locale the policy was shown in."
          }
        },
        "required": [
          "session_id",
          "consent_types",
          "policy_version"
        ]
      },
      "ConsentRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "consent_record"
            ]
          },
          "session_id": {
            "type": "string",
            "pattern": "^vs_"
          },
          "consent_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConsentType"
            }
          },
          "policy_version": {
            "type": "string"
          },
          "granted_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "session_id",
          "consent_types",
          "policy_version",
          "granted_at"
        ]
      },
      "Merchant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^mch_"
          },
          "object": {
            "type": "string",
            "enum": [
              "merchant"
            ]
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended"
            ]
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          },
          "tier": {
            "$ref": "#/components/schemas/EngineTier"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "slug",
          "status",
          "mode",
          "tier",
          "created_at"
        ]
      },
      "Entitlements": {
        "type": "object",
        "description": "What the key in the Authorization header is entitled to.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "entitlements"
            ]
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          },
          "tier": {
            "$ref": "#/components/schemas/EngineTier"
          },
          "environment": {
            "$ref": "#/components/schemas/Environment"
          },
          "permissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKeyPermission"
            }
          }
        },
        "required": [
          "object",
          "mode",
          "tier",
          "environment",
          "permissions"
        ]
      },
      "MeteredOperation": {
        "type": "string",
        "enum": [
          "liveness",
          "face_match",
          "document",
          "age_estimation",
          "aml_screening",
          "proof_of_address",
          "verification"
        ],
        "description": "A metered unit. The check types, plus `verification`, the per-session anchor that meters the session itself rather than a check inside it."
      },
      "UsageSummary": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "usage_summary"
            ]
          },
          "since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Echoes the `since` you passed, or null when the window was unbounded."
          },
          "total": {
            "type": "integer",
            "description": "All metered operations in the window."
          },
          "billable": {
            "type": "integer",
            "description": "The subset of `total` that is billable."
          },
          "by_check_type": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "check_type": {
                  "$ref": "#/components/schemas/MeteredOperation"
                },
                "count": {
                  "type": "integer"
                },
                "billable": {
                  "type": "integer"
                }
              },
              "required": [
                "check_type",
                "count",
                "billable"
              ]
            }
          }
        },
        "required": [
          "object",
          "since",
          "total",
          "billable",
          "by_check_type"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "permission_error",
                  "rate_limit_error",
                  "idempotency_error",
                  "not_found_error",
                  "api_error"
                ]
              },
              "code": {
                "type": "string",
                "description": "UPPER_SNAKE machine code."
              },
              "message": {
                "type": "string"
              },
              "param": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "code",
              "message",
              "request_id"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing/invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key authenticated but lacks a permission the endpoint requires, or the entitlement is missing. Code `INSUFFICIENT_PERMISSIONS`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "IdempotencyConflict": {
        "description": "Idempotency-Key reused with a different body",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until retry."
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Temporarily unavailable. Retry with backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
