{
  "openapi": "3.1.0",
  "info": {
    "title": "FaceLock Integrator Verification API",
    "version": "1.0.0",
    "description": "Server-to-server ad-hoc and browser proof session API for integrators. Browser session hyperlinks use a minimal proof-portal URL (`?referenceId=` only); operation, FaceTec refs, and tenant branding load server-side via challenge-context. Optional `callbackData` (arbitrary JSON) may be supplied on POST /sessions; it is stored, returned by GET /sessions/{referenceId}, and posted to your webhook on completion/fail/expire. If omitted, `callbackData` is null. For browser IDV sessions, GET /sessions/{referenceId} and completion webhooks include `idvDisposition` and `idvVerificationMetrics` (applicant name/DOB when learned from the scanned ID). Webhook event types and payload schemas are documented under components (ChallengeCallbackPayload, WebhookEventType) and in /developer/#events. Completed verifications are logged to UsageLog and aggregated for Reseller billing; billable usage is reported to Stripe Metronome (MfaUserMetricId, VerificationMetricId). Stripe handles invoicing only."
  },
  "servers": [
    { "url": "https://partner.facelock.id", "description": "Production" },
    { "url": "https://partner-dev.facelock.id", "description": "Development" }
  ],
  "paths": {
    "/api/v1/verifications/adhoc": {
      "post": {
        "summary": "Create ad-hoc verification challenge",
        "description": "Push an ad-hoc liveness challenge to an enrolled Authenticator user. Identify the target with tenantUserKey (opaque FaceLock key, e.g. tu-…) and/or userId (registered contact: email or E.164 phone). At least one is required; when both are sent, tenantUserKey wins. Resolution is always scoped to the authenticated tenant. Response includes the resolved tenantUserKey (and userId when known). Uniform errors: 'Target user not found for this tenant' and 'Target user is not ready for ad-hoc verification'.",
        "security": [{ "tenantHeader": [], "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["purpose"],
                "properties": {
                  "tenantUserKey": {
                    "type": "string",
                    "description": "Opaque TenantUser key (e.g. tu-abc…). Prefer when known."
                  },
                  "userId": {
                    "type": "string",
                    "description": "Registered contact for the tenant user: normalized email or E.164 phone. Resolved within the authenticated tenant only."
                  },
                  "purpose": { "type": "string" },
                  "initiatorReference": { "type": "string" },
                  "expiresInMinutes": { "type": "integer", "default": 5 }
                }
              },
              "examples": {
                "byUserId": {
                  "summary": "By registered email",
                  "value": {
                    "userId": "jane@example.com",
                    "purpose": "Wire transfer approval",
                    "expiresInMinutes": 5
                  }
                },
                "byTenantUserKey": {
                  "summary": "By opaque key",
                  "value": {
                    "tenantUserKey": "tu-abc",
                    "purpose": "Wire transfer approval"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Challenge created (live keys; ZeroProof success)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "referenceId": { "type": "string" },
                    "tenantUserKey": { "type": "string" },
                    "userId": { "type": "string", "nullable": true },
                    "status": { "type": "string" },
                    "expiresAt": { "type": "string", "format": "date-time" },
                    "pushSent": { "type": "integer" },
                    "pushTotal": { "type": "integer" },
                    "warning": { "type": "string", "nullable": true }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Sandbox challenge stub created"
          },
          "400": {
            "description": "Validation or eligibility failure (missing identity, not found, not ready, invalid contact format)"
          },
          "401": { "description": "Invalid or missing credentials" }
        }
      }
    },
    "/api/v1/verifications/adhoc/{referenceId}": {
      "get": {
        "summary": "Poll challenge status",
        "security": [{ "tenantHeader": [], "bearerAuth": [] }],
        "parameters": [
          {
            "name": "referenceId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Challenge status",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AdHocChallengeStatusResponse" }
              }
            }
          },
          "404": { "description": "Challenge not found for tenant" }
        }
      }
    },
    "/api/v1/verifications/sessions": {
      "post": {
        "summary": "Create browser proof session",
        "description": "Returns a minimal proof.facelock.live hyperlink (`?referenceId=` only) for liveness, enrollment, verification, or idv in the browser. Operation and branding are resolved server-side when the applicant opens the link. Provide optional `callbackData` (any JSON object) at create time; it is stored, returned on GET /sessions/{referenceId}, and included in completion webhooks. If omitted, `callbackData` is null. Optional `redirectOnCompletion` + `redirectUrl` cause the proof portal to navigate to your URL (with placeholders) after FaceTec finishes instead of showing the static completion page.",
        "tags": ["Verifications"],
        "security": [{ "tenantHeader": [], "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["operation", "purpose"],
                "properties": {
                  "operation": {
                    "type": "string",
                    "enum": ["liveness", "enrollment", "verification", "idv"]
                  },
                  "purpose": { "type": "string" },
                  "tenantUserKey": { "type": "string" },
                  "userId": { "type": "string" },
                  "displayName": { "type": "string" },
                  "initiatorReference": { "type": "string" },
                  "expiresInMinutes": { "type": "integer", "default": 30, "minimum": 5, "maximum": 10080 },
                  "callbackData": { "type": "object", "additionalProperties": true },
                  "redirectOnCompletion": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, proof portal redirects to redirectUrl after FaceTec terminal status."
                  },
                  "redirectUrl": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "Absolute https URL template. Placeholders: {{referenceId}}, {{sdkStatus}}, {{sdkStatusCode}}, {{operation}}, {{status}}, {{externalDatabaseRefId}}. Required when redirectOnCompletion is true."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Browser session created with minimal hyperlink",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BrowserSessionCreateResponse" },
                "example": {
                  "referenceId": "a1b2c3d4e5f6",
                  "hyperlink": "https://proof.facelock.live/?referenceId=a1b2c3d4e5f6",
                  "operation": "idv",
                  "status": "pending",
                  "expiresAt": "2026-06-17T18:30:00Z",
                  "callbackData": { "operation": "idv", "purpose": "University application pre-check", "initiatorReference": "integrator:..." }
                }
              }
            }
          },
          "401": { "description": "Invalid or missing credentials" }
        }
      }
    },
    "/api/v1/verifications/sessions/{referenceId}": {
      "get": {
        "summary": "Poll browser proof session status",
        "description": "Returns the current status of a browser proof session. For `operation=idv`, when FaceTec / review data is available the response includes `idvDisposition` and `idvVerificationMetrics` (applicant biographics such as `documentFullName`, `givenName`/`familyName`, `firstName`/`lastName`, and `dateOfBirth` are null until learned from the scanned ID).",
        "tags": ["Verifications"],
        "security": [{ "tenantHeader": [], "bearerAuth": [] }],
        "parameters": [
          {
            "name": "referenceId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Session status",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BrowserSessionStatusResponse" },
                "example": {
                  "referenceId": "a1b2c3d4e5f6",
                  "operation": "idv",
                  "status": "completed",
                  "completedAt": "2026-06-17T18:05:11Z",
                  "hyperlink": "https://proof.facelock.live/?referenceId=a1b2c3d4e5f6",
                  "callbackData": { "adhocData": "idv", "purpose": "callback Envoc live integrator test", "extraData": "integrator:envoc-com" },
                  "idvDisposition": {
                    "disposition": "accepted",
                    "reason": "manual_acceptance",
                    "reasonLabel": "Accepted by reviewer",
                    "isAutomatic": false,
                    "isFinal": true,
                    "fraudFlag": false,
                    "matchLevel": 6,
                    "livenessProven": true,
                    "launchId": "launch-abc"
                  },
                  "idvVerificationMetrics": {
                    "matchLevel": 6,
                    "matchLevelLabel": "1:100,000",
                    "livenessProven": true,
                    "documentFullName": "Jane Doe",
                    "givenName": "Jane",
                    "familyName": "Doe",
                    "firstName": "Jane",
                    "lastName": "Doe",
                    "dateOfBirth": "1990-01-15",
                    "documentType": "DriversLicense"
                  }
                }
              }
            }
          },
          "404": { "description": "Session not found for this tenant" },
          "401": { "description": "Invalid or missing credentials" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "tenantHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-FaceLock-Tenant-Key"
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "schemas": {
      "BrowserSessionCallbackData": {
        "type": "object",
        "description": "Optional arbitrary JSON object supplied by the integrator at creation time. If omitted on POST, the value is null. Returned on completion webhooks. Never included in the applicant hyperlink query string."
      },
      "BrowserSessionCreateResponse": {
        "type": "object",
        "properties": {
          "referenceId": { "type": "string" },
          "hyperlink": {
            "type": "string",
            "format": "uri",
            "description": "Minimal proof-portal URL containing only referenceId in the query string",
            "pattern": "^https://[^/]+/\\?referenceId=[^&]+$"
          },
          "operation": { "type": "string", "enum": ["liveness", "enrollment", "verification", "idv"] },
          "status": { "type": "string", "enum": ["pending"] },
          "expiresAt": { "type": "string", "format": "date-time" },
          "sandbox": { "type": "boolean", "description": "Present when using a sandbox API key" },
          "message": { "type": "string", "description": "Sandbox explanatory message" },
          "callbackData": {
            "type": "object",
            "description": "Optional arbitrary JSON object supplied at creation time (or null if omitted). Returned on create response, poll, and completion webhooks."
          },
          "redirectOnCompletion": {
            "type": "boolean",
            "description": "Echo of create-time flag when completion redirect is configured"
          },
          "redirectUrl": {
            "type": "string",
            "description": "Echo of create-time redirect URL template when redirectOnCompletion is true"
          }
        },
        "required": ["referenceId", "hyperlink", "operation", "status"]
      },
      "BrowserSessionStatusResponse": {
        "type": "object",
        "properties": {
          "referenceId": { "type": "string" },
          "tenantKey": { "type": "string" },
          "operation": { "type": "string", "enum": ["liveness", "enrollment", "verification", "idv"] },
          "status": { "type": "string", "enum": ["pending", "completed", "expired", "failed"] },
          "purpose": { "type": "string" },
          "initiatorReference": { "type": "string" },
          "tenantUserKey": { "type": "string", "nullable": true },
          "userId": { "type": "string", "nullable": true },
          "completedAt": { "type": "string", "format": "date-time", "nullable": true },
          "expiresAt": { "type": "string", "format": "date-time" },
          "hyperlink": {
            "type": "string",
            "format": "uri",
            "description": "Minimal proof-portal URL while session is pending"
          },
          "sandbox": { "type": "boolean" },
          "callbackData": {
            "type": "object",
            "nullable": true,
            "description": "Callback data stored at creation time. Returned on poll and webhooks."
          },
          "idvDisposition": {
            "allOf": [{ "$ref": "#/components/schemas/IdvDisposition" }],
            "nullable": true,
            "description": "Present for browser IDV sessions when disposition/review data exists; null for non-IDV operations or before IDV data is available."
          },
          "idvVerificationMetrics": {
            "allOf": [{ "$ref": "#/components/schemas/IdvVerificationMetrics" }],
            "nullable": true,
            "description": "Sanitized FaceTec IDV signals for browser IDV sessions. Applicant biographics are null until learned from the scanned ID."
          }
        },
        "required": ["referenceId", "operation", "status"]
      },
      "AdHocChallengeStatusResponse": {
        "type": "object",
        "description": "Poll response for a mobile ad-hoc verification challenge.",
        "properties": {
          "referenceId": { "type": "string" },
          "status": {
            "type": "string",
            "enum": ["pending", "completed", "expired", "failed"]
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sandbox": {
            "type": "boolean",
            "description": "Present when using a sandbox API key"
          }
        },
        "required": ["referenceId", "status"]
      },
      "WebhookEventType": {
        "type": "string",
        "description": "Integrator completion webhook event name (see /developer/#events).",
        "enum": [
          "challenge.completed",
          "challenge.failed",
          "challenge.expired",
          "challenge.idv_completed",
          "challenge.disposition.updated"
        ]
      },
      "ChallengeCallbackPayload": {
        "type": "object",
        "description": "JSON body POSTed to the tenant completion webhook. Delivery secrets are never included. Sandbox harness replays use the same shape plus header X-Sandbox-Webhook-Event: true.",
        "properties": {
          "referenceId": { "type": "string" },
          "tenantKey": { "type": "string" },
          "event": { "$ref": "#/components/schemas/WebhookEventType" },
          "operation": {
            "type": "string",
            "enum": ["liveness", "enrollment", "verification", "idv"]
          },
          "status": {
            "type": "string",
            "enum": ["pending", "completed", "expired", "failed"]
          },
          "success": { "type": "boolean" },
          "completedAt": { "type": "string", "format": "date-time" },
          "callbackData": {
            "type": "object",
            "nullable": true,
            "description": "Create-time callbackData snapshot, or null if omitted"
          },
          "launchId": { "type": "string", "nullable": true },
          "idvDisposition": {
            "allOf": [{ "$ref": "#/components/schemas/IdvDisposition" }],
            "nullable": true
          },
          "idvVerificationMetrics": {
            "allOf": [{ "$ref": "#/components/schemas/IdvVerificationMetrics" }],
            "nullable": true
          }
        },
        "required": ["referenceId", "tenantKey", "event", "operation", "status", "success", "completedAt"]
      },
      "IdvDisposition": {
        "type": "object",
        "description": "IDV review outcome for a browser IDV session.",
        "properties": {
          "disposition": {
            "type": "string",
            "enum": ["passed", "needs_review", "accepted", "rejected"],
            "description": "passed normalizes to accepted for success checks."
          },
          "reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "face_did_not_match",
              "definite_fraud",
              "liveness_not_proven",
              "spoof_detected",
              "idv_failed",
              "manual_rejection",
              "manual_acceptance",
              "match_level_below_threshold",
              "barcode_check_failed",
              "quality_check_failed",
              "face_on_doc_mismatch",
              "text_on_doc_mismatch",
              "ocr_template_mismatch",
              "unexpected_media",
              "incomplete_results"
            ]
          },
          "reasonLabel": { "type": "string", "nullable": true },
          "isAutomatic": { "type": "boolean" },
          "isFinal": { "type": "boolean" },
          "fraudFlag": { "type": "boolean" },
          "reviewedBy": { "type": "string", "nullable": true },
          "reviewedAt": { "type": "string", "format": "date-time", "nullable": true },
          "matchLevel": { "type": "integer", "nullable": true },
          "livenessProven": { "type": "boolean", "nullable": true },
          "launchId": { "type": "string", "nullable": true },
          "presetUsed": { "type": "string", "nullable": true },
          "policyVersion": { "type": "integer", "nullable": true },
          "autoDecisionJson": {
            "type": "string",
            "nullable": true,
            "description": "Optional serialized auto-decision / audit signals snapshot"
          }
        }
      },
      "IdvVerificationMetrics": {
        "type": "object",
        "description": "Key FaceTec IDV verification signals and applicant biographics (when learned).",
        "properties": {
          "matchLevel": { "type": "integer", "nullable": true },
          "matchLevelLabel": { "type": "string", "nullable": true, "description": "Human-readable match level (e.g. 1:100,000)." },
          "livenessProven": { "type": "boolean", "nullable": true },
          "didMatchOcrTemplate": { "type": "boolean", "nullable": true },
          "barcodeStatus": { "type": "string", "nullable": true },
          "tamperingDetected": { "type": "boolean", "nullable": true },
          "spoofAttemptDetected": { "type": "boolean", "nullable": true },
          "documentFullName": {
            "type": "string",
            "nullable": true,
            "description": "Applicant full name from barcode/OCR when available."
          },
          "givenName": {
            "type": "string",
            "nullable": true,
            "description": "Given / first name from barcode/OCR when available."
          },
          "familyName": {
            "type": "string",
            "nullable": true,
            "description": "Family / last name from barcode/OCR when available."
          },
          "firstName": {
            "type": "string",
            "nullable": true,
            "description": "Alias of givenName for integrators that prefer first/last naming."
          },
          "lastName": {
            "type": "string",
            "nullable": true,
            "description": "Alias of familyName for integrators that prefer first/last naming."
          },
          "dateOfBirth": {
            "type": "string",
            "nullable": true,
            "description": "Applicant date of birth when available (prefer ISO YYYY-MM-DD)."
          },
          "documentType": { "type": "string", "nullable": true },
          "expirationDate": { "type": "string", "nullable": true },
          "faceTecSdkVersion": { "type": "string", "nullable": true }
        }
      },
      "MetronomeUsageReporting": {
        "type": "object",
        "description": "FaceLock reports aggregated Reseller usage to Stripe Metronome ingest. UsageLog rows are the audit source for verification volume in the current billing period.",
        "properties": {
          "mfaUserMetricId": {
            "type": "string",
            "description": "Metronome billable metric for peak concurrent MFA users (Metronome:MfaUserMetricId)"
          },
          "verificationMetricId": {
            "type": "string",
            "description": "Metronome billable metric for verification delta (Metronome:VerificationMetricId)"
          },
          "customerId": {
            "type": "string",
            "description": "Stripe Customer ID used as Metronome customer_id on ingest"
          },
          "transactionId": {
            "type": "string",
            "description": "Idempotency key per usage report (reportKey-mfa / reportKey-verif)"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Verifications",
      "description": "Ad-hoc and browser proof identity verification sessions"
    },
    {
      "name": "Webhooks",
      "description": "Outbound completion webhook events (ChallengeCallbackPayload). See /developer/#events"
    },
    {
      "name": "Billing",
      "description": "Usage is aggregated from UsageLog and reported to Stripe Metronome; Stripe handles invoicing"
    }
  ]
}
