> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Passive Liveness

> Detect presentation attacks — printed photos, screen replays, masks, deepfakes — from a single face image. No video, motion, or user interaction required.

**How it works.** The image is analyzed by a biometric model; the largest detected face is evaluated and a liveness `score` (0–100) is computed. `status` is `Declined` when any `error`-severity warning fires:
- `NO_FACE_DETECTED` — the liveness model found no face,
- `LOW_LIVENESS_SCORE` — score at or below `face_liveness_score_decline_threshold` (default `30`),
- `LIVENESS_FACE_ATTACK` — the model detected a presentation attack,
- `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` — the face matches an entry on your face blocklist.

`MULTIPLE_FACES_DETECTED` is a `warning`-severity signal (does not decline), and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` are `information`-severity signals that the same face already appeared in another approved session (they never decline). `face_quality` and `face_luminance` (0–100, nullable) describe capture quality.

**Blocklist and duplicate screening.** Runs on **every** call — with or without `save_api_request` — by matching the detected face against your application's face search index. Prior faces with the same `vendor_data` are excluded from the entire index search — so re-running liveness for the same user does not trigger `DUPLICATED_FACE`, and a blocklisted face originating from a session with the caller's own `vendor_data` is also NOT flagged (`FACE_IN_BLOCKLIST` screening is bypassed for same-`vendor_data` faces).

**Billing.** Each `200` response consumes one Passive Liveness API credit (standalone APIs have no free tier). Insufficient balance returns `403` before any image processing.

**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook), the reference image and face crop are stored, and the detected face is **enrolled into your face search index** so later Face Search calls and duplicate checks can match it. When `false`, the call is one-shot: nothing is stored and the face is not enrolled (blocklist/duplicate screening still runs).

**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.

**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{"detail": "You do not have permission to perform this action."}`) — this API never returns `401`.

**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.



## OpenAPI

