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

# Send Email Code

> Send a one-time passcode (OTP) to an email address, then verify it with [`POST /v3/email/check/`](/standalone-apis/email-check).

**How send and check pair up.** Verification state is keyed by your application plus the `email` address — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same address and the code the user received before the window closes. Calling send again for the same address while a verification is pending generates a fresh code for that same verification (`status: "Retry"`, same `request_id`); the previous code stops working. At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.

**Deliverability pre-check.** Before anything is sent, the address goes through syntax and DNS/MX validation. Addresses that cannot receive mail return `200` with `status: "Undeliverable"` and `reason: "email_can_not_be_delivered"` — no email is sent, nothing is billed, and the verification is immediately finalized as `Declined` (a follow-up check returns `Expired or Not Found`). A downstream send failure reports the same way.

**Code format and branding.** Codes are 4–8 characters (`options.code_size`, default 6), numeric by default; set `options.alphanumeric_code: true` for uppercase letters and digits (the check comparison is case-insensitive). The email template is localized via `options.locale` (54 supported languages) and can use your application's white-label branding via `options.use_white_label_customization`.

**Billing.** One Email Verification API credit per successful send (`status: "Success"`), charged at send time. `Retry` and `Undeliverable` sends are free, and checks are free.

**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.

**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no email is sent and nothing is persisted. Use code `123456` on the sandbox check.

