{
  "openapi": "3.1.0",
  "info": {
    "title": "ThirdFactor Obsidian API",
    "version": "2026-05-17",
    "description": "Identity, KYC, AML and onboarding API. Two surfaces share one tenant API key: the native KYC engine (`/v3`, sent as `x-api-key`) and the partner applications/lists API (`/api/v1`, sent as `Authorization: Bearer`). Branch on the HTTP status first, then the stable string error code.",
    "contact": {
      "name": "ThirdFactor Support",
      "email": "support@thirdfactor.ai"
    }
  },
  "servers": [
    {
      "url": "https://v3.thirdfactor.ai",
      "description": "Set this to your Obsidian deployment base URL"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "KYC Sessions",
      "description": "Native verification sessions (/v3)."
    },
    {
      "name": "Tools",
      "description": "One-shot standalone tools (/v3/tools)."
    },
    {
      "name": "Workflows & Usage",
      "description": "Workflows, usage and credits (/v3)."
    },
    {
      "name": "Applications",
      "description": "Partner hosted applications (/api/v1)."
    },
    {
      "name": "Lists",
      "description": "Tenant risk lists (/api/v1)."
    }
  ],
  "paths": {
    "/api/v1/sessions/": {
      "post": {
        "tags": [
          "Applications"
        ],
        "summary": "Create hosted session",
        "description": "Create a hosted verification/onboarding session for an applicant and receive a one-time hosted URL to redirect them to.\n\nThe request body carries a single **HS256 partner-signed JWT** signed with your tenant JWT signing secret. The JWT claim set is:\n\n| Claim | Required | Meaning |\n| --- | --- | --- |\n| `jti` | yes | Idempotency key and external reference for the session (stored as the application's `external_ref`). A repeat create with the same `jti` returns the original session and is never charged twice. |\n| `iat` | yes | Issued-at UNIX timestamp. |\n| `exp` | yes | JWT expiry (UNIX). Must be no more than 5 minutes after `iat` (`exp - iat <= 300`). |\n| `flow_id` | yes | Tenant flow to run. |\n| `payload` | no | Initial applicant data, e.g. `phone`, `email`, `identifier`, `full_name`. |\n| `session_ttl_minutes` | no | Hosted URL lifetime. Omit for the tenant default, `0` for no expiry, or `1..525600` for a custom lifetime. |\n\nCreating a session debits the tenant's `SESSION_CREATE` credit rate; `402 insufficient_credits` is returned when the balance is exhausted. Redirect the applicant to the returned `url`.",
        "operationId": "createHostedSession",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JwtSessionRequest"
              },
              "example": {
                "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjb3JlLWJhbmtpbmctaWQtMTIzIiwiaWF0IjoxNzE1OTk5MDAwLCJleHAiOjE3MTU5OTkzMDAsImZsb3dfaWQiOiJzYXZpbmdzX3YxIiwicGF5bG9hZCI6eyJwaG9uZSI6Ijk4NDEwMDAwMDAiLCJlbWFpbCI6ImN1c3RvbWVyQGV4YW1wbGUuY29tIn19.SIGNATURE"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session created (or original session returned on idempotent replay).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostedSessionResponse"
                },
                "example": {
                  "ok": true,
                  "session_id": "ge9vgmlmfbi9k5q",
                  "url": "https://acme.thirdfactor.io/s/8Xq2f6a1c9d0e2f4g6h8j0k2l4m6n8p0",
                  "expires": true,
                  "expires_at": "2026-05-18T12:00:00Z",
                  "credits_remaining": 99
                }
              }
            }
          },
          "400": {
            "description": "Missing `jwt` in body, missing `flow_id` claim, or invalid `session_ttl_minutes`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "example": {
                  "ok": false,
                  "error": "invalid_request",
                  "detail": "flow_id is required"
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid Bearer API key, or an invalid/expired partner JWT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "example": {
                  "ok": false,
                  "error": "invalid_jwt",
                  "detail": "expired"
                }
              }
            }
          },
          "402": {
            "description": "Tenant credit balance is exhausted; the `SESSION_CREATE` fee could not be debited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "example": {
                  "ok": false,
                  "error": "insufficient_credits"
                }
              }
            }
          },
          "404": {
            "description": "The `flow_id` claim does not match any flow for this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "example": {
                  "ok": false,
                  "error": "flow_not_found"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/applications/{application_id}/": {
      "parameters": [
        {
          "name": "application_id",
          "in": "path",
          "required": true,
          "description": "Application (session) id, as returned by `session_id` from Create Hosted Session.",
          "schema": {
            "type": "string",
            "example": "ge9vgmlmfbi9k5q"
          }
        }
      ],
      "get": {
        "tags": [
          "Applications"
        ],
        "summary": "Get application",
        "description": "Fetch the full record for a single application. The response is scoped to your tenant — a `404` is returned for ids that belong to a different tenant or do not exist.\n\nPrefer `application.decision` (stable contract: `approved`, `rejected`, `manual_review`, `in_progress`, `abandoned`) over the more detailed `status` for external branching.",
        "operationId": "getApplication",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The application record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "application": {
                      "$ref": "#/components/schemas/Application"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "application": {
                    "id": "ge9vgmlmfbi9k5q",
                    "external_ref": "core-banking-id-123",
                    "tracking_id": "APP-2026-0001",
                    "status": "manual_review",
                    "decision": "manual_review",
                    "session_expires_at": "2026-05-18T12:00:00Z",
                    "session_expiry_state": "active",
                    "phone": "9841000000",
                    "email": "customer@example.com",
                    "created_at": "2026-05-18T08:00:00Z",
                    "updated_at": "2026-05-18T08:10:00Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "example": {
                  "ok": false,
                  "error": "invalid_token",
                  "detail": "API key required"
                }
              }
            }
          },
          "404": {
            "description": "No application with this id belongs to your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "example": {
                  "ok": false,
                  "error": "not_found"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Applications"
        ],
        "summary": "Update application session expiry",
        "description": "Update the hosted-URL expiry for an application. Send **either** `expire_now: true` to expire the live hosted URL immediately, **or** `session_expires_at` to set/clear the expiry. Use `null` or an empty string for `session_expires_at` to make the hosted URL non-expiring.\n\nAt least one supported field must be present, otherwise `400 no_supported_fields` is returned.",
        "operationId": "updateApplicationExpiry",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApplicationPatch"
              },
              "examples": {
                "expireNow": {
                  "summary": "Expire the hosted URL now",
                  "value": {
                    "expire_now": true
                  }
                },
                "setExpiry": {
                  "summary": "Set a specific expiry",
                  "value": {
                    "session_expires_at": "2026-05-18T12:00:00Z"
                  }
                },
                "clearExpiry": {
                  "summary": "Make the hosted URL non-expiring",
                  "value": {
                    "session_expires_at": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated application record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "application": {
                      "$ref": "#/components/schemas/Application"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "application": {
                    "id": "ge9vgmlmfbi9k5q",
                    "external_ref": "core-banking-id-123",
                    "status": "manual_review",
                    "decision": "manual_review",
                    "session_expires_at": "2026-05-18T12:00:00Z",
                    "session_expiry_state": "active"
                  }
                }
              }
            }
          },
          "400": {
            "description": "No supported field supplied, or an unparseable `session_expires_at`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "examples": {
                  "noFields": {
                    "value": {
                      "ok": false,
                      "error": "no_supported_fields"
                    }
                  },
                  "badDate": {
                    "value": {
                      "ok": false,
                      "error": "invalid_session_expires_at"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "example": {
                  "ok": false,
                  "error": "invalid_token",
                  "detail": "API key required"
                }
              }
            }
          },
          "404": {
            "description": "No application with this id belongs to your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiV1Error"
                },
                "example": {
                  "ok": false,
                  "error": "not_found"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/lists/": {
      "get": {
        "tags": [
          "Lists"
        ],
        "summary": "List risk lists",
        "description": "List the tenant-scoped risk lists (blocklists) used by flows. Supported list types are `phone`, `email`, and `identifier`.",
        "operationId": "listRiskLists",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Filter by list type.",
            "schema": {
              "type": "string",
              "enum": [
                "phone",
                "email",
                "identifier"
              ]
            }
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "Filter by active state. Truthy values: `1`, `true`, `yes`.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Risk lists for the tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "lists": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RiskList"
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "lists": [
                    {
                      "id": "90f2c1a8-4f4e-4d1c-9b95-7dfda01965ba",
                      "name": "Sanctions",
                      "description": "Client-managed sanctions list",
                      "type": "email",
                      "is_active": true,
                      "entry_count": 1200,
                      "created_at": "2026-05-18T08:00:00Z",
                      "updated_at": "2026-05-18T08:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid `type` filter value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "type must be phone, email, or identifier."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Lists"
        ],
        "summary": "Create risk list",
        "description": "Create a new tenant-scoped risk list. `type` is one of `phone`, `email`, or `identifier` and is immutable after creation because entry values are normalized by type. Creating a list with a name that already exists returns `409`.",
        "operationId": "createRiskList",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RiskListCreate"
              },
              "example": {
                "name": "Sanctions",
                "type": "identifier",
                "description": "Client-managed OCR identifier list",
                "is_active": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "List created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "list": {
                      "$ref": "#/components/schemas/RiskList"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "list": {
                    "id": "90f2c1a8-4f4e-4d1c-9b95-7dfda01965ba",
                    "name": "Sanctions",
                    "description": "Client-managed OCR identifier list",
                    "type": "identifier",
                    "is_active": true,
                    "entry_count": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing `name`, or an invalid `type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "name is required."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          },
          "409": {
            "description": "A list with this name already exists for the tenant. The existing list `id` is included.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "A list named 'Sanctions' already exists.",
                  "id": "90f2c1a8-4f4e-4d1c-9b95-7dfda01965ba"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/lists/{list_id}/": {
      "parameters": [
        {
          "name": "list_id",
          "in": "path",
          "required": true,
          "description": "Risk list UUID.",
          "schema": {
            "type": "string",
            "format": "uuid",
            "example": "90f2c1a8-4f4e-4d1c-9b95-7dfda01965ba"
          }
        }
      ],
      "patch": {
        "tags": [
          "Lists"
        ],
        "summary": "Update list metadata",
        "description": "Update a risk list's mutable metadata (`name`, `description`, `is_active`). `type` is immutable because entries are normalized by type — create a new list if the type must change.",
        "operationId": "updateRiskList",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New list name (must not be blank if provided)."
                  },
                  "description": {
                    "type": "string"
                  },
                  "is_active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "name": "Sanctions v2",
                "description": "Updated source",
                "is_active": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "list": {
                      "$ref": "#/components/schemas/RiskList"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "list": {
                    "id": "90f2c1a8-4f4e-4d1c-9b95-7dfda01965ba",
                    "name": "Sanctions v2",
                    "description": "Updated source",
                    "type": "identifier",
                    "is_active": true,
                    "entry_count": 1200,
                    "created_at": "2026-05-18T08:00:00Z",
                    "updated_at": "2026-05-18T09:00:00Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`name` was provided but blank.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "name cannot be blank."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No list with this id belongs to your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Not found."
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Lists"
        ],
        "summary": "Delete risk list",
        "description": "Delete a risk list and all of its entries. This is irreversible.",
        "operationId": "deleteRiskList",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No list with this id belongs to your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Not found."
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/lists/{list_id}/entries/": {
      "parameters": [
        {
          "name": "list_id",
          "in": "path",
          "required": true,
          "description": "Risk list UUID.",
          "schema": {
            "type": "string",
            "format": "uuid",
            "example": "90f2c1a8-4f4e-4d1c-9b95-7dfda01965ba"
          }
        }
      ],
      "get": {
        "tags": [
          "Lists"
        ],
        "summary": "List entries",
        "description": "List the entries in a risk list, with pagination and an optional search filter that matches against both value and comment.",
        "operationId": "listEntries",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number (default `1`).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "description": "Entries per page (default `100`, max `500`).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring filter over entry value and comment.",
            "schema": {
              "type": "string",
              "example": "9812"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paged entries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "total": {
                      "type": "integer",
                      "example": 1
                    },
                    "page": {
                      "type": "integer",
                      "example": 1
                    },
                    "page_size": {
                      "type": "integer",
                      "example": 100
                    },
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ListEntry"
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "total": 1,
                  "page": 1,
                  "page_size": 100,
                  "entries": [
                    {
                      "id": "4a4b1e25-7d9b-4d5f-8c5c-f6718794c747",
                      "value": "9812345678",
                      "comment": "Confirmed fraud source",
                      "created_at": "2026-05-18T08:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No list with this id belongs to your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Not found."
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Lists"
        ],
        "summary": "Upsert one entry or bulk add",
        "description": "Add entries to a list. Send **either** a single `{ value, comment }` object (upsert — `201` when created, `200` when the normalized value already existed and was updated), **or** an `{ entries: [...] }` object to bulk-add (idempotent; duplicates skipped by normalized value; always `201`).\n\nValues are normalized on write by list type: `phone` → digits only, `email` → trimmed + lowercased, `identifier` → trimmed.",
        "operationId": "addEntries",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/ListEntryUpsert"
                  },
                  {
                    "$ref": "#/components/schemas/BulkEntries"
                  }
                ]
              },
              "examples": {
                "single": {
                  "summary": "Upsert one entry",
                  "value": {
                    "value": "+977 9812-345678",
                    "comment": "Confirmed fraud source"
                  }
                },
                "bulk": {
                  "summary": "Bulk add",
                  "value": {
                    "entries": [
                      {
                        "value": "9812345678",
                        "comment": "source A"
                      },
                      {
                        "value": "bad@example.com",
                        "comment": "source B"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Single upsert where the normalized value already existed and its comment was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "entry": {
                      "$ref": "#/components/schemas/ListEntry"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "entry": {
                    "id": "4a4b1e25-7d9b-4d5f-8c5c-f6718794c747",
                    "value": "9812345678",
                    "comment": "Confirmed fraud source",
                    "created_at": "2026-05-18T08:00:00Z"
                  }
                }
              }
            }
          },
          "201": {
            "description": "Single entry created, or bulk add result.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean"
                        },
                        "entry": {
                          "$ref": "#/components/schemas/ListEntry"
                        }
                      }
                    },
                    {
                      "$ref": "#/components/schemas/BulkEntriesResult"
                    }
                  ]
                },
                "examples": {
                  "singleCreated": {
                    "summary": "Single entry created",
                    "value": {
                      "ok": true,
                      "entry": {
                        "id": "4a4b1e25-7d9b-4d5f-8c5c-f6718794c747",
                        "value": "9812345678",
                        "comment": "Confirmed fraud source",
                        "created_at": "2026-05-18T08:00:00Z"
                      }
                    }
                  },
                  "bulk": {
                    "summary": "Bulk add result",
                    "value": {
                      "ok": true,
                      "created": 2,
                      "skipped": 0,
                      "invalid": 0,
                      "errors": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Single upsert with a missing/empty `value`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "value is required."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No list with this id belongs to your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Not found."
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Lists"
        ],
        "summary": "Replace all entries",
        "description": "Replace **all** entries in a list with the supplied set, in a single transaction. Intended for client-owned nightly sync jobs where Obsidian should exactly mirror the upstream source. If any entry is invalid, the whole request is rejected with `400` and no changes are made.",
        "operationId": "replaceEntries",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkEntries"
              },
              "example": {
                "entries": [
                  {
                    "value": "1111111111",
                    "comment": "latest export"
                  },
                  {
                    "value": "2222222222",
                    "comment": "latest export"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entries replaced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "replaced": {
                      "type": "boolean",
                      "example": true
                    },
                    "deleted": {
                      "type": "integer",
                      "description": "Count of entries removed.",
                      "example": 1200
                    },
                    "created": {
                      "type": "integer",
                      "description": "Count of entries written.",
                      "example": 2
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "replaced": true,
                  "deleted": 1200,
                  "created": 2
                }
              }
            }
          },
          "400": {
            "description": "One or more entries are invalid; nothing was changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "invalid": {
                      "type": "integer"
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "row": {
                            "type": "integer",
                            "nullable": true
                          },
                          "error": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "error": "invalid_entries",
                  "invalid": 1,
                  "errors": [
                    {
                      "row": 2,
                      "error": "value is required"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No list with this id belongs to your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Not found."
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/lists/{list_id}/entries/{entry_id}/": {
      "parameters": [
        {
          "name": "list_id",
          "in": "path",
          "required": true,
          "description": "Risk list UUID.",
          "schema": {
            "type": "string",
            "format": "uuid",
            "example": "90f2c1a8-4f4e-4d1c-9b95-7dfda01965ba"
          }
        },
        {
          "name": "entry_id",
          "in": "path",
          "required": true,
          "description": "List entry UUID.",
          "schema": {
            "type": "string",
            "format": "uuid",
            "example": "4a4b1e25-7d9b-4d5f-8c5c-f6718794c747"
          }
        }
      ],
      "patch": {
        "tags": [
          "Lists"
        ],
        "summary": "Update one entry",
        "description": "Update a single entry's `value` and/or `comment`. The `value` is re-normalized by the list type. A `409` is returned if the new value collides with an existing entry in the same list.",
        "operationId": "updateEntry",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "string",
                    "description": "New value (re-normalized by list type; must not be blank if provided)."
                  },
                  "comment": {
                    "type": "string",
                    "description": "Note, truncated to 500 chars."
                  }
                }
              },
              "example": {
                "value": "9812345678",
                "comment": "Updated note"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated entry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "entry": {
                      "$ref": "#/components/schemas/ListEntry"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "entry": {
                    "id": "4a4b1e25-7d9b-4d5f-8c5c-f6718794c747",
                    "value": "9812345678",
                    "comment": "Updated note",
                    "created_at": "2026-05-18T08:00:00Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`value` was provided but normalized to blank.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "value cannot be blank."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No such entry in this list for your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Not found."
                }
              }
            }
          },
          "409": {
            "description": "The new value collides with an existing entry in the list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "entry value already exists."
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Lists"
        ],
        "summary": "Delete one entry",
        "description": "Delete a single entry from a risk list.",
        "operationId": "deleteEntry",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Entry deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No such entry in this list for your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListError"
                },
                "example": {
                  "error": "Not found."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/": {
      "post": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Create verification session",
        "description": "Create a hosted native-KYC verification session and get back an absolute `url` to redirect the applicant to. The hosted flow runs the workflow's enabled modules and the server enforces the configured liveness method (it cannot be downgraded client-side). Creating a session debits the tenant's `SESSION_CREATE` credit rate. Send an `Idempotency-Key` header to make retries safe — a repeat create with the same key returns the original session (`201`) and is never charged twice.",
        "operationId": "createV3Session",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Client-supplied unique key (max 128 chars). A repeat create with the same key returns the original session and is not charged again.",
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "example": "create-user-123-2026-07-03"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V3SessionCreate"
              },
              "example": {
                "workflow_id": "b2c1e9f0-4a3d-4c1b-9e2a-8f7d6c5b4a30",
                "vendor_data": "your-user-reference-123",
                "callback_url": "https://partner.example.com/kyc/done",
                "contact_email": "customer@example.com",
                "contact_phone": "9841000000",
                "expires_in_hours": 24,
                "locale": "en",
                "prefill": {
                  "full_name": "Aarav Sharma",
                  "date_of_birth": "1990-04-12",
                  "email": "aarav@example.com",
                  "phone": "9841000000",
                  "nationality": "NP"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session created (or, on an idempotent replay, the original session).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Session"
                },
                "example": {
                  "id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
                  "session_kind": "user",
                  "status": "not_started",
                  "stage": "created",
                  "url": "https://verify.example.com/verify/eyJ0b2tlbiI6...",
                  "vendor_data": "your-user-reference-123",
                  "workflow": "b2c1e9f0-4a3d-4c1b-9e2a-8f7d6c5b4a30",
                  "decision": "in_progress",
                  "decision_v3": {
                    "id_verifications": [],
                    "liveness_checks": [],
                    "face_matches": [],
                    "aml_screenings": [],
                    "reviews": []
                  },
                  "risk": null,
                  "tags": [],
                  "credits_charged": 1,
                  "callback_url": "https://partner.example.com/kyc/done",
                  "metadata": {},
                  "expires_at": "2026-07-04T08:00:00Z",
                  "created_at": "2026-07-03T08:00:00Z",
                  "completed_at": null
                }
              }
            }
          },
          "400": {
            "description": "Validation error (bad field types, out-of-range `expires_in_hours`, etc.).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "expires_in_hours must be between 1 and 720."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "402": {
            "description": "Tenant credit balance is exhausted; the session mint fee could not be debited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "insufficient_credits"
                }
              }
            }
          },
          "404": {
            "description": "The supplied `workflow_id` does not belong to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "workflow_id not found."
                }
              }
            }
          },
          "429": {
            "description": "Rate limit for session creation exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "List sessions",
        "description": "List the tenant's verification sessions, newest first. The response body is a **bare JSON array**; total/page metadata rides in the `X-Total-Count`, `X-Page`, and `X-Page-Size` response headers. Also reachable as `GET /v3/sessions` (no trailing slash). Filter with `?status=` and `?session_kind=` (an unknown value returns `400`).",
        "operationId": "listV3Sessions",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by session status. An unknown value returns 400.",
            "schema": {
              "type": "string",
              "enum": [
                "not_started",
                "in_progress",
                "approved",
                "declined",
                "review",
                "expired",
                "abandoned"
              ]
            },
            "example": "approved"
          },
          {
            "name": "session_kind",
            "in": "query",
            "required": false,
            "description": "Filter by session kind. An unknown value returns 400.",
            "schema": {
              "type": "string",
              "enum": [
                "user"
              ]
            },
            "example": "user"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 1
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "description": "Rows per page (max 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "example": 50
          }
        ],
        "responses": {
          "200": {
            "description": "A bare array of sessions (newest first). Pagination metadata is in the response headers.",
            "headers": {
              "X-Total-Count": {
                "description": "Total number of sessions matching the filter.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Page": {
                "description": "The page number returned.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Page-Size": {
                "description": "The page size applied.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/V3SessionListItem"
                  }
                },
                "example": [
                  {
                    "id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
                    "session_kind": "user",
                    "status": "approved",
                    "stage": "completed",
                    "url": "https://verify.example.com/verify/eyJ0b2tlbiI6...",
                    "vendor_data": "your-user-reference-123",
                    "workflow": "b2c1e9f0-4a3d-4c1b-9e2a-8f7d6c5b4a30",
                    "decision": "approved",
                    "decision_v3": {
                      "id_verifications": [],
                      "liveness_checks": [],
                      "face_matches": [],
                      "aml_screenings": [],
                      "reviews": []
                    },
                    "risk": null,
                    "tags": [
                      "vip"
                    ],
                    "credits_charged": 3.5,
                    "callback_url": "https://partner.example.com/kyc/done",
                    "metadata": {},
                    "expires_at": "2026-07-04T08:00:00Z",
                    "created_at": "2026-07-03T08:00:00Z",
                    "completed_at": "2026-07-03T08:12:41Z"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "An unknown `status` or `session_kind` filter value was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "invalid_status"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/{session_id}/": {
      "get": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Get session",
        "description": "Retrieve a single verification session in full, including its per-module results grouped under `decision_v3`.",
        "operationId": "getV3Session",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "responses": {
          "200": {
            "description": "The session with its decision and module results.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Session"
                },
                "example": {
                  "id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
                  "session_kind": "user",
                  "status": "approved",
                  "stage": "completed",
                  "url": "https://verify.example.com/verify/eyJ0b2tlbiI6...",
                  "vendor_data": "your-user-reference-123",
                  "workflow": "b2c1e9f0-4a3d-4c1b-9e2a-8f7d6c5b4a30",
                  "decision": "approved",
                  "decision_v3": {
                    "id_verifications": [
                      {
                        "node_id": "ID_VERIFICATION",
                        "status": "passed",
                        "score": 0.98,
                        "warnings": [],
                        "data": {
                          "document_type": "citizenship",
                          "document_number": "12-34-56-78901"
                        }
                      }
                    ],
                    "liveness_checks": [
                      {
                        "node_id": "LIVENESS",
                        "status": "passed",
                        "score": 0.99,
                        "warnings": [],
                        "data": {}
                      }
                    ],
                    "face_matches": [
                      {
                        "node_id": "FACE_MATCH",
                        "status": "passed",
                        "score": 0.94,
                        "warnings": [],
                        "data": {}
                      }
                    ],
                    "aml_screenings": [],
                    "reviews": []
                  },
                  "risk": null,
                  "tags": [],
                  "credits_charged": 3.5,
                  "callback_url": "https://partner.example.com/kyc/done",
                  "metadata": {},
                  "expires_at": "2026-07-04T08:00:00Z",
                  "created_at": "2026-07-03T08:00:00Z",
                  "completed_at": "2026-07-03T08:12:41Z"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/{session_id}/decision/": {
      "get": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Get decision summary",
        "description": "A compact decision summary: the session status, the rolled-up `decision`, the per-module `decision_v3` arrays, and any `reason`.",
        "operationId": "getV3SessionDecision",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "responses": {
          "200": {
            "description": "The compact decision summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3DecisionSummary"
                },
                "example": {
                  "session_id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
                  "status": "approved",
                  "decision": "approved",
                  "decision_v3": {
                    "id_verifications": [],
                    "liveness_checks": [],
                    "face_matches": [],
                    "aml_screenings": [],
                    "reviews": []
                  },
                  "reason": ""
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/{session_id}/update-status/": {
      "post": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Override session status",
        "description": "Override the session decision. `status` must be a valid session status (else `400`). `PATCH` and `POST` are equivalent.",
        "operationId": "updateV3SessionStatus",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V3UpdateStatusBody"
              },
              "example": {
                "status": "approved",
                "reason": "Manual override after document re-review."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Session"
                }
              }
            }
          },
          "400": {
            "description": "The supplied `status` is not a valid session status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Invalid status."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Write rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Override session status (PATCH)",
        "description": "Override the session decision. `status` must be a valid session status (else `400`). Identical behavior to the `POST` variant.",
        "operationId": "patchV3SessionStatus",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V3UpdateStatusBody"
              },
              "example": {
                "status": "declined",
                "reason": "Document tampering detected on manual review."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Session"
                }
              }
            }
          },
          "400": {
            "description": "The supplied `status` is not a valid session status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Invalid status."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Write rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/{session_id}/delete/": {
      "post": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Delete session",
        "description": "Soft-delete the session and its evidence. Returns `204` with no body. `POST` and `DELETE` are equivalent.",
        "operationId": "deleteV3SessionViaPost",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "responses": {
          "204": {
            "description": "Session soft-deleted; no content."
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant."
          },
          "429": {
            "description": "Write rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Delete session (DELETE)",
        "description": "Soft-delete the session and its evidence. Returns `204` with no body. Identical behavior to the `POST` variant.",
        "operationId": "deleteV3Session",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "responses": {
          "204": {
            "description": "Session soft-deleted; no content."
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant."
          },
          "429": {
            "description": "Write rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/{session_id}/logs/": {
      "get": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Get session logs",
        "description": "The session's chronological event timeline.",
        "operationId": "getV3SessionLogs",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "responses": {
          "200": {
            "description": "The event timeline for the session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3SessionLogs"
                },
                "example": {
                  "session_id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
                  "logs": [
                    {
                      "event": "session.created",
                      "source": "api",
                      "message": "Session created via partner API",
                      "detail": {},
                      "created_at": "2026-07-03T08:00:00Z"
                    },
                    {
                      "event": "module.completed",
                      "source": "flow",
                      "message": "ID verification passed",
                      "detail": {
                        "feature": "ID_VERIFICATION",
                        "score": 0.98
                      },
                      "created_at": "2026-07-03T08:11:20Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/{session_id}/checklist/": {
      "get": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Get evidence checklist",
        "description": "The session's evidence checklist plus its current `stage`.",
        "operationId": "getV3SessionChecklist",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "responses": {
          "200": {
            "description": "The evidence checklist and stage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Checklist"
                },
                "example": {
                  "session_id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
                  "stage": "completed",
                  "checklist": [
                    {
                      "key": "document",
                      "label": "Identity document",
                      "status": "collected"
                    },
                    {
                      "key": "selfie",
                      "label": "Selfie / liveness",
                      "status": "collected"
                    },
                    {
                      "key": "aml",
                      "label": "AML screening",
                      "status": "pending"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/{session_id}/tags/": {
      "get": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "List session tags",
        "description": "List the trust-layer tags applied to this session.",
        "operationId": "listV3SessionTags",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "responses": {
          "200": {
            "description": "The tags currently on this session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3SessionTagList"
                },
                "example": {
                  "session_id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
                  "tags": [
                    {
                      "name": "vip",
                      "source": "manual",
                      "applied_by": "partner-api"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Apply a session tag",
        "description": "Apply a trust-layer tag to the session. The stored tag is normalized (lowercased, spaces to underscores).",
        "operationId": "applyV3SessionTag",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V3TagBody"
              },
              "example": {
                "tag": "vip"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tag was applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3TagApplied"
                },
                "example": {
                  "applied": true,
                  "tag": "vip"
                }
              }
            }
          },
          "400": {
            "description": "The `tag` field was missing or blank.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "tag_required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Write rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Remove a session tag",
        "description": "Remove a trust-layer tag from the session.",
        "operationId": "removeV3SessionTag",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V3TagBody"
              },
              "example": {
                "tag": "vip"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Whether a matching tag was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3TagRemoved"
                },
                "example": {
                  "removed": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Write rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/session/{session_id}/generate-pdf": {
      "get": {
        "tags": [
          "KYC Sessions"
        ],
        "summary": "Download verification report (PDF)",
        "description": "Download the session's verification report as an `application/pdf` attachment (binary). Note: this route has **no trailing slash**.",
        "operationId": "generateV3SessionPdf",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The verification session UUID.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
          }
        ],
        "responses": {
          "200": {
            "description": "The verification report as a binary PDF attachment.",
            "headers": {
              "Content-Disposition": {
                "description": "Attachment filename, e.g. `attachment; filename=\"session-<id>.pdf\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Authentication credentials were not provided."
                }
              }
            }
          },
          "404": {
            "description": "No session with that id belongs to this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Session not found."
                }
              }
            }
          },
          "429": {
            "description": "Read rate limit exceeded; back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "Request was throttled."
                }
              }
            }
          }
        }
      }
    },
    "/v3/workflows/": {
      "get": {
        "tags": [
          "Workflows & Usage"
        ],
        "summary": "List KYC workflows",
        "description": "Returns every KYC workflow configured for the API key's tenant. Use a workflow `id` as the `workflow_id` when creating a hosted session (`POST /v3/session/`); omit it there to fall back to the tenant default. `features` lists the enabled feature keys (modules) the workflow runs.",
        "operationId": "listV3Workflows",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Workflows for the tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WorkflowSummary"
                  }
                },
                "example": [
                  {
                    "id": "b21c8f4e-7d3a-4c0e-9f11-2a6b5d0e9c34",
                    "name": "Standard KYC",
                    "status": "active",
                    "features": [
                      "ID_VERIFICATION",
                      "LIVENESS",
                      "FACE_MATCH",
                      "AML_SCREENING"
                    ]
                  },
                  {
                    "id": "f7a0d1c9-1e88-4b52-a0d3-8c9e2f6b7a10",
                    "name": "Lite (ID only)",
                    "status": "active",
                    "features": [
                      "ID_VERIFICATION"
                    ]
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (per-tenant read bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    },
    "/v3/usage/workflows/": {
      "get": {
        "tags": [
          "Workflows & Usage"
        ],
        "summary": "Credits consumed per workflow",
        "description": "Aggregates the tenant's credit debits grouped by the workflow that produced them. Each entry reports the workflow `name`, total `credits` consumed, and the number of ledger `entries` that contributed. Sessions with no associated workflow are grouped under `\"-\"`.",
        "operationId": "getV3UsageByWorkflow",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Per-workflow usage report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageReport"
                },
                "example": {
                  "usage": [
                    {
                      "workflow": "Lite (ID only)",
                      "credits": 42,
                      "entries": 42
                    },
                    {
                      "workflow": "Standard KYC",
                      "credits": 318.5,
                      "entries": 127
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (per-tenant read bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    },
    "/v3/credits/ledger/": {
      "get": {
        "tags": [
          "Workflows & Usage"
        ],
        "summary": "Credit ledger",
        "description": "Paginated credit ledger for the tenant, newest first. Each row records a `delta` (negative for a debit, positive for a top-up), the `reason`, the `feature` key charged, and the linked `session_id` / `application_id` when applicable. Filter by ISO date with `since` / `until` and paginate with `page` / `page_size` (max 500).",
        "operationId": "getV3CreditLedger",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only include ledger rows created on or after this date (inclusive). Format `YYYY-MM-DD`; an unparseable value returns 400.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-06-01"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Only include ledger rows created on or before this date (inclusive). Format `YYYY-MM-DD`; an unparseable value returns 400.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-06-30"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "description": "Rows per page. Clamped to the range 1..500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of ledger rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditLedgerPage"
                },
                "example": {
                  "count": 254,
                  "page": 1,
                  "page_size": 100,
                  "results": [
                    {
                      "id": "9c1f0a72-4e6b-4d21-8a90-1b2c3d4e5f60",
                      "delta": "-2.50",
                      "reason": "session_create",
                      "feature": "SESSION_CREATE",
                      "session_id": "6f0e2b7a-9c1d-4e2f-8a0b-1c2d3e4f5a6b",
                      "application_id": "",
                      "created_at": "2026-06-30T08:14:22.512000+00:00"
                    },
                    {
                      "id": "1a2b3c4d-5e6f-4071-9a8b-0c1d2e3f4a5b",
                      "delta": "-1.00",
                      "reason": "tool_usage",
                      "feature": "AML_CHECK",
                      "session_id": "",
                      "application_id": "",
                      "created_at": "2026-06-30T07:59:10.004000+00:00"
                    },
                    {
                      "id": "7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b",
                      "delta": "500.00",
                      "reason": "topup",
                      "feature": "",
                      "session_id": "",
                      "application_id": "",
                      "created_at": "2026-06-29T00:00:00+00:00"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid `since` / `until` date.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "since must be YYYY-MM-DD."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (per-tenant read bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    },
    "/v3/tools/aml-check/": {
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "AML / watchlist screening",
        "description": "Ad-hoc AML watchlist screening for a name plus optional identifiers (same engine as the in-flow `AML_SCREENING` module). Provide either `full_name` or `first_name` + `last_name`. Returns a `risk_level` and the classified matches. Charges the tenant's `AML_CHECK` rate on success.",
        "operationId": "runV3AmlCheck",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AmlCheckRequest"
              },
              "example": {
                "full_name": "Vladimir Petrov",
                "date_of_birth": "1968-03-14",
                "nationality": "RU",
                "country": "RU",
                "match_threshold": 0.7
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Screening result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AmlCheckResult"
                },
                "example": {
                  "risk_level": "high",
                  "matches": [
                    {
                      "entity_id": "NK-9x8y7z",
                      "name": "Vladimir PETROV",
                      "schema": "Person",
                      "score": 0.94,
                      "match": true,
                      "lists": [
                        "sanctions"
                      ],
                      "topics": [
                        "sanction"
                      ],
                      "datasets": [
                        "us_ofac_sdn",
                        "eu_fsf"
                      ],
                      "birth_date": "1968-03-14",
                      "countries": [
                        "ru"
                      ],
                      "aliases": [
                        "V. Petrov"
                      ],
                      "nationalities": [
                        "ru"
                      ],
                      "positions": [],
                      "gender": "male",
                      "addresses": [
                        "Moscow, Russia"
                      ],
                      "id_numbers": [
                        "7712345678"
                      ],
                      "url": "https://www.opensanctions.org/entities/NK-9x8y7z/"
                    }
                  ],
                  "possible_matches": [],
                  "informational_matches": [],
                  "lists_checked": [
                    "pep",
                    "sanctions"
                  ],
                  "provider": "watchlist",
                  "dataset": "default",
                  "algorithm": "logic-v1",
                  "threshold": 0.7
                }
              }
            }
          },
          "400": {
            "description": "Validation error (e.g. no name supplied).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "name_required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits to run the tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "insufficient_credits"
                }
              }
            }
          },
          "403": {
            "description": "The `AML_CHECK` feature is not enabled for this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3FeatureError"
                },
                "example": {
                  "detail": "feature_not_enabled",
                  "features": [
                    "AML_CHECK"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (inference-heavy tools bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    },
    "/v3/tools/face-search/": {
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "1:N face search",
        "description": "Ranks the tenant's enrolled identities by facial similarity to the probe photo (read-only; nothing is flagged). Supply a base64 JPEG/PNG image. `threshold` is clamped to 0.2..0.95 (default 0.35) and `top_k` to 1..50 (default 12). Charges the tenant's `FACE_SEARCH` rate on success.",
        "operationId": "runV3FaceSearch",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FaceSearchRequest"
              },
              "example": {
                "image_base64": "/9j/4AAQSkZJRgABAQEAYABgAAD...",
                "threshold": 0.4,
                "top_k": 10
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked face matches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaceSearchResult"
                },
                "example": {
                  "matches": [
                    {
                      "individual_id": "3c9a1e77-2b40-4d8f-9a11-6e5c4b3a2d10",
                      "name": "Aarav Sharma",
                      "external_user_id": "user-ref-123",
                      "status": "active",
                      "kyc_status": "approved",
                      "score": 0.8123,
                      "thumb": "https://cdn.example.com/faces/3c9a1e77.jpg"
                    }
                  ],
                  "threshold": 0.4
                }
              }
            }
          },
          "400": {
            "description": "Missing / invalid image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "image_required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits to run the tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "insufficient_credits"
                }
              }
            }
          },
          "403": {
            "description": "The `FACE_SEARCH` feature is not enabled for this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3FeatureError"
                },
                "example": {
                  "detail": "feature_not_enabled",
                  "features": [
                    "FACE_SEARCH"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "No face could be detected in the probe image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "no_face_detected"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (inference-heavy tools bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    },
    "/v3/tools/signature-verify/": {
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "1:1 signature verification",
        "description": "Compares a probe signature against a reference signature and returns a similarity `score` plus a `verdict`. Both images are cropped to the signature ink before comparison; a photo of a person is rejected. `threshold` is clamped to 0.2..0.95 (default 0.62). Charges the tenant's `SIGNATURE_VERIFY` rate on success.",
        "operationId": "runV3SignatureVerify",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignatureVerifyRequest"
              },
              "example": {
                "reference_base64": "iVBORw0KGgoAAAANSUhEUg...",
                "probe_base64": "iVBORw0KGgoAAAANSUhEUg...",
                "threshold": 0.62
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comparison result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureVerifyResult"
                },
                "example": {
                  "score": 0.71,
                  "verdict": "match",
                  "matched": true,
                  "threshold": 0.62,
                  "engine": "obsidian"
                }
              }
            }
          },
          "400": {
            "description": "Missing / invalid reference or probe image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "probe_required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits to run the tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "insufficient_credits"
                }
              }
            }
          },
          "403": {
            "description": "The `SIGNATURE_VERIFY` feature is not enabled for this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3FeatureError"
                },
                "example": {
                  "detail": "feature_not_enabled",
                  "features": [
                    "SIGNATURE_VERIFY"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "One of the inputs contains no detectable signature (or is a photo of a person).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "no_signature_detected"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (inference-heavy tools bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    },
    "/v3/tools/signature-search/": {
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "Reverse (1:N) signature search (beta)",
        "description": "BETA. Ranks the tenant's enrolled signature specimens by similarity to the probe signature — a recall aid, not a verdict (confirm with the 1:1 comparison). The probe is cropped to the signature ink first. `threshold` is clamped to 0.2..0.95 (default 0.6) and `top_k` to 1..50 (default 12). Charges the tenant's `SIGNATURE_SEARCH` rate on success.",
        "operationId": "runV3SignatureSearch",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignatureSearchRequest"
              },
              "example": {
                "image_base64": "iVBORw0KGgoAAAANSUhEUg...",
                "threshold": 0.6,
                "top_k": 12
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked signature matches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureSearchResult"
                },
                "example": {
                  "matches": [
                    {
                      "individual_id": "3c9a1e77-2b40-4d8f-9a11-6e5c4b3a2d10",
                      "name": "Aarav Sharma",
                      "external_user_id": "user-ref-123",
                      "status": "active",
                      "score": 0.7642,
                      "thumb": "https://cdn.example.com/signatures/abc123.png"
                    }
                  ],
                  "threshold": 0.6,
                  "beta": true
                }
              }
            }
          },
          "400": {
            "description": "Missing / invalid image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "image_required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits to run the tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "insufficient_credits"
                }
              }
            }
          },
          "403": {
            "description": "The `SIGNATURE_SEARCH` feature is not enabled for this tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3FeatureError"
                },
                "example": {
                  "detail": "feature_not_enabled",
                  "features": [
                    "SIGNATURE_SEARCH"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "No detectable signature in the probe image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "no_signature_detected"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (inference-heavy tools bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    },
    "/v3/tools/signature-enroll/": {
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "Enroll a signature specimen",
        "description": "Adds a signature specimen to an identity's gallery (grows what reverse search can find). Identify the target with either `individual_id` or `external_user_id`; when both are absent, or no identity matches, a 404 is returned. The image is cropped to the signature ink before enrollment.",
        "operationId": "runV3SignatureEnroll",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignatureEnrollRequest"
              },
              "example": {
                "external_user_id": "user-ref-123",
                "image_base64": "iVBORw0KGgoAAAANSUhEUg...",
                "label": "loan-application-2026"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Specimen enrolled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureEnrollResult"
                },
                "example": {
                  "sample_id": "a1b2c3d4-e5f6-4071-8a9b-0c1d2e3f4a5b",
                  "individual_id": "3c9a1e77-2b40-4d8f-9a11-6e5c4b3a2d10"
                }
              }
            }
          },
          "400": {
            "description": "Missing / invalid image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "image_required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "404": {
            "description": "No matching identity for the supplied `individual_id` / `external_user_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "individual_not_found"
                }
              }
            }
          },
          "422": {
            "description": "No detectable signature in the supplied image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "no_signature_detected"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (inference-heavy tools bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    },
    "/v3/tools/document-extract/": {
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "Identity-document OCR extraction",
        "description": "Runs identity-document OCR on a base64 JPEG/PNG image via the platform document-OCR service and returns the extracted fields, a romanised/English read (`translated_fields`), the detected document type, and quality signals. Documents with no trained template (e.g. disability cards) fall back to a best-effort generic extraction flagged with `generic_extraction`.",
        "operationId": "runV3DocumentExtract",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentExtractRequest"
              },
              "example": {
                "image_base64": "/9j/4AAQSkZJRgABAQEAYABgAAD..."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extracted document fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentExtractResult"
                },
                "example": {
                  "fields": {
                    "full_name": "AARAV SHARMA",
                    "document_number": "P1234567",
                    "date_of_birth": "1990-04-12",
                    "expiry_date": "2030-04-11",
                    "nationality": "NPL"
                  },
                  "translated_fields": {
                    "full_name": "Aarav Sharma"
                  },
                  "detected_type": "passport",
                  "confidence": 0.97,
                  "is_document_valid": true,
                  "has_portrait": true
                }
              }
            }
          },
          "400": {
            "description": "Missing / invalid image.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "image_required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "authentication_failed"
                }
              }
            }
          },
          "422": {
            "description": "No fields could be extracted (unsupported document type or unreadable photo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "ocr_no_fields"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (inference-heavy tools bucket).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V3Error"
                },
                "example": {
                  "detail": "throttled"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Tenant API key for the native KYC API (/v3)."
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Tenant API key as a bearer token for the partner API (/api/v1)."
      }
    },
    "schemas": {
      "JwtSessionRequest": {
        "type": "object",
        "required": [
          "jwt"
        ],
        "description": "Body for Create Hosted Session — carries a single HS256 partner-signed JWT.",
        "properties": {
          "jwt": {
            "type": "string",
            "description": "HS256 JWT signed with the tenant JWT signing secret. Claims: `jti` (req), `iat` (req), `exp` (req, <= iat+300s), `flow_id` (req), `payload` (opt), `session_ttl_minutes` (opt: omit=tenant default, 0=no expiry, 1..525600=custom)."
          }
        },
        "example": {
          "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjb3JlLWJhbmtpbmctaWQtMTIzIiwiaWF0IjoxNzE1OTk5MDAwLCJleHAiOjE3MTU5OTkzMDAsImZsb3dfaWQiOiJzYXZpbmdzX3YxIn0.SIGNATURE"
        }
      },
      "HostedSessionResponse": {
        "type": "object",
        "description": "Successful Create Hosted Session response.",
        "properties": {
          "ok": {
            "type": "boolean",
            "example": true
          },
          "session_id": {
            "type": "string",
            "description": "Application/session id — use as `application_id` on the Applications endpoints.",
            "example": "ge9vgmlmfbi9k5q"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute hosted URL to redirect the applicant to.",
            "example": "https://acme.thirdfactor.io/s/8Xq2f6a1c9d0e2f4g6h8j0k2l4m6n8p0"
          },
          "expires": {
            "type": "boolean",
            "description": "Whether the hosted URL has an expiry.",
            "example": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Hosted URL expiry, or null when non-expiring.",
            "example": "2026-05-18T12:00:00Z"
          },
          "credits_remaining": {
            "type": "integer",
            "description": "Tenant credit balance after the SESSION_CREATE debit.",
            "example": 99
          },
          "replay": {
            "type": "boolean",
            "description": "Present and true when a repeated `jti` returned the original session (idempotent replay).",
            "example": true
          }
        }
      },
      "Application": {
        "type": "object",
        "description": "A verification/onboarding application (session). Prefer `decision` for external branching over the more detailed `status`.",
        "properties": {
          "id": {
            "type": "string",
            "example": "ge9vgmlmfbi9k5q"
          },
          "external_ref": {
            "type": "string",
            "description": "The `jti` from the session-create JWT.",
            "example": "core-banking-id-123"
          },
          "tracking_id": {
            "type": "string",
            "example": "APP-2026-0001"
          },
          "status": {
            "type": "string",
            "description": "Detailed workflow status (e.g. `awaiting_form`, `form_filled`, `sdk_redirected`, `completed`, `kyc_failed`, `manual_review`, `abandoned`). Do not build long-term external branching on raw statuses.",
            "example": "manual_review"
          },
          "decision": {
            "type": "string",
            "enum": [
              "approved",
              "rejected",
              "manual_review",
              "in_progress",
              "abandoned"
            ],
            "description": "Stable decision contract.",
            "example": "manual_review"
          },
          "session_expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-05-18T12:00:00Z"
          },
          "session_expiry_state": {
            "type": "string",
            "enum": [
              "active",
              "expired",
              "never"
            ],
            "example": "active"
          },
          "phone": {
            "type": "string",
            "example": "9841000000"
          },
          "email": {
            "type": "string",
            "example": "customer@example.com"
          },
          "flow_id": {
            "type": "string",
            "example": "savings_v1"
          },
          "flow_version": {
            "type": "integer",
            "example": 3
          },
          "current_step_id": {
            "type": "string",
            "example": "review"
          },
          "document_number": {
            "type": "string",
            "example": "123456789"
          },
          "sdk_verified": {
            "type": "boolean",
            "example": false
          },
          "kyc_fail_reason": {
            "type": "string",
            "example": ""
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-05-18T08:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-05-18T08:10:00Z"
          }
        }
      },
      "ApplicationPatch": {
        "type": "object",
        "description": "Body for Update Application Session Expiry. Send exactly one of the two fields.",
        "properties": {
          "expire_now": {
            "type": "boolean",
            "description": "When true, expires the live hosted URL immediately.",
            "example": true
          },
          "session_expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "New expiry (ISO 8601). Use null or an empty string to make the hosted URL non-expiring."
          }
        }
      },
      "RiskList": {
        "type": "object",
        "description": "A tenant-scoped risk list (blocklist).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "90f2c1a8-4f4e-4d1c-9b95-7dfda01965ba"
          },
          "name": {
            "type": "string",
            "example": "Sanctions"
          },
          "description": {
            "type": "string",
            "example": "Client-managed sanctions list"
          },
          "type": {
            "type": "string",
            "enum": [
              "phone",
              "email",
              "identifier"
            ],
            "description": "List type. Immutable after creation.",
            "example": "email"
          },
          "is_active": {
            "type": "boolean",
            "example": true
          },
          "entry_count": {
            "type": "integer",
            "example": 1200
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-05-18T08:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-05-18T08:00:00Z"
          }
        }
      },
      "RiskListCreate": {
        "type": "object",
        "required": [
          "name",
          "type"
        ],
        "description": "Body for Create Risk List.",
        "properties": {
          "name": {
            "type": "string",
            "example": "Sanctions"
          },
          "type": {
            "type": "string",
            "enum": [
              "phone",
              "email",
              "identifier"
            ],
            "description": "List type. Immutable after creation; also accepted as `block_type`.",
            "example": "identifier"
          },
          "description": {
            "type": "string",
            "example": "Client-managed OCR identifier list"
          },
          "is_active": {
            "type": "boolean",
            "default": true,
            "example": true
          }
        }
      },
      "ListEntry": {
        "type": "object",
        "description": "An entry within a risk list. `value` is the normalized stored value.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "4a4b1e25-7d9b-4d5f-8c5c-f6718794c747"
          },
          "value": {
            "type": "string",
            "example": "9812345678"
          },
          "comment": {
            "type": "string",
            "example": "Confirmed fraud source"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-05-18T08:00:00Z"
          }
        }
      },
      "ListEntryUpsert": {
        "type": "object",
        "required": [
          "value"
        ],
        "description": "Body for upserting a single list entry. The value is normalized by list type before storage.",
        "properties": {
          "value": {
            "type": "string",
            "description": "Raw value; normalized on write (phone → digits only, email → trimmed+lowercased, identifier → trimmed).",
            "example": "+977 9812-345678"
          },
          "comment": {
            "type": "string",
            "description": "Note, truncated to 500 chars.",
            "example": "Confirmed fraud source"
          }
        }
      },
      "BulkEntries": {
        "type": "object",
        "required": [
          "entries"
        ],
        "description": "Body carrying a batch of entries (used for bulk add and replace-all).",
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ListEntryUpsert"
            },
            "example": [
              {
                "value": "9812345678",
                "comment": "source A"
              },
              {
                "value": "bad@example.com",
                "comment": "source B"
              }
            ]
          }
        }
      },
      "BulkEntriesResult": {
        "type": "object",
        "description": "Result of a bulk add (idempotent; duplicates skipped by normalized value).",
        "properties": {
          "ok": {
            "type": "boolean",
            "example": true
          },
          "created": {
            "type": "integer",
            "example": 2
          },
          "skipped": {
            "type": "integer",
            "example": 0
          },
          "invalid": {
            "type": "integer",
            "example": 0
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row": {
                  "type": "integer",
                  "nullable": true
                },
                "error": {
                  "type": "string"
                }
              }
            },
            "example": []
          }
        }
      },
      "ApiV1Error": {
        "type": "object",
        "description": "Error body for /api/v1/sessions/ and /api/v1/applications/. Branch on the HTTP status first; treat `error` as the stable machine code.",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              false
            ],
            "example": false
          },
          "error": {
            "type": "string",
            "description": "Stable machine code, e.g. `invalid_request`, `invalid_token`, `invalid_jwt`, `insufficient_credits`, `flow_not_found`, `not_found`, `no_supported_fields`, `invalid_session_expires_at`.",
            "example": "not_found"
          },
          "detail": {
            "type": "string",
            "description": "Optional human-readable detail.",
            "example": ""
          }
        }
      },
      "ListError": {
        "type": "object",
        "description": "Error body for /api/v1/lists/* endpoints. Do not string-match the human-readable message; branch on the HTTP status.",
        "properties": {
          "error": {
            "type": "string",
            "example": "Not found."
          }
        }
      },
      "V3SessionCreate": {
        "type": "object",
        "description": "Body for creating a native-KYC verification session. All fields are optional.",
        "properties": {
          "workflow_id": {
            "type": "string",
            "format": "uuid",
            "description": "KYC workflow UUID (from `GET /v3/workflows/`). Tenant default when omitted."
          },
          "session_kind": {
            "type": "string",
            "enum": [
              "user"
            ],
            "default": "user",
            "description": "Session kind. Currently only `user`."
          },
          "vendor_data": {
            "type": "string",
            "description": "Your external user reference; doubles as the linked identity's `external_user_id`."
          },
          "callback_url": {
            "type": "string",
            "format": "uri",
            "description": "Where the applicant is redirected after completion."
          },
          "contact_email": {
            "type": "string",
            "format": "email",
            "description": "Applicant contact email; used for OTP + notifications."
          },
          "contact_phone": {
            "type": "string",
            "description": "Applicant contact phone; used for OTP + notifications."
          },
          "expires_in_hours": {
            "type": "integer",
            "minimum": 1,
            "maximum": 720,
            "default": 48,
            "description": "Hosted-URL lifetime in hours (1..720)."
          },
          "locale": {
            "type": "string",
            "maxLength": 8,
            "description": "UI language for the hosted flow (default resolves to the workflow's configured language, else `en`)."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form metadata stored on the session and echoed back."
          },
          "prefill": {
            "$ref": "#/components/schemas/V3SessionPrefill"
          },
          "reference": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional reference data the integrator already holds — matched against what the applicant captures. Keys (all optional): `face_image`, `document_image`, `signature_image` (base64, data-URI ok), `authenticated` (bool), `vendor_identity` (free dict)."
          }
        }
      },
      "V3SessionPrefill": {
        "type": "object",
        "description": "Applicant identity used to pre-populate the hosted flow. Only these keys are used; unknown keys are ignored.",
        "properties": {
          "full_name": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD."
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "nationality": {
            "type": "string",
            "description": "ISO-2 country code."
          },
          "address_line1": {
            "type": "string"
          },
          "address_line2": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "description": "ISO-2 country code."
          },
          "zip_code": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "V3Session": {
        "type": "object",
        "description": "A native-KYC verification session with its rolled-up decision and per-module results.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "session_kind": {
            "type": "string",
            "enum": [
              "user"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "not_started",
              "in_progress",
              "approved",
              "declined",
              "review",
              "expired",
              "abandoned"
            ]
          },
          "stage": {
            "type": "string",
            "description": "Coarse lifecycle stage (e.g. `created`, `in_progress`, `completed`)."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute hosted-flow URL to redirect the applicant to."
          },
          "vendor_data": {
            "type": "string",
            "description": "Your external user reference."
          },
          "workflow": {
            "type": [
              "string",
              "null"
            ],
            "description": "The workflow UUID this session runs, if any."
          },
          "decision": {
            "type": "string",
            "description": "Rolled-up decision.",
            "enum": [
              "in_progress",
              "approved",
              "declined",
              "review"
            ]
          },
          "decision_v3": {
            "$ref": "#/components/schemas/V3DecisionArrays"
          },
          "risk": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Risk summary, or null."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Trust-layer tag names on the session."
          },
          "credits_charged": {
            "type": "number",
            "description": "Total credits debited for this session."
          },
          "callback_url": {
            "type": "string",
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "V3SessionListItem": {
        "$ref": "#/components/schemas/V3Session"
      },
      "V3DecisionSummary": {
        "type": "object",
        "description": "Compact decision summary for a session.",
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "not_started",
              "in_progress",
              "approved",
              "declined",
              "review",
              "expired",
              "abandoned"
            ]
          },
          "decision": {
            "type": "string",
            "enum": [
              "in_progress",
              "approved",
              "declined",
              "review"
            ]
          },
          "decision_v3": {
            "$ref": "#/components/schemas/V3DecisionArrays"
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "V3DecisionArrays": {
        "type": "object",
        "description": "Module results grouped by feature family. Each array holds `V3ModuleResult` entries; empty arrays are present for features not run. `reviews` holds any manual-review record.",
        "properties": {
          "id_verifications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "liveness_checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "face_matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "aml_screenings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "nfc_verifications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "poa_verifications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "phone_verifications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "email_verifications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "ip_analyses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "database_validations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "questionnaire_responses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "registry_checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "document_verifications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "key_people_checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ModuleResult"
            }
          },
          "reviews": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3ReviewRecord"
            }
          }
        }
      },
      "V3ModuleResult": {
        "type": "object",
        "description": "A single module result as it appears inside a `decision_v3` array.",
        "properties": {
          "node_id": {
            "type": "string",
            "description": "The node id this result came from (falls back to the feature key)."
          },
          "status": {
            "type": "string",
            "description": "Module outcome, e.g. `passed`, `failed`, `review`, `pending`."
          },
          "score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Module confidence/score, if any."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Non-fatal warnings raised by the module."
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Module-specific result payload."
          }
        }
      },
      "V3ReviewRecord": {
        "type": "object",
        "description": "A manual-review record, present in `reviews` when the session carried review notes or a decline reason.",
        "properties": {
          "status": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "V3UpdateStatusBody": {
        "type": "object",
        "description": "Body for overriding a session's status.",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "not_started",
              "in_progress",
              "approved",
              "declined",
              "review",
              "expired",
              "abandoned"
            ],
            "description": "New session status. Must be a valid session status."
          },
          "reason": {
            "type": "string",
            "description": "Optional human-readable reason recorded with the override."
          }
        }
      },
      "V3SessionLogs": {
        "type": "object",
        "description": "A session's chronological event timeline.",
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid"
          },
          "logs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V3SessionLog"
            }
          }
        }
      },
      "V3SessionLog": {
        "type": "object",
        "description": "A single event on the session timeline.",
        "properties": {
          "event": {
            "type": "string",
            "description": "Event key, e.g. `session.created`."
          },
          "source": {
            "type": "string",
            "description": "Origin of the event, e.g. `api`, `flow`, `system`."
          },
          "message": {
            "type": "string",
            "description": "Human-readable message."
          },
          "detail": {
            "type": "object",
            "additionalProperties": true,
            "description": "Structured event detail."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "V3Checklist": {
        "type": "object",
        "description": "The session's evidence checklist and current stage.",
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid"
          },
          "stage": {
            "type": "string",
            "description": "Coarse lifecycle stage of the session."
          },
          "checklist": {
            "type": "array",
            "description": "Evidence checklist items.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "V3SessionTagList": {
        "type": "object",
        "description": "The trust-layer tags on a session.",
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "name": {
                  "type": "string"
                },
                "source": {
                  "type": "string"
                },
                "applied_by": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "V3TagBody": {
        "type": "object",
        "description": "Body for applying or removing a session tag.",
        "required": [
          "tag"
        ],
        "properties": {
          "tag": {
            "type": "string",
            "description": "The tag to apply or remove."
          }
        }
      },
      "V3TagApplied": {
        "type": "object",
        "description": "Result of applying a tag.",
        "properties": {
          "applied": {
            "type": "boolean"
          },
          "tag": {
            "type": "string",
            "description": "The normalized (lowercased, underscored) tag."
          }
        }
      },
      "V3TagRemoved": {
        "type": "object",
        "description": "Result of removing a tag.",
        "properties": {
          "removed": {
            "type": "boolean"
          }
        }
      },
      "V3Error": {
        "type": "object",
        "description": "Standard error body for `/v3/*` endpoints. `detail` is a stable machine code (e.g. `insufficient_credits`, `no_face_detected`) or a human-readable message — branch on the HTTP status first, then on the code.",
        "properties": {
          "detail": {
            "type": "string",
            "description": "Error code or message.",
            "example": "insufficient_credits"
          }
        },
        "required": [
          "detail"
        ]
      },
      "V3FeatureError": {
        "type": "object",
        "description": "Error body returned with HTTP 403 when a tool's feature is not enabled for the tenant. Carries the list of feature keys that must be enabled.",
        "properties": {
          "detail": {
            "type": "string",
            "example": "feature_not_enabled"
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Feature keys the tenant lacks.",
            "example": [
              "AML_CHECK"
            ]
          }
        },
        "required": [
          "detail"
        ]
      },
      "WorkflowSummary": {
        "type": "object",
        "description": "A KYC workflow available to the tenant.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Workflow UUID; use as `workflow_id` when creating a session.",
            "example": "b21c8f4e-7d3a-4c0e-9f11-2a6b5d0e9c34"
          },
          "name": {
            "type": "string",
            "example": "Standard KYC"
          },
          "status": {
            "type": "string",
            "description": "Workflow lifecycle status.",
            "example": "active"
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Enabled feature/module keys the workflow runs.",
            "example": [
              "ID_VERIFICATION",
              "LIVENESS",
              "FACE_MATCH",
              "AML_SCREENING"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "features"
        ]
      },
      "UsageReport": {
        "type": "object",
        "description": "Credits consumed grouped by workflow.",
        "properties": {
          "usage": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageReportEntry"
            }
          }
        },
        "required": [
          "usage"
        ]
      },
      "UsageReportEntry": {
        "type": "object",
        "properties": {
          "workflow": {
            "type": "string",
            "description": "Workflow name (or `\"-\"` when unassociated).",
            "example": "Standard KYC"
          },
          "credits": {
            "type": "number",
            "description": "Total credits consumed by this workflow (rounded to 4 dp).",
            "example": 318.5
          },
          "entries": {
            "type": "integer",
            "description": "Number of debit ledger rows counted.",
            "example": 127
          }
        },
        "required": [
          "workflow",
          "credits",
          "entries"
        ]
      },
      "CreditLedgerPage": {
        "type": "object",
        "description": "A page of credit ledger rows.",
        "properties": {
          "count": {
            "type": "integer",
            "description": "Total matching rows across all pages.",
            "example": 254
          },
          "page": {
            "type": "integer",
            "example": 1
          },
          "page_size": {
            "type": "integer",
            "example": 100
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreditLedgerEntry"
            }
          }
        },
        "required": [
          "count",
          "page",
          "page_size",
          "results"
        ]
      },
      "CreditLedgerEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "9c1f0a72-4e6b-4d21-8a90-1b2c3d4e5f60"
          },
          "delta": {
            "type": "string",
            "description": "Signed credit change as a decimal string (negative = debit, positive = top-up).",
            "example": "-2.50"
          },
          "reason": {
            "type": "string",
            "description": "Why the ledger row was written.",
            "example": "session_create"
          },
          "feature": {
            "type": "string",
            "description": "Feature key charged (empty for non-feature rows like top-ups).",
            "example": "SESSION_CREATE"
          },
          "session_id": {
            "type": "string",
            "description": "Linked verification session id, or empty string.",
            "example": "6f0e2b7a-9c1d-4e2f-8a0b-1c2d3e4f5a6b"
          },
          "application_id": {
            "type": "string",
            "description": "Linked application id, or empty string.",
            "example": ""
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-06-30T08:14:22.512000+00:00"
          }
        },
        "required": [
          "id",
          "delta",
          "reason",
          "feature",
          "session_id",
          "application_id",
          "created_at"
        ]
      },
      "AmlCheckRequest": {
        "type": "object",
        "description": "AML screening query. Provide either `full_name` or both `first_name` and `last_name`.",
        "properties": {
          "full_name": {
            "type": "string",
            "description": "Full name to screen.",
            "example": "Vladimir Petrov"
          },
          "first_name": {
            "type": "string",
            "description": "First name (alternative to `full_name`).",
            "example": "Vladimir"
          },
          "last_name": {
            "type": "string",
            "description": "Last name (alternative to `full_name`).",
            "example": "Petrov"
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "description": "Date of birth `YYYY-MM-DD` — corroborates matches.",
            "example": "1968-03-14"
          },
          "nationality": {
            "type": "string",
            "description": "ISO-2 nationality.",
            "example": "RU"
          },
          "country": {
            "type": "string",
            "description": "ISO-2 country of residence.",
            "example": "RU"
          },
          "document_number": {
            "type": "string",
            "description": "Identity/passport number to match.",
            "example": "7712345678"
          },
          "match_threshold": {
            "type": "number",
            "description": "Minimum candidate score to consider (0..1).",
            "example": 0.7
          }
        },
        "required": [
          "full_name"
        ]
      },
      "AmlCheckResult": {
        "type": "object",
        "description": "AML screening outcome. `matches` are confirmed hits, `possible_matches` are sub-threshold, `informational_matches` are notices (e.g. POI datasets).",
        "properties": {
          "risk_level": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low",
              "clear"
            ],
            "description": "high = confirmed hit; medium = only possible matches; low = only informational; clear = nothing found.",
            "example": "high"
          },
          "matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AmlMatch"
            }
          },
          "possible_matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AmlMatch"
            }
          },
          "informational_matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AmlMatch"
            }
          },
          "lists_checked": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Watchlists screened against.",
            "example": [
              "pep",
              "sanctions"
            ]
          },
          "provider": {
            "type": "string",
            "example": "watchlist"
          },
          "dataset": {
            "type": "string",
            "example": "default"
          },
          "algorithm": {
            "type": "string",
            "example": "logic-v1"
          },
          "threshold": {
            "type": "number",
            "example": 0.7
          }
        },
        "required": [
          "risk_level",
          "matches",
          "possible_matches",
          "informational_matches",
          "lists_checked",
          "provider",
          "dataset",
          "algorithm",
          "threshold"
        ]
      },
      "AmlMatch": {
        "type": "object",
        "description": "A single screened watchlist entity.",
        "properties": {
          "entity_id": {
            "type": "string",
            "example": "NK-9x8y7z"
          },
          "name": {
            "type": "string",
            "example": "Vladimir PETROV"
          },
          "schema": {
            "type": "string",
            "description": "FollowTheMoney schema (e.g. Person).",
            "example": "Person"
          },
          "score": {
            "type": "number",
            "description": "Match score 0..1.",
            "example": 0.94
          },
          "match": {
            "type": "boolean",
            "description": "Whether the engine considers this a confirmed match.",
            "example": true
          },
          "lists": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "sanctions"
            ]
          },
          "topics": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "sanction"
            ]
          },
          "datasets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "us_ofac_sdn",
              "eu_fsf"
            ]
          },
          "birth_date": {
            "type": "string",
            "example": "1968-03-14"
          },
          "countries": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "ru"
            ]
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "V. Petrov"
            ]
          },
          "nationalities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "ru"
            ]
          },
          "positions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": []
          },
          "gender": {
            "type": "string",
            "example": "male"
          },
          "addresses": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "Moscow, Russia"
            ]
          },
          "id_numbers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "7712345678"
            ]
          },
          "url": {
            "type": "string",
            "description": "Link to the full entity record.",
            "example": "https://www.opensanctions.org/entities/NK-9x8y7z/"
          },
          "uncorroborated": {
            "type": "boolean",
            "description": "Present and true when a name match lacks DOB corroboration on a non-sanctions list."
          }
        },
        "required": [
          "entity_id",
          "name",
          "score",
          "match",
          "lists",
          "datasets"
        ]
      },
      "FaceSearchRequest": {
        "type": "object",
        "properties": {
          "image_base64": {
            "type": "string",
            "description": "Base64-encoded JPEG or PNG probe photo (data-URI prefix allowed).",
            "example": "/9j/4AAQSkZJRgABAQEAYABgAAD..."
          },
          "threshold": {
            "type": "number",
            "description": "Minimum similarity to include (clamped 0.2..0.95). Default 0.35.",
            "example": 0.4
          },
          "top_k": {
            "type": "integer",
            "description": "Max results (clamped 1..50). Default 12.",
            "example": 10
          }
        },
        "required": [
          "image_base64"
        ]
      },
      "FaceSearchResult": {
        "type": "object",
        "properties": {
          "matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FaceSearchMatch"
            }
          },
          "threshold": {
            "type": "number",
            "description": "Effective threshold used.",
            "example": 0.4
          }
        },
        "required": [
          "matches",
          "threshold"
        ]
      },
      "FaceSearchMatch": {
        "type": "object",
        "properties": {
          "individual_id": {
            "type": "string",
            "format": "uuid",
            "example": "3c9a1e77-2b40-4d8f-9a11-6e5c4b3a2d10"
          },
          "name": {
            "type": "string",
            "example": "Aarav Sharma"
          },
          "external_user_id": {
            "type": "string",
            "description": "Your external reference for the identity (may be empty).",
            "example": "user-ref-123"
          },
          "status": {
            "type": "string",
            "description": "Identity status.",
            "example": "active"
          },
          "kyc_status": {
            "type": "string",
            "description": "Identity KYC status.",
            "example": "approved"
          },
          "score": {
            "type": "number",
            "description": "Facial similarity 0..1.",
            "example": 0.8123
          },
          "thumb": {
            "type": "string",
            "description": "URL to the matched face thumbnail (may be empty).",
            "example": "https://cdn.example.com/faces/3c9a1e77.jpg"
          }
        },
        "required": [
          "individual_id",
          "name",
          "score"
        ]
      },
      "SignatureVerifyRequest": {
        "type": "object",
        "properties": {
          "reference_base64": {
            "type": "string",
            "description": "Base64-encoded reference signature (JPEG/PNG).",
            "example": "iVBORw0KGgoAAAANSUhEUg..."
          },
          "probe_base64": {
            "type": "string",
            "description": "Base64-encoded probe signature (JPEG/PNG).",
            "example": "iVBORw0KGgoAAAANSUhEUg..."
          },
          "threshold": {
            "type": "number",
            "description": "Match threshold (clamped 0.2..0.95). Default 0.62.",
            "example": 0.62
          }
        },
        "required": [
          "reference_base64",
          "probe_base64"
        ]
      },
      "SignatureVerifyResult": {
        "type": "object",
        "properties": {
          "score": {
            "type": "number",
            "description": "Signature similarity 0..1.",
            "example": 0.71
          },
          "verdict": {
            "type": "string",
            "enum": [
              "match",
              "review",
              "mismatch"
            ],
            "description": "match ≥ threshold; review ≥ 0.75×threshold; mismatch below.",
            "example": "match"
          },
          "matched": {
            "type": "boolean",
            "example": true
          },
          "threshold": {
            "type": "number",
            "description": "Effective threshold used.",
            "example": 0.62
          },
          "engine": {
            "type": "string",
            "description": "Comparison engine identifier.",
            "example": "obsidian"
          },
          "reason": {
            "type": "string",
            "description": "Present when the comparator returned a non-scored reason instead of a verdict."
          }
        },
        "required": [
          "score",
          "verdict",
          "matched"
        ]
      },
      "SignatureSearchRequest": {
        "type": "object",
        "properties": {
          "image_base64": {
            "type": "string",
            "description": "Base64-encoded probe signature (JPEG/PNG).",
            "example": "iVBORw0KGgoAAAANSUhEUg..."
          },
          "threshold": {
            "type": "number",
            "description": "Minimum similarity to include (clamped 0.2..0.95). Default 0.6.",
            "example": 0.6
          },
          "top_k": {
            "type": "integer",
            "description": "Max results (clamped 1..50). Default 12.",
            "example": 12
          }
        },
        "required": [
          "image_base64"
        ]
      },
      "SignatureSearchResult": {
        "type": "object",
        "properties": {
          "matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignatureSearchMatch"
            }
          },
          "threshold": {
            "type": "number",
            "example": 0.6
          },
          "beta": {
            "type": "boolean",
            "description": "Always true — this endpoint is beta.",
            "example": true
          }
        },
        "required": [
          "matches",
          "threshold",
          "beta"
        ]
      },
      "SignatureSearchMatch": {
        "type": "object",
        "properties": {
          "individual_id": {
            "type": "string",
            "format": "uuid",
            "example": "3c9a1e77-2b40-4d8f-9a11-6e5c4b3a2d10"
          },
          "name": {
            "type": "string",
            "example": "Aarav Sharma"
          },
          "external_user_id": {
            "type": "string",
            "example": "user-ref-123"
          },
          "status": {
            "type": "string",
            "example": "active"
          },
          "score": {
            "type": "number",
            "description": "Signature similarity 0..1.",
            "example": 0.7642
          },
          "thumb": {
            "type": "string",
            "description": "URL to the enrolled specimen thumbnail (may be empty).",
            "example": "https://cdn.example.com/signatures/abc123.png"
          }
        },
        "required": [
          "individual_id",
          "name",
          "score"
        ]
      },
      "SignatureEnrollRequest": {
        "type": "object",
        "description": "Enroll a signature specimen for an identity. Supply exactly one of `individual_id` or `external_user_id`.",
        "properties": {
          "individual_id": {
            "type": "string",
            "format": "uuid",
            "description": "Target identity UUID.",
            "example": "3c9a1e77-2b40-4d8f-9a11-6e5c4b3a2d10"
          },
          "external_user_id": {
            "type": "string",
            "description": "Target identity by your external reference (most recent match wins).",
            "example": "user-ref-123"
          },
          "image_base64": {
            "type": "string",
            "description": "Base64-encoded signature image (JPEG/PNG).",
            "example": "iVBORw0KGgoAAAANSUhEUg..."
          },
          "label": {
            "type": "string",
            "description": "Optional label for the specimen (≤120 chars).",
            "example": "loan-application-2026"
          }
        },
        "required": [
          "image_base64"
        ]
      },
      "SignatureEnrollResult": {
        "type": "object",
        "properties": {
          "sample_id": {
            "type": "string",
            "format": "uuid",
            "description": "Id of the newly enrolled specimen.",
            "example": "a1b2c3d4-e5f6-4071-8a9b-0c1d2e3f4a5b"
          },
          "individual_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identity the specimen was added to.",
            "example": "3c9a1e77-2b40-4d8f-9a11-6e5c4b3a2d10"
          }
        },
        "required": [
          "sample_id",
          "individual_id"
        ]
      },
      "DocumentExtractRequest": {
        "type": "object",
        "properties": {
          "image_base64": {
            "type": "string",
            "description": "Base64-encoded JPEG or PNG of the identity document (data-URI prefix allowed).",
            "example": "/9j/4AAQSkZJRgABAQEAYABgAAD..."
          }
        },
        "required": [
          "image_base64"
        ]
      },
      "DocumentExtractResult": {
        "type": "object",
        "description": "OCR-extracted document fields plus quality signals. Extra keys (`detected_type_display`, `generic_extraction`, `extraction_note`, `full_text`) appear on the best-effort generic path.",
        "properties": {
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Extracted field name → value.",
            "example": {
              "full_name": "AARAV SHARMA",
              "document_number": "P1234567",
              "date_of_birth": "1990-04-12"
            }
          },
          "translated_fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Romanised / English read of the same fields (non-Latin documents).",
            "example": {
              "full_name": "Aarav Sharma"
            }
          },
          "detected_type": {
            "type": "string",
            "nullable": true,
            "description": "Detected document type.",
            "example": "passport"
          },
          "confidence": {
            "type": "number",
            "nullable": true,
            "description": "OCR confidence 0..1.",
            "example": 0.97
          },
          "is_document_valid": {
            "type": "boolean",
            "nullable": true,
            "description": "Whether the document passed structural validity checks.",
            "example": true
          },
          "has_portrait": {
            "type": "boolean",
            "description": "Whether a portrait/face was found on the document.",
            "example": true
          },
          "detected_type_display": {
            "type": "string",
            "description": "Human-readable type label (generic/refined path).",
            "example": "Disability ID"
          },
          "generic_extraction": {
            "type": "boolean",
            "description": "True when the best-effort generic extraction path was used.",
            "example": true
          },
          "extraction_note": {
            "type": "string",
            "description": "Caveat present on the generic path.",
            "example": "Generic extraction — no trained template for this document type; fields are best-effort."
          },
          "full_text": {
            "type": "string",
            "description": "Raw OCR text (generic path only)."
          }
        },
        "required": [
          "fields",
          "translated_fields",
          "detected_type",
          "confidence",
          "is_document_valid",
          "has_portrait"
        ]
      }
    }
  }
}