````yaml POST /v3/passive-liveness/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: Identity verification API. Authenticate with x-api-key header.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/passive-liveness/:
    post:
      tags:
        - Standalone APIs
      summary: Passive Liveness
      description: >-
        Detect presentation attacks — printed photos, screen replays, masks,
        deepfakes — from a single face image. No video, motion, or user
        interaction required.


        **How it works.** The image is analyzed by a biometric model; the
        largest detected face is evaluated and a liveness `score` (0–100) is
        computed. `status` is `Declined` when any `error`-severity warning
        fires:

        - `NO_FACE_DETECTED` — the liveness model found no face,

        - `LOW_LIVENESS_SCORE` — score at or below
        `face_liveness_score_decline_threshold` (default `30`),

        - `LIVENESS_FACE_ATTACK` — the model detected a presentation attack,

        - `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` — the face matches
        an entry on your face blocklist.


        `MULTIPLE_FACES_DETECTED` is a `warning`-severity signal (does not
        decline), and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` are
        `information`-severity signals that the same face already appeared in
        another approved session (they never decline). `face_quality` and
        `face_luminance` (0–100, nullable) describe capture quality.


        **Blocklist and duplicate screening.** Runs on **every** call — with or
        without `save_api_request` — by matching the detected face against your
        application's face search index. Prior faces with the same `vendor_data`
        are excluded from the entire index search — so re-running liveness for
        the same user does not trigger `DUPLICATED_FACE`, and a blocklisted face
        originating from a session with the caller's own `vendor_data` is also
        NOT flagged (`FACE_IN_BLOCKLIST` screening is bypassed for
        same-`vendor_data` faces).


        **Billing.** Each `200` response consumes one Passive Liveness API
        credit (standalone APIs have no free tier). Insufficient balance returns
        `403` before any image processing.


        **Session persistence and face enrollment (`save_api_request`, default
        `true`).** When `true`, the call is persisted as an API-type session
        (Business Console, `GET /v3/session/{sessionId}/decision/` via the
        returned `request_id`, `status.updated` webhook), the reference image
        and face crop are stored, and the detected face is **enrolled into your
        face search index** so later Face Search calls and duplicate checks can
        match it. When `false`, the call is one-shot: nothing is stored and the
        face is not enrolled (blocklist/duplicate screening still runs).


        **Sandbox.** Sandbox API keys skip all processing and billing: after
        request validation (malformed input still returns `400`), the endpoint
        returns a static `Approved` mock payload, no session is persisted, and
        no credits are consumed.


        **Authentication.** Send your application's API key in the `x-api-key`
        header. Missing or invalid credentials return `403` (`{"detail": "You do
        not have permission to perform this action."}`) — this API never returns
        `401`.


        **Rate limit.** Shared write budget of 300 requests/min per API key
        across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.
      operationId: post_v3passive-liveness
      parameters: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - user_image
              properties:
                user_image:
                  type: string
                  format: binary
                  description: >-
                    Front-facing face image (e.g. a selfie). Allowed extensions:
                    `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size:
                    **5 MB** (larger files are rejected with `400`). Images are
                    automatically compressed to ~0.5 MB before processing, so
                    very high resolutions do not improve accuracy. The image
                    should contain a single subject — when several faces are
                    present, the largest one is evaluated and a
                    `MULTIPLE_FACES_DETECTED` warning is added.
                face_liveness_score_decline_threshold:
                  type: number
                  format: float
                  default: 30
                  minimum: 0
                  maximum: 100
                  description: >-
                    Liveness score threshold (0–100). A computed score **at or
                    below** this value emits a `LOW_LIVENESS_SCORE` warning and
                    sets `status` to `Declined`. Default `30`; raise it for a
                    stricter anti-spoofing posture. Values outside 0–100 return
                    `400`.
                  example: 30
                rotate_image:
                  type: boolean
                  default: false
                  description: >-
                    When `true`, the service tries 90-degree rotations of the
                    input and uses the orientation that yields the best face
                    detection. Useful when EXIF orientation is missing. Adds
                    latency.
                  example: false
                save_api_request:
                  type: boolean
                  default: true
                  description: >-
                    When `true` (default), persists the call as an API-type
                    session, stores the face images, emits a `status.updated`
                    webhook, and enrolls the detected face into your face search
                    index. When `false`, the call is one-shot — nothing is
                    stored and the face is not enrolled. Blocklist and duplicate
                    screening run either way.
                  example: true
                vendor_data:
                  type: string
                  description: >-
                    Optional opaque identifier (your user id, email, UUID…)
                    stored on the persisted session and echoed back. Also used
                    to exclude prior sessions with the same `vendor_data` from
                    the duplicate-face check, so re-verifying the same user does
                    not raise `DUPLICATED_FACE`.
                  example: user-123
                metadata:
                  type: object
                  additionalProperties: true
                  description: >-
                    Optional JSON object stored with the session (when
                    `save_api_request=true`) and echoed back. In multipart
                    requests, send it as a JSON-encoded string field — it is
                    parsed into an object.
                  example:
                    flow: withdrawal
            example:
              user_image: (binary JPEG/PNG selfie)
              face_liveness_score_decline_threshold: 30
              rotate_image: false
              save_api_request: true
              vendor_data: user-123
              metadata:
                flow: withdrawal
      responses:
        '200':
          description: >-
            Passive liveness completed. `liveness.score` is the model's 0–100
            confidence that the image shows a live person; `status` is
            `Approved` unless an `error`-severity warning fired. A spoofed or
            blocklisted face still returns `200` — inspect `liveness.status` and
            `liveness.warnings`, not just the HTTP code. When
            `save_api_request=true`, `request_id` is the persisted session id.
          content:
            application/json:
              examples:
                Approved:
                  summary: Live face — no warnings
                  value:
                    request_id: a1b2c3d4-e5f6-7890-1234-567890abcdef
                    liveness:
                      status: Approved
                      method: PASSIVE
                      score: 95
                      user_image:
                        entities:
                          - bbox:
                              - 661
                              - 728
                              - 1688
                              - 2188
                            confidence: 0.732973
                            age: 26.91
                            gender: male
                            race: null
                        best_angle: 0
                      warnings: []
                      face_quality: 84.21
                      face_luminance: 50.33
                    vendor_data: user-123
                    metadata: null
                    created_at: '2026-06-12T01:04:42.763237+00:00'
                Declined - presentation attack:
                  summary: The model flagged a spoof (print, replay, mask…)
                  value:
                    request_id: f1c8c9b2-d3e4-4f5a-9b8c-7d6e5f4a3b2c
                    liveness:
                      status: Declined
                      method: PASSIVE
                      score: 12.5
                      user_image:
                        entities:
                          - bbox:
                              - 100
                              - 120
                              - 420
                              - 540
                            confidence: 0.81
                            age: 30.1
                            gender: female
                            race: null
                        best_angle: 0
                      warnings:
                        - risk: LOW_LIVENESS_SCORE
                          feature: LIVENESS
                          additional_data: null
                          log_type: error
                          short_description: Low liveness score
                          long_description: >-
                            The liveness check resulted in a low score,
                            indicating potential use of non-live facial
                            representations or poor-quality biometric data.
                        - risk: LIVENESS_FACE_ATTACK
                          feature: LIVENESS
                          additional_data: null
                          log_type: error
                          short_description: Liveness Face Attack
                          long_description: >-
                            The system detected a potential attempt to bypass
                            the liveness check.
                      face_quality: 72.4
                      face_luminance: 48.2
                    vendor_data: user-123
                    metadata: null
                    created_at: '2026-06-12T01:04:42.763237+00:00'
                Declined - face on blocklist:
                  summary: Live face, but it matches a blocklist entry
                  value:
                    request_id: 0b54a1de-22aa-4e0f-9c84-5a2b1f3c4d5e
                    liveness:
                      status: Declined
                      method: PASSIVE
                      score: 93.75
                      user_image:
                        entities:
                          - bbox:
                              - 40
                              - 40
                              - 100
                              - 100
                            confidence: 0.722082
                            age: 27
                            gender: male
                            race: null
                        best_angle: 0
                      warnings:
                        - risk: FACE_IN_BLOCKLIST
                          feature: LIVENESS
                          additional_data:
                            blocklisted_session_id: 882c42d5-8a4d-4d20-8080-a22f57822c86
                            blocklisted_session_number: 3242
                            api_service: null
                          log_type: error
                          short_description: Face in blocklist
                          long_description: >-
                            The system identified a face in the blocklist, which
                            means the face is not allowed to be verified.
                      face_quality: null
                      face_luminance: null
                    vendor_data: user-123
                    metadata: null
                    created_at: '2026-06-12T01:04:42.763237+00:00'
              schema:
                type: object
                properties:
                  request_id:
                    type: string
                    format: uuid
                    description: >-
                      Persisted session id when `save_api_request=true` (usable
                      with `GET /v3/session/{sessionId}/decision/`); otherwise a
                      transient correlation UUID.
                  liveness:
                    type: object
                    properties:
                      status:
                        type: string
                        enum:
                          - Approved
                          - Declined
                        description: >-
                          `Declined` when at least one `error`-severity warning
                          fired (no face, low score, presentation attack, or a
                          blocklist hit); `Approved` otherwise.
                          `warning`/`information` entries (multiple faces,
                          duplicates) never decline on their own.
                      method:
                        type: string
                        enum:
                          - PASSIVE
                        description: Always `PASSIVE` for this endpoint.
                      score:
                        type: number
                        format: float
                        nullable: true
                        minimum: 0
                        maximum: 100
                        description: >-
                          Liveness confidence (0–100, two decimals) that the
                          image depicts a live person rather than a spoof.
                          `null` when the model returned no confidence.
                        example: 95
                      user_image:
                        type: object
                        description: Face-detection results for the submitted image.
                        properties:
                          entities:
                            type: array
                            items:
                              type: object
                              properties:
                                bbox:
                                  type: array
                                  items:
                                    type: integer
                                  minItems: 4
                                  maxItems: 4
                                  description: >-
                                    Bounding box of the detected face as
                                    `[x_min, y_min, x_max, y_max]` pixel
                                    coordinates in the processed image.
                                  example:
                                    - 661
                                    - 728
                                    - 1688
                                    - 2188
                                confidence:
                                  type: number
                                  format: float
                                  minimum: 0
                                  maximum: 1
                                  description: Face-detection confidence (0–1).
                                  example: 0.732973
                                age:
                                  type: number
                                  format: float
                                  description: >-
                                    Model-estimated age of the detected face, in
                                    years. Informational only — it does not
                                    affect the liveness decision.
                                  example: 26.91
                                gender:
                                  type: string
                                  description: >-
                                    Model-predicted gender of the detected face
                                    (`male` or `female`). Informational only.
                                  example: male
                                race:
                                  type: string
                                  nullable: true
                                  description: >-
                                    Reserved field — always `null` in responses
                                    from this endpoint.
                                  example: null
                            description: >-
                              One entry per detected face. The largest face is
                              the one evaluated.
                          best_angle:
                            type: integer
                            nullable: true
                            description: >-
                              Rotation (degrees) that produced the best face
                              detection; non-zero only when `rotate_image=true`
                              corrected the orientation.
                            example: 0
                      warnings:
                        type: array
                        description: >-
                          Risk signals. `error` severity (declines):
                          `NO_FACE_DETECTED`, `LOW_LIVENESS_SCORE` (score at or
                          below the threshold), `LIVENESS_FACE_ATTACK`,
                          `FACE_IN_BLOCKLIST`, `POSSIBLE_FACE_IN_BLOCKLIST`.
                          `warning` severity (review, no decline):
                          `MULTIPLE_FACES_DETECTED`. `information` severity (no
                          decline): `DUPLICATED_FACE`,
                          `POSSIBLE_DUPLICATED_FACE` — the same face already
                          appeared in another approved session.
                        items:
                          type: object
                          properties:
                            risk:
                              type: string
                              enum:
                                - NO_FACE_DETECTED
                                - LOW_LIVENESS_SCORE
                                - LIVENESS_FACE_ATTACK
                                - MULTIPLE_FACES_DETECTED
                                - FACE_IN_BLOCKLIST
                                - POSSIBLE_FACE_IN_BLOCKLIST
                                - DUPLICATED_FACE
                                - POSSIBLE_DUPLICATED_FACE
                              description: Machine-readable risk code.
                            feature:
                              type: string
                              enum:
                                - LIVENESS
                              description: >-
                                Feature that raised the warning. Always
                                `LIVENESS` on this endpoint.
                            additional_data:
                              type: object
                              nullable: true
                              additionalProperties: true
                              description: >-
                                `null` for most warnings. Blocklist hits carry
                                `{blocklisted_session_id,
                                blocklisted_session_number, api_service}`;
                                duplicate hits carry `{duplicated_session_id,
                                duplicated_session_number, api_service}`
                                pointing at the matching session.
                            log_type:
                              type: string
                              enum:
                                - error
                                - warning
                                - information
                              description: >-
                                Severity. `error` warnings drive `status` to
                                `Declined`; `warning` and `information` entries
                                are advisory and never decline on their own.
                            short_description:
                              type: string
                              description: Human-readable one-line summary of the risk.
                            long_description:
                              type: string
                              description: Human-readable explanation of the risk.
                      face_quality:
                        type: number
                        format: float
                        nullable: true
                        minimum: 0
                        maximum: 100
                        description: >-
                          Capture-quality score of the detected face, normalized
                          to 0–100. `null` when the model could not produce a
                          reliable value.
                        example: 84.21
                      face_luminance:
                        type: number
                        format: float
                        nullable: true
                        minimum: 0
                        maximum: 100
                        description: >-
                          Brightness of the detected face, normalized to 0–100.
                          `null` when unavailable.
                        example: 50.33
                  vendor_data:
                    type: string
                    nullable: true
                    description: Echo of the `vendor_data` you sent, or `null`.
                  metadata:
                    type: object
                    nullable: true
                    additionalProperties: true
                    description: Echo of the `metadata` object you sent, or `null`.
                  created_at:
                    type: string
                    format: date-time
                    description: >-
                      ISO 8601 timestamp (UTC) of when the response was
                      generated, e.g. `2026-06-12T01:04:42.763237+00:00`.
        '400':
          description: >-
            Validation error. Returned when `user_image` is missing, exceeds 5
            MB, has an unsupported extension, fails server-side image decoding,
            or when an option is out of range. Field errors use DRF's `{field:
            [messages]}` envelope; image-decoding failures use `{"error": ...}`.
          content:
            application/json:
              examples:
                Missing image:
                  summary: '`user_image` not included in the form data'
                  value:
                    user_image:
                      - No file was submitted.
                Invalid image format:
                  summary: Allowed extension but undecodable image content
                  value:
                    error: Invalid user image format.
                Unsupported file extension:
                  summary: File extension outside tiff/jpg/jpeg/png/webp
                  value:
                    user_image:
                      - >-
                        File extension “txt” is not allowed. Allowed extensions
                        are: tiff, jpg, jpeg, png, webp.
                File too large:
                  summary: Upload exceeds the 5 MB limit
                  value:
                    user_image:
                      - File size should not exceed 5 MB
        '403':
          description: >-
            Permission denied. Returned when the `x-api-key` header is missing,
            malformed, revoked, or belongs to another environment — and also
            when the calling organization's balance cannot cover the call.
            Authentication failures return `403` with `{"detail": ...}`; this
            API never returns `401`. Credit shortfalls return `403` with
            `{"error": ...}` before any image processing happens.
          content:
            application/json:
              examples:
                Missing or invalid API key:
                  summary: No `x-api-key` header, or the key is invalid/revoked
                  value:
                    detail: You do not have permission to perform this action.
                Not enough credits:
                  summary: Organization balance cannot cover the call
                  value:
                    error: >-
                      You don't have enough credits to perform this request.
                      Please top up at https://business.didit.me
        '429':
          description: >-
            Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget
            of 300 write requests per minute per API key. The response carries
            `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`,
            and `Retry-After` headers.
          content:
            application/json:
              examples:
                Write rate limit exceeded:
                  summary: More than 300 write requests in the current minute
                  value:
                    detail: >-
                      Write request rate limit exceeded. You can make up to 300
                      requests per minute.
              schema:
                type: object
                properties:
                  detail:
                    type: string
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: curl
          label: cURL
          source: |-
            curl -X POST 'https://verification.didit.me/v3/passive-liveness/' \
              -H 'x-api-key: YOUR_API_KEY' \
              -F 'user_image=@./selfie.jpg' \
              -F 'face_liveness_score_decline_threshold=30' \
              -F 'save_api_request=true' \
              -F 'vendor_data=user-123'
        - lang: python
          label: Python
          source: |-
            import requests

            url = 'https://verification.didit.me/v3/passive-liveness/'
            headers = {'x-api-key': 'YOUR_API_KEY'}

            with open('selfie.jpg', 'rb') as f:
                files = {'user_image': ('selfie.jpg', f, 'image/jpeg')}
                data = {
                    'face_liveness_score_decline_threshold': 30,
                    'save_api_request': 'true',
                    'vendor_data': 'user-123',
                }
                resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)

            resp.raise_for_status()
            body = resp.json()
            print('status:', body['liveness']['status'])
            print('score:', body['liveness']['score'])
            for w in body['liveness']['warnings']:
                print('warning:', w['risk'], '-', w['log_type'])
        - lang: javascript
          label: JavaScript
          source: >-
            import fs from 'node:fs';


            const form = new FormData();

            form.append('user_image', new
            Blob([fs.readFileSync('./selfie.jpg')]), 'selfie.jpg');

            form.append('face_liveness_score_decline_threshold', '30');

            form.append('save_api_request', 'true');

            form.append('vendor_data', 'user-123');


            const response = await
            fetch('https://verification.didit.me/v3/passive-liveness/', {
              method: 'POST',
              headers: { 'x-api-key': 'YOUR_API_KEY' },
              body: form,
            });


            if (!response.ok) throw new Error(`Passive liveness failed:
            ${response.status}`);

            const body = await response.json();

            console.log('status:', body.liveness.status, 'score:',
            body.liveness.score);

            body.liveness.warnings.forEach(w => console.log('warning:', w.risk,
            '-', w.log_type));
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````