**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/send/
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/send/:
    post:
      tags:
        - Standalone APIs
      summary: Send Email Code
      description: >-
        Send a one-time passcode (OTP) to an email address, then verify it with
        [`POST /v3/email/check/`](/standalone-apis/email-check).


        **How send and check pair up.** Verification state is keyed by your
        application plus the `email` address — the check call does not take
        `request_id`. Each new send creates a pending verification that lives
        for **5 minutes**: call the check with the same address and the code the
        user received before the window closes. Calling send again for the same
        address while a verification is pending generates a fresh code for that
        same verification (`status: "Retry"`, same `request_id`); the previous
        code stops working. At most one retry is attached this way (two sends
        total); a further send starts a fresh verification with a new
        `request_id`. The 5-minute window is measured from the first send and is
        not extended by retries.


        **Deliverability pre-check.** Before anything is sent, the address goes
        through syntax and DNS/MX validation. Addresses that cannot receive mail
        return `200` with `status: "Undeliverable"` and `reason:
        "email_can_not_be_delivered"` — no email is sent, nothing is billed, and
        the verification is immediately finalized as `Declined` (a follow-up
        check returns `Expired or Not Found`). A downstream send failure reports
        the same way.


        **Code format and branding.** Codes are 4–8 characters
        (`options.code_size`, default 6), numeric by default; set
        `options.alphanumeric_code: true` for uppercase letters and digits (the
        check comparison is case-insensitive). The email template is localized
        via `options.locale` (54 supported languages) and can use your
        application's white-label branding via
        `options.use_white_label_customization`.


        **Billing.** One Email Verification API credit per successful send
        (`status: "Success"`), charged at send time. `Retry` and `Undeliverable`
        sends are free, and checks are free.


        **Session persistence.** Every new verification is persisted as an
        API-type session: `request_id` is a real session id you can pass to `GET
        /v3/session/{sessionId}/decision/`, the verification appears in the
        Business Console, and `status.updated` webhooks fire as it progresses.


        **Sandbox.** Sandbox API keys skip delivery and billing: after request
        validation (malformed input still returns `400`), the endpoint returns a
        static `Success` payload with a random `request_id`; no email is sent
        and nothing is persisted. Use code `123456` on the sandbox check.


        **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_send
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                  description: >-
                    Recipient email address. Malformed addresses return `400`;
                    syntactically valid addresses that cannot receive mail
                    (failed DNS/MX validation) return `200` with `status:
                    "Undeliverable"`.
                  example: alice@example.com
                options:
                  type: object
                  description: >-
                    OTP format, localization, and branding options. All fields
                    are optional.
                  properties:
                    code_size:
                      type: integer
                      minimum: 4
                      maximum: 8
                      default: 6
                      description: >-
                        Number of characters in the OTP (digits by default;
                        uppercase letters and digits when `alphanumeric_code` is
                        `true`).
                    alphanumeric_code:
                      type: boolean
                      default: false
                      description: >-
                        When `true`, the OTP is composed of uppercase letters
                        `A–Z` and digits `0–9` instead of digits only. The
                        `/v3/email/check/` comparison is case-insensitive.
                    locale:
                      type: string
                      maxLength: 5
                      enum:
                        - en
                        - ar
                        - bn
                        - bg
                        - bs
                        - ca
                        - cs
                        - da
                        - de
                        - el
                        - es
                        - et
                        - fa
                        - fi
                        - fr
                        - he
                        - hi
                        - hr
                        - hu
                        - hy
                        - id
                        - it
                        - ja
                        - ka
                        - kk
                        - ko
                        - ky
                        - lt
                        - lv
                        - cnr
                        - mk
                        - mn
                        - ms
                        - nl
                        - 'no'
                        - pl
                        - pt-BR
                        - pt
                        - ro
                        - ru
                        - sk
                        - sl
                        - so
                        - sq
                        - sr
                        - sv
                        - th
                        - tr
                        - uk
                        - uz
                        - vi
                        - zh-CN
                        - zh-TW
                        - zh
                      description: >-
                        Language of the OTP email template. Defaults to `en`.
                        Unsupported values return `400` listing all supported
                        locales.
                      example: en
                    use_white_label_customization:
                      type: boolean
                      default: false
                      description: >-
                        When `true`, the OTP email uses your application's
                        white-label customization (logo, colors, sender
                        branding) instead of the default Didit template.
                signals:
                  type: object
                  description: >-
                    Optional device and network signals about the end user,
                    forwarded to the anti-fraud layer to improve detection of
                    abusive or automated traffic. All fields are optional.
                  properties:
                    ip:
                      type: string
                      description: IP address of the end user's device. IPv4 or IPv6.
                      example: 192.0.2.1
                    device_id:
                      type: string
                      maxLength: 255
                      description: >-
                        Unique identifier of the end user's device. Android:
                        `ANDROID_ID`; iOS: `identifierForVendor`.
                      example: 8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2
                    device_platform:
                      type: string
                      enum:
                        - android
                        - ios
                        - ipados
                        - tvos
                        - web
                      description: Platform of the end user's device.
                    device_model:
                      type: string
                      maxLength: 255
                      description: Model of the end user's device.
                      example: iPhone17,2
                    os_version:
                      type: string
                      maxLength: 64
                      description: Operating-system version of the end user's device.
                      example: 18.0.1
                    app_version:
                      type: string
                      maxLength: 64
                      description: Version of your application.
                      example: 1.2.34
                    user_agent:
                      type: string
                      maxLength: 512
                      description: User agent of the end user's browser or app.
                      example: >-
                        Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X)
                        AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3
                        Mobile/15E148 Safari/604.1
                vendor_data:
                  type: string
                  description: >-
                    Optional caller-controlled identifier (your internal user
                    id, an email, a UUID, etc.) persisted on the session and
                    echoed back in the send response, the matching check
                    response, webhooks, and the Business Console. Use it to
                    correlate Didit's `request_id` with your user record.
                metadata:
                  type: object
                  nullable: true
                  description: >-
                    Optional free-form JSON object persisted on the session and
                    echoed back in the send response, the matching check
                    response, webhooks, and the Business Console.
              required:
                - email
            examples:
              Numeric 6-digit code:
                summary: Default — 6-digit numeric code in English
                value:
                  email: alice@example.com
                  options:
                    locale: en
                  vendor_data: user-1234
              Alphanumeric 8-character code:
                summary: Uppercase letters + digits, Spanish template
                value:
                  email: bob@example.com
                  options:
                    code_size: 8
                    alphanumeric_code: true
                    locale: es
              White-label branding:
                summary: >-
                  Send the OTP email with your application's white-label
                  customization
                value:
                  email: carol@example.com
                  options:
                    locale: en
                    use_white_label_customization: true
                  vendor_data: user-5678
      responses:
        '200':
          description: >-
            Send acknowledged. Inspect `status`: `Success` and `Retry` mean a
            code is on its way; `Undeliverable` means the address cannot receive
            mail and the verification is already finalized as `Declined`.
            `request_id` is the persisted session id (same id on a `Retry`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailVerificationSendResponse'
              examples:
                Success:
                  summary: OTP emailed for a new verification (billed)
                  value:
                    request_id: e39cb057-92fc-4b59-b84e-02fec29a0f24
                    status: Success
                    reason: null
                    vendor_data: user-1234
                    metadata: null
                Retry:
                  summary: >-
                    Second send inside the 5-minute window — fresh code, same
                    request_id, free
                  value:
                    request_id: e39cb057-92fc-4b59-b84e-02fec29a0f24
                    status: Retry
                    reason: null
                    vendor_data: user-1234
                    metadata: null
                Undeliverable:
                  summary: >-
                    Address failed DNS/MX validation — verification finalized as
                    Declined, nothing billed
                  value:
                    request_id: cb1a5011-344d-444d-bdce-218904745d7f
                    status: Undeliverable
                    reason: email_can_not_be_delivered
                    vendor_data: user-1234
                    metadata: null
        '400':
          description: >-
            Validation error — DRF's field-error envelope: one array of messages
            per offending field, nested under `options` for option errors.
          content:
            application/json:
              examples:
                Invalid email:
                  summary: Malformed email address
                  value:
                    email:
                      - Enter a valid email address.
                Unsupported locale:
                  summary: '`options.locale` outside the supported set'
                  value:
                    options:
                      locale:
                        - >-
                          Invalid locale. Supported locales are en, ar, bn, bg,
                          bs, ca, cs, da, de, el, es, et, fa, fi, fr, he, hi,
                          hr, hu, hy, id, it, ja, ka, kk, ko, ky, lt, lv, cnr,
                          mk, mn, ms, nl, no, pl, pt-BR, pt, ro, ru, sk, sl, so,
                          sq, sr, sv, th, tr, uk, uz, vi, zh-CN, zh-TW, zh.
                Code size out of range:
                  summary: '`options.code_size` outside 4–8'
                  value:
                    options:
                      code_size:
                        - Ensure this value is less than or equal to 8.
        '403':
          description: >-
            Permission denied. Returned when the `x-api-key` header is missing,
            malformed, revoked, or belongs to another environment (`{"detail":
            ...}`) — this API never returns `401`. Also returned before any send
            when the organization's balance cannot cover one Email Verification
            API credit (`{"error": ...}`).
          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 send price
                  value:
                    error: You don't have enough credits to perform this request.
        '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/send/ \
              -H 'x-api-key: YOUR_API_KEY' \
              -H 'Content-Type: application/json' \
              -d '{
                "email": "alice@example.com",
                "options": { "code_size": 6, "locale": "en" },
                "vendor_data": "user-1234"
              }'
        - lang: python
          label: Python
          source: >-
            import os, requests


            resp = requests.post(
                "https://verification.didit.me/v3/email/send/",
                headers={
                    "x-api-key": os.environ["DIDIT_API_KEY"],
                    "Content-Type": "application/json",
                },
                json={
                    "email": "alice@example.com",
                    "options": {"code_size": 6, "locale": "en"},
                    "vendor_data": "user-1234",
                },
                timeout=15,
            )

            resp.raise_for_status()

            print(resp.json())  # {request_id, status, reason, vendor_data,
            metadata}

            # Then verify with POST /v3/email/check/ using the same email
        - lang: javascript
          label: JavaScript
          source: >-
            const res = await
            fetch('https://verification.didit.me/v3/email/send/', {
              method: 'POST',
              headers: {
                'x-api-key': 'YOUR_API_KEY',
                'Content-Type': 'application/json',
              },
              body: JSON.stringify({
                email: 'alice@example.com',
                options: { code_size: 6, locale: 'en' },
                vendor_data: 'user-1234',
              }),
            });

            if (!res.ok) throw new Error(`Email send failed: ${res.status}`);

            const data = await res.json();

            console.log(data); // { request_id, status, reason, vendor_data,
            metadata }

            // Then verify with POST /v3/email/check/ using the same email
components:
  schemas:
    EmailVerificationSendResponse:
      type: object
      properties:
        request_id:
          type: string
          format: uuid
          description: >-
            Session id of the verification. A `Retry` send returns the same
            `request_id` as the original send. This id appears in the Business
            Console, is returned again by a finalized `POST /v3/email/check/`,
            and can be passed to `GET /v3/session/{sessionId}/decision/`.
        status:
          type: string
          enum:
            - Success
            - Retry
            - Undeliverable
          description: >-
            `Success` — OTP emailed to a new verification (billed). `Retry` —
            fresh OTP emailed for the pending verification created by a previous
            send (free). `Undeliverable` — the address failed deliverability
            validation or the message could not be sent; the verification is
            immediately finalized as `Declined` and nothing is billed.
        reason:
          type: string
          nullable: true
          enum:
            - email_can_not_be_delivered
            - null
          description: >-
            `email_can_not_be_delivered` when `status` is `Undeliverable`;
            `null` otherwise.
        vendor_data:
          type: string
          nullable: true
          description: >-
            Echo of the `vendor_data` stored on the session (from the first
            send).
        metadata:
          type: object
          nullable: true
          description: Echo of the `metadata` stored on the session (from the first send).
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````