> ## 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.

# Check Email Code

> Verify the OTP delivered by [`POST /v3/email/send/`](/standalone-apis/email-send) and get the final verification result plus email intelligence: deliverability, breach exposure (`is_breached` and the `breaches` list from a breach-intelligence database), disposable-provider detection, and duplicate usage of the address across your sessions.

**How the check finds the verification.** Matching is by your application plus the `email` address — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, undeliverable at send time, or older than 5 minutes) the endpoint returns `200` with `status: "Expired or Not Found"`.

**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: "Failed"` with the attempts remaining and `email: null`; the third wrong code finalizes the verification as `Declined` with an `EMAIL_CODE_ATTEMPTS_EXCEEDED` warning and returns the full email report. Codes are compared case-insensitively (relevant for `alphanumeric_code` sends); only the most recently sent code is valid.

**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted address, or the address found undeliverable at finalization), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `email` carries the full report (`is_breached`, `breaches`, `is_disposable`, `is_undeliverable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.

**Risk actions.** `duplicated_email_action`, `breached_email_action`, and `disposable_email_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `email.warnings` without affecting the status.

**Billing.** Checks are free — the credit is consumed by the successful send.

**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.

**Sandbox.** Sandbox API keys skip all processing: code `123456` returns a static `Approved` payload with a simplified `email` object (`status`, `email`, `is_breached`, `is_disposable`, `is_undeliverable`); any other code returns `Failed`. Nothing is persisted.

**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/email/check/
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/email/check/:
    post:
      tags:
        - Standalone APIs
      summary: Check Email Code
      description: >-
        Verify the OTP delivered by [`POST
        /v3/email/send/`](/standalone-apis/email-send) and get the final
        verification result plus email intelligence: deliverability, breach
        exposure (`is_breached` and the `breaches` list from a
        breach-intelligence database), disposable-provider detection, and
        duplicate usage of the address across your sessions.


        **How the check finds the verification.** Matching is by your
        application plus the `email` address — `request_id` is not an input. The
        most recent pending verification created within the last **5 minutes**
        is checked. If there is none (never sent, already finalized,
        undeliverable at send time, or older than 5 minutes) the endpoint
        returns `200` with `status: "Expired or Not Found"`.


        **Attempt budget.** Each verification allows **3 code attempts**. The
        first two wrong codes return `status: "Failed"` with the attempts
        remaining and `email: null`; the third wrong code finalizes the
        verification as `Declined` with an `EMAIL_CODE_ATTEMPTS_EXCEEDED`
        warning and returns the full email report. Codes are compared
        case-insensitively (relevant for `alphanumeric_code` sends); only the
        most recently sent code is valid.


        **Outcomes.** `Approved` — correct code and no declining risk.
        `Declined` — terminal: the code was correct but a declining risk matched
        (a `DECLINE` action below, a blocklisted address, or the address found
        undeliverable at finalization), or the attempt budget was exhausted.
        `Failed` — wrong code, attempts remaining. `Expired or Not Found` —
        nothing to check. On `Approved`/`Declined` the `request_id` equals the
        send's `request_id` (the session id) and `email` carries the full report
        (`is_breached`, `breaches`, `is_disposable`, `is_undeliverable`,
        `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not
        Found` the `request_id` is a one-off random UUID.


        **Risk actions.** `duplicated_email_action`, `breached_email_action`,
        and `disposable_email_action` decide what happens when the corresponding
        risk is detected on a correct code: `DECLINE` flips the final status to
        `Declined`; `NO_ACTION` (default) records the risk in `email.warnings`
        without affecting the status.


        **Billing.** Checks are free — the credit is consumed by the successful
        send.


        **Session persistence.** A finalized check updates the session created
        by the send (visible in the Business Console, queryable via `GET
        /v3/session/{sessionId}/decision/`) and fires a `status.updated`
        webhook.


        **Sandbox.** Sandbox API keys skip all processing: code `123456` returns
        a static `Approved` payload with a simplified `email` object (`status`,
        `email`, `is_breached`, `is_disposable`, `is_undeliverable`); any other
        code returns `Failed`. Nothing is persisted.


        **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_v3email_check
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                  description: >-
                    The same email address used in the matching [`POST
                    /v3/email/send/`](/standalone-apis/email-send) call. This is
                    what links the check to the send.
                  example: alice@example.com
                code:
                  type: string
                  maxLength: 10
                  description: >-
                    The OTP the end user received: 4–8 digits, or 4–8
                    letters/digits when `alphanumeric_code: true` was used at
                    send time. Comparison is case-insensitive.
                  example: '123456'
                duplicated_email_action:
                  type: string
                  enum:
                    - NO_ACTION
                    - DECLINE
                  default: NO_ACTION
                  description: >-
                    What to do when the same address was already used and
                    approved by a different user (only previously Approved
                    verifications count) (different `vendor_data`) of your
                    application. `DECLINE` flips the final `status` to
                    `Declined`; `NO_ACTION` records the risk in `email.warnings`
                    and fills `email.matches`.
                breached_email_action:
                  type: string
                  enum:
                    - NO_ACTION
                    - DECLINE
                  default: NO_ACTION
                  description: >-
                    What to do when the address appears in known data breaches
                    (`email.is_breached`). `DECLINE` flips the final `status` to
                    `Declined`; `NO_ACTION` records the risk in
                    `email.warnings`.
                disposable_email_action:
                  type: string
                  enum:
                    - NO_ACTION
                    - DECLINE
                  default: NO_ACTION
                  description: >-
                    What to do when the domain belongs to a
                    disposable/temporary-mail provider (`email.is_disposable`).
                    `DECLINE` flips the final `status` to `Declined`;
                    `NO_ACTION` records the risk in `email.warnings`.
              required:
                - email
                - code
            examples:
              Default:
                summary: Record any risks but never auto-decline
                value:
                  email: alice@example.com
                  code: '123456'
              Strict risk policy:
                summary: Auto-decline disposable and breached inboxes
                value:
                  email: alice@example.com
                  code: '123456'
                  disposable_email_action: DECLINE
                  breached_email_action: DECLINE
      responses:
        '200':
          description: >-
            Check completed — wrong codes and missing verifications also return
            `200`; inspect `status`, not the HTTP code. `email` is populated
            only on finalized outcomes (`Approved`/`Declined`), `null` on
            `Failed`, and absent on `Expired or Not Found`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailVerificationCheckResponse'
              examples:
                Approved:
                  summary: Correct code — clean address, full email report
                  value:
                    request_id: e39cb057-92fc-4b59-b84e-02fec29a0f24
                    status: Approved
                    message: The verification code is correct.
                    email:
                      status: Approved
                      email: alice@example.com
                      is_breached: false
                      breaches: []
                      is_disposable: false
                      is_undeliverable: false
                      verification_attempts: 1
                      verified_at: '2026-06-12T01:24:47.311323Z'
                      warnings: []
                      lifecycle:
                        - type: EMAIL_VERIFICATION_MESSAGE_SENT
                          timestamp: '2026-06-12T01:23:39.580554+00:00'
                          details:
                            status: Success
                            reason: null
                          fee: 0.03
                        - type: VALID_CODE_ENTERED
                          timestamp: '2026-06-12T01:24:47.311201+00:00'
                          details:
                            code_tried: '123456'
                            status: Approved
                          fee: 0
                        - type: EMAIL_VERIFICATION_APPROVED
                          timestamp: '2026-06-12T01:24:47.384292+00:00'
                          details: null
                          fee: 0
                      matches: []
                    vendor_data: user-1234
                    metadata: null
                    created_at: '2026-06-12T01:24:47.401719+00:00'
                Approved with breach exposure:
                  summary: >-
                    Correct code; breach exposure recorded as a warning
                    (breached_email_action=NO_ACTION)
                  value:
                    request_id: 3a1f2b6c-9d4e-4f7a-8b2c-1e5d6f7a8b9c
                    status: Approved
                    message: The verification code is correct.
                    email:
                      status: Approved
                      email: bob@example.com
                      is_breached: true
                      breaches:
                        - name: ExampleApp
                          domain: exampleapp.com
                          breach_date: '2019-03-21'
                          breach_emails_count: 763117
                          description: >-
                            In March 2019, the social platform ExampleApp
                            suffered a data breach that exposed email addresses
                            and passwords.
                          logo_path: https://<media-host>/logos/ExampleApp.png
                          data_classes:
                            - email_addresses
                            - passwords
                          is_verified: true
                      is_disposable: false
                      is_undeliverable: false
                      verification_attempts: 1
                      verified_at: '2026-06-12T01:24:47.311323Z'
                      warnings:
                        - feature: EMAIL
                          risk: BREACHED_EMAIL_DETECTED
                          additional_data: null
                          log_type: information
                          short_description: Breached email detected
                          long_description: >-
                            This email address was found in one or more known
                            data breaches.
                      lifecycle:
                        - type: EMAIL_VERIFICATION_MESSAGE_SENT
                          timestamp: '2026-06-12T01:23:39.580554+00:00'
                          details:
                            status: Success
                            reason: null
                          fee: 0.03
                        - type: VALID_CODE_ENTERED
                          timestamp: '2026-06-12T01:24:47.311201+00:00'
                          details:
                            code_tried: '835126'
                            status: Approved
                          fee: 0
                        - type: EMAIL_VERIFICATION_APPROVED
                          timestamp: '2026-06-12T01:24:47.384292+00:00'
                          details: null
                          fee: 0
                      matches: []
                    vendor_data: user-5678
                    metadata: null
                    created_at: '2026-06-12T01:24:47.401719+00:00'
                Failed (attempts remaining):
                  summary: >-
                    Wrong code — the user can retry; request_id here is a
                    one-off UUID
                  value:
                    request_id: 11111111-2222-3333-4444-555555555555
                    status: Failed
                    message: 'The verification code is incorrect. Attempts remaining: 2'
                    email: null
                    vendor_data: user-1234
                    metadata: null
                    created_at: '2026-06-12T01:24:21.512340+00:00'
                Declined (risk policy):
                  summary: >-
                    Correct code, but the address is disposable and
                    disposable_email_action=DECLINE
                  value:
                    request_id: 8c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f
                    status: Declined
                    message: The verification code is correct.
                    email:
                      status: Declined
                      email: tempuser42@mailinator.com
                      is_breached: false
                      breaches: []
                      is_disposable: true
                      is_undeliverable: false
                      verification_attempts: 1
                      verified_at: '2026-06-12T01:24:47.311323Z'
                      warnings:
                        - feature: EMAIL
                          risk: DISPOSABLE_EMAIL_DETECTED
                          additional_data: null
                          log_type: error
                          short_description: Disposable email detected
                          long_description: >-
                            The system detected that the email is disposable,
                            which is not allowed.
                      lifecycle:
                        - type: EMAIL_VERIFICATION_MESSAGE_SENT
                          timestamp: '2026-06-12T01:23:39.580554+00:00'
                          details:
                            status: Success
                            reason: null
                          fee: 0.03
                        - type: VALID_CODE_ENTERED
                          timestamp: '2026-06-12T01:24:47.311201+00:00'
                          details:
                            code_tried: '123456'
                            status: Approved
                          fee: 0
                        - type: EMAIL_VERIFICATION_DECLINED
                          timestamp: '2026-06-12T01:24:47.384292+00:00'
                          details:
                            reason: DISPOSABLE_EMAIL_DETECTED
                          fee: 0
                      matches: []
                    vendor_data: user-1234
                    metadata: null
                    created_at: '2026-06-12T01:24:47.401719+00:00'
                Expired or Not Found:
                  summary: >-
                    No pending verification for this address in the last 5
                    minutes
                  value:
                    request_id: 88808a77-06e9-4cb0-85f5-9c052ddfd987
                    status: Expired or Not Found
                    message: No pending email verification found in the last 5 minutes.
                    vendor_data: null
                    metadata: null
                    created_at: '2026-06-12T01:24:59.887904+00:00'
        '400':
          description: >-
            Validation error — DRF's field-error envelope: one array of messages
            per offending field.
          content:
            application/json:
              examples:
                Invalid email:
                  summary: Malformed email address
                  value:
                    email:
                      - Enter a valid email address.
                Missing code:
                  summary: '`code` not included in the body'
                  value:
                    code:
                      - This field is required.
        '403':
          description: >-
            Permission denied. Returned when the `x-api-key` header is missing,
            malformed, revoked, or belongs to another environment — this API
            never returns `401`. Checks are free, so there is no credit check
            here.
          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.
        '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/email/check/ \
              -H 'x-api-key: YOUR_API_KEY' \
              -H 'Content-Type: application/json' \
              -d '{
                "email": "alice@example.com",
                "code": "123456",
                "disposable_email_action": "DECLINE"
              }'
        - lang: python
          label: Python
          source: >-
            import os, requests


            resp = requests.post(
                "https://verification.didit.me/v3/email/check/",
                headers={
                    "x-api-key": os.environ["DIDIT_API_KEY"],
                    "Content-Type": "application/json",
                },
                json={
                    "email": "alice@example.com",  # same address as the send call
                    "code": "123456",
                    "disposable_email_action": "DECLINE",
                },
                timeout=15,
            )

            resp.raise_for_status()

            result = resp.json()

            print(result["status"])  # Approved / Declined / Failed / Expired or
            Not Found

            if result["status"] in ("Approved", "Declined"):
                print(result["email"]["is_breached"], result["email"]["is_disposable"])
        - lang: javascript
          label: JavaScript
          source: >-
            const res = await
            fetch('https://verification.didit.me/v3/email/check/', {
              method: 'POST',
              headers: {
                'x-api-key': 'YOUR_API_KEY',
                'Content-Type': 'application/json',
              },
              body: JSON.stringify({
                email: 'alice@example.com', // same address as the send call
                code: '123456',
                disposable_email_action: 'DECLINE',
              }),
            });

            const data = await res.json();

            if (data.status === 'Approved') {
              // full email report is in data.email (breaches, is_disposable, matches, ...)
            }
components:
  schemas:
    EmailVerificationCheckResponse:
      type: object
      properties:
        request_id:
          type: string
          format: uuid
          description: >-
            On `Approved`/`Declined`: the session id of the matched verification
            — identical to the `request_id` returned by `POST /v3/email/send/`.
            On `Failed` and `Expired or Not Found`: a random one-off UUID that
            cannot be looked up later.
        status:
          type: string
          enum:
            - Approved
            - Declined
            - Failed
            - Expired or Not Found
          description: >-
            `Approved` — correct code, no declining risk. `Declined` — terminal:
            a declining risk matched or the attempt budget (3) was exhausted.
            `Failed` — wrong code, attempts remaining. `Expired or Not Found` —
            no pending verification for this address in the last 5 minutes.
        message:
          type: string
          description: >-
            Human-readable explanation of the outcome, including the number of
            attempts remaining after a wrong code.
        email:
          type: object
          nullable: true
          description: >-
            Full email report. Present (non-null) only on finalized outcomes
            (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired
            or Not Found`.
          properties:
            status:
              type: string
              enum:
                - Approved
                - Declined
                - In Review
                - Not Finished
                - Expired
              description: >-
                Final status of the email verification. Matches the top-level
                `status` for finalized checks.
            email:
              type: string
              format: email
              description: The verified email address.
              example: alice@example.com
            is_breached:
              type: boolean
              description: >-
                `true` when the address appears in known data breaches (breach
                intelligence lookup run at finalization).
            breaches:
              type: array
              description: >-
                Up to 5 most recent known breaches containing this address.
                Empty array (or omitted) when `is_breached` is `false`.
              items:
                type: object
                properties:
                  name:
                    type: string
                    description: Breach identifier.
                  domain:
                    type: string
                    description: Domain of the breached service.
                  breach_date:
                    type: string
                    format: date
                    description: Date the breach occurred.
                  breach_emails_count:
                    type: integer
                    description: Total number of accounts exposed in the breach.
                  description:
                    type: string
                    description: >-
                      Human-readable description of the breach (may contain HTML
                      markup).
                  logo_path:
                    type: string
                    description: URL of the breached service's logo.
                  data_classes:
                    type: array
                    items:
                      type: string
                    description: >-
                      Categories of data exposed, in snake_case (e.g.,
                      `email_addresses`, `passwords`).
                  is_verified:
                    type: boolean
                    description: Whether the breach has been verified as legitimate.
            is_disposable:
              type: boolean
              description: >-
                `true` when the domain belongs to a known
                disposable/temporary-mail provider.
            is_undeliverable:
              type: boolean
              description: >-
                `true` when the address cannot receive mail (failed syntax or
                DNS/MX validation, or the message bounced).
            verification_attempts:
              type: integer
              description: >-
                Number of OTP sends for this verification (1, or 2 after a
                retry).
            verified_at:
              type: string
              format: date-time
              nullable: true
              description: >-
                When the correct code was entered. `null` if the verification
                was never approved.
            warnings:
              type: array
              description: >-
                Risk warnings recorded during the verification (duplicate,
                breached, disposable, undeliverable, attempts exceeded,
                blocklist, etc.). Empty array when there are none.
              items:
                type: object
                properties:
                  feature:
                    type: string
                    description: Feature that produced the warning. Always `EMAIL` here.
                  risk:
                    type: string
                    description: Machine-readable risk code.
                  additional_data:
                    type: object
                    nullable: true
                    description: >-
                      Risk-specific context (e.g., the duplicated session id for
                      duplicate risks). `null` when the risk carries no extra
                      data.
                  log_type:
                    type: string
                    enum:
                      - error
                      - warning
                      - information
                    description: >-
                      Severity. `error` risks decline the verification,
                      `warning` flags it for review, `information` is recorded
                      without affecting the status.
                  short_description:
                    type: string
                    description: Human-readable one-line summary of the risk.
                  long_description:
                    type: string
                    description: Human-readable explanation of the risk.
            lifecycle:
              type: array
              description: >-
                Chronological audit trail of the verification. Event `type` is
                one of `EMAIL_VERIFICATION_MESSAGE_SENT`,
                `EMAIL_VERIFICATION_RETRY_MESSAGE_SENT`, `VALID_CODE_ENTERED`,
                `INVALID_CODE_ENTERED`, `EMAIL_VERIFICATION_APPROVED`,
                `EMAIL_VERIFICATION_DECLINED`, `EMAIL_VERIFICATION_IN_REVIEW`,
                `EMAIL_VERIFICATION_EXPIRED`.
              items:
                type: object
                properties:
                  type:
                    type: string
                    description: Event type.
                  timestamp:
                    type: string
                    format: date-time
                    description: When the event happened.
                  details:
                    type: object
                    nullable: true
                    description: >-
                      Event-specific payload. Send events: `{status, reason}`.
                      Code-entry events: `{code_tried, status}`. Final-status
                      events: `null`, or `{reason}` when declined or in review.
                  fee:
                    type: number
                    description: >-
                      USD amount billed for this event. Non-zero only on the
                      first successful send; 0 for retries, code entries, and
                      status events.
            matches:
              type: array
              description: >-
                Other sessions of your application (KYC, KYB, or API) where the
                same email address was used by a different user (different
                `vendor_data`). Up to 5 entries; a synthetic list-entry
                blocklist match is prepended when present (and only when no
                session-derived blocklisted email exists) — session-derived
                matches keep created_at order. Empty array when there are no
                matches.
              items:
                type: object
                properties:
                  session_id:
                    type: string
                    format: uuid
                    description: >-
                      Session id of the other verification that used this email
                      address.
                  session_number:
                    type: integer
                    description: Human-friendly sequential number of that session.
                  vendor_data:
                    type: string
                    nullable: true
                    description: '`vendor_data` of that session.'
                  verification_date:
                    type: string
                    description: When that session was created (`YYYY-MM-DDTHH:MM:SSZ`).
                  email:
                    type: string
                    description: The matched email address.
                  status:
                    type: string
                    description: >-
                      Status of that session (e.g., `Approved`, `Declined`, `In
                      Progress`).
                  is_blocklisted:
                    type: boolean
                    description: >-
                      Whether the email address is on your application's
                      blocklist.
                  api_service:
                    type: string
                    nullable: true
                    description: >-
                      For API-type sessions, the standalone API that created it
                      (e.g., `PHONE_VERIFICATION`); `null` for regular
                      verification sessions.
                  source:
                    type: string
                    description: >-
                      Where the match was found. Usually `session`; a blocklist
                      hit via an organization list entry prepends a synthetic
                      match with `source: "list_entry"` and null
                      `session_id`/`session_number`.
        vendor_data:
          type: string
          nullable: true
          description: >-
            `vendor_data` of the matched verification's session. `null` on
            `Expired or Not Found`.
        metadata:
          type: object
          nullable: true
          description: >-
            `metadata` of the matched verification's session. `null` on `Expired
            or Not Found`.
        created_at:
          type: string
          format: date-time
          description: Timestamp of this check response.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````