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

# 发送手机验证码 API：短信一次性验证码

> 调用 POST /v3/phone/send/ 发送短信一次性验证码。被拦截号码自动重试，验证码有效期 5 分钟，按量付费，无月度最低消费。Didit 提供支持。

## 在短信中设置品牌名称

短信验证码会携带您在 Business Console 中设置的应用短信发送方名称，无需额外付费：

> 123456 是 **Acme Bank** 的验证码

如需让某条验证码使用其他名称，请传入 `options.sms_sender_name`（1 至 30 个字符，仅限字母、数字、空格以及 ' & - !，且不能全部为数字）：

```json theme={null}
{
  "phone_number": "+14155552671",
  "options": {
    "preferred_channel": "sms",
    "sms_sender_name": "Acme Bank"
  }
}
```

不符合这些规则的名称会返回 `400`，且不会发送验证码。
WhatsApp 验证码使用固定消息，不带品牌标识。如需以您的企业名称发送，请在 Business Console 中连接您自己的 WhatsApp Business 号码。参见[通过您自己的号码发送 WhatsApp 验证码](https://docs.didit.me/zh/core-technology/phone-verification/overview.md#通过您自己的号码发送-whatsapp-验证码)。
有关验证码携带哪个名称，以及新名称的每日限额，请参见[品牌化验证消息](https://docs.didit.me/zh/core-technology/phone-verification/overview.md#品牌化验证消息)。

## 处理服务商故障

如果电话服务商暂时不可用，此 endpoint 会返回 `502` 以及一个稳定的机器可读代码：

```json theme={null}
{
  "detail": "The phone verification provider is temporarily unavailable.",
  "code": "phone_provider_unavailable"
}
```

应将此响应视为可重试。请使用较短的退避间隔，并向用户显示临时服务提示，而不是要求其更改有效的手机号码或验证码。


## OpenAPI

````yaml zh/openapi-25.json POST /v3/phone/send/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: 身份证件验证 API。使用 x-api-key 请求头进行身份验证。
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/phone/send/:
    post:
      tags:
        - Standalone APIs
      summary: 发送手机验证码
      description: >-
        通过 WhatsApp、SMS 或其他受支持的渠道向电话号码发送一次性验证码 (OTP)，然后使用 [`POST
        /v3/phone/check/`](/zh/standalone-apis/phone-check) 进行核验。


        **发送与核验的配对方式。** 验证状态以您的应用加上 E.164 格式的 `phone_number` 为键，核验调用不需要传入
        `request_id`。每次新的发送都会创建一个待处理的验证，有效期为 **5
        分钟**：在窗口关闭前，使用相同的号码和用户收到的验证码调用核验。在验证处于待处理状态时对同一号码再次调用发送，会为该次验证重新发送验证码（`status:
        "Retry"`，相同的 `request_id`）。以此方式最多可关联一次重试（总共两次发送）；进一步发送将使用新的 `request_id`
        启动一次全新验证。5 分钟窗口从首次发送开始计算，重试不会延长该时间。


        **投递渠道。** `options.preferred_channel` 用于选择渠道（默认为 `whatsapp`；还支持
        `sms`、`telegram`、`voice`、`rcs`、`viber` 和
        `zalo`）。当首选渠道对目标国家/地区或号码不可用时，投递将自动回退到 SMS，发送仍会报告
        `Success`，实际使用的渠道会在核验响应（`phone.verification_method` 和 `lifecycle`
        事件）中报告。


        **品牌定制。** SMS 验证码带有在 Business Console 中设置的应用 SMS 发送方名称，无需额外费用。传入
        `options.sms_sender_name` 可使用其他名称发送一条验证码，或传入
        `options.use_white_label_customization: true`
        在未设置发送方名称时回退到您的白标公开名称。WhatsApp 验证码使用固定消息；在 Business Console 中连接您自己的
        WhatsApp Business 号码即可使用您的企业名称和号码作为发送方，仅按 Didit 费用计费。


        **发送状态。** `Success`：已为新验证发送 OTP；`Retry`：已为待处理验证重新发送
        OTP；`Blocked`：反欺诈层拒绝发送（参见 `reason`，例如
        `repeated_attempts`、`suspicious`、`spam`）。`Blocked` 发送会立即将验证终结为
        `Declined` 并带有高风险警告，因此后续核验将返回 `Expired or Not Found`。


        **计费。**
        价格取决于目标国家/地区和实际投递消息的渠道，参见[电话验证定价](/zh/getting-started/phone-verification-pricing)。只有
        `Blocked`
        发送保证免费：任何其他未计费的发送尝试均在验证终结或过期时收取费用，即使投递从未得到确认或投递提供商报告消息无法送达也是如此。核验始终免费。作为防滥用措施，电话验证在您的组织完成首次充值之前处于禁用状态（`403`）。


        **会话持久化。** 每次新验证都会作为 API 类型的会话进行持久化：`request_id` 是一个真实的会话 ID，您可以将其传递给
        `GET /v3/session/{sessionId}/decision/`，验证会显示在 Business Console
        中，并且随着验证的推进会触发 `status.updated` Webhook。


        **沙盒。** 沙盒 API 密钥会跳过投递和计费：在请求验证之后（格式错误的输入仍返回 `400`），endpoint 会返回带有随机
        `request_id` 的静态 `Success` payload；不会发送任何消息，也不会持久化任何内容。在沙盒核验中使用验证码
        `123456`。


        **身份验证。** 在 `x-api-key` 请求头中发送您应用的 API 密钥。缺少凭据或凭据无效将返回 `403`（`{"detail":
        "You do not have permission to perform this action."}`），此 API 从不返回
        `401`。


        **速率限制。** 所有 POST/PATCH/DELETE endpoint 共享每个 API 密钥 300
        次请求/分钟的写入预算，外加每个电话号码每小时 4 次发送尝试的防滥用上限；超过任一限制均返回 `429`。
      operationId: post_v3phone_send
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                phone_number:
                  type: string
                  maxLength: 20
                  description: >-
                    E.164 格式的收件人电话号码，以前导 `+` 和国家/地区代码开头（例如
                    `+14155552671`）。号码会经过验证并标准化为 E.164；无法解析或无效的号码将返回 `400` 并附带
                    `phone_number` 字段错误。
                  example: '+14155552671'
                options:
                  type: object
                  description: 一次性验证码投递选项。所有字段均为可选。
                  properties:
                    code_size:
                      type: integer
                      minimum: 4
                      maximum: 8
                      default: 6
                      description: OTP 中的数字位数。
                    locale:
                      type: string
                      maxLength: 5
                      pattern: ^[a-z]{2,3}(-[A-Z]{2,3})?$
                      description: >-
                        用于本地化验证消息的 BCP-47 区域设置（例如 `en-US`、`fr`、`es`）。无效格式返回
                        `400`。
                      example: en-US
                    preferred_channel:
                      type: string
                      enum:
                        - whatsapp
                        - sms
                        - telegram
                        - voice
                        - rcs
                        - viber
                        - zalo
                      default: whatsapp
                      description: >-
                        首选投递渠道。如果该渠道对目标国家/地区或号码不可用，消息将自动回退为 SMS，发送仍会报告
                        `Success`。
                    use_white_label_customization:
                      type: boolean
                      default: false
                      description: >-
                        为 `true`
                        时，发送将采用白标：如果您应用没有设置短信发送方名称，短信将改为显示您的白标公开名称（"123456 是您在
                        Acme Bank
                        的验证码"）。如果设置了短信发送方名称，无论是否设置此标志都会使用该名称。适用于短信消息，包括从其他渠道回退的短信；WhatsApp
                        验证码使用固定消息发送，不带品牌标识。默认为 `false`。
                    sms_sender_name:
                      type: string
                      nullable: true
                      maxLength: 30
                      example: Acme Bank
                      description: >-
                        该验证码短信所携带的名称（"123456 是您用于 Acme Bank
                        的验证码"），用以代替您应用的短信发送方名称。免费。长度为 1 到 30 个字符：仅限字母、数字、空格以及 '
                        & - !，不能全为数字，单词之间使用单个空格；任何其他值都将返回 `400` 且不会发送验证码。如果省略、为
                        `null`
                        或留空，则使用您应用的短信发送方名称。每个应用每天（UTC）最多可以开始使用两个新名称；超过后，验证码将使用您应用的名称发送。适用于短信消息，包括从其他渠道回退的短信；WhatsApp
                        验证码不带有品牌标识。
                      pattern: ^$|^(?![\d ]+$)[\p{L}\p{N}'&!-]+( [\p{L}\p{N}'&!-]+)*$
                signals:
                  type: object
                  description: 关于终端用户的可选设备与网络信号，转发至反欺诈层以提升对滥用或自动化流量的检测能力。所有字段均为可选。
                  properties:
                    ip:
                      type: string
                      description: 终端用户设备的 IP 地址。IPv4 或 IPv6。
                      example: 192.0.2.1
                    device_id:
                      type: string
                      maxLength: 255
                      description: >-
                        终端用户设备的唯一标识符。Android：`ANDROID_ID`；iOS：`identifierForVendor`。
                      example: 8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2
                    device_platform:
                      type: string
                      enum:
                        - android
                        - ios
                        - ipados
                        - tvos
                        - web
                      description: 终端用户设备的平台。
                    device_model:
                      type: string
                      maxLength: 255
                      description: 终端用户设备的型号。
                      example: iPhone17,2
                    os_version:
                      type: string
                      maxLength: 64
                      description: 终端用户设备的操作系统版本。
                      example: 18.0.1
                    app_version:
                      type: string
                      maxLength: 64
                      description: 您的应用版本。
                      example: 1.2.34
                    user_agent:
                      type: string
                      maxLength: 512
                      description: 终端用户浏览器或应用的 User-Agent。
                      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: >-
                    由调用方控制的可选标识符（您的内部用户 id、邮箱、UUID
                    等），在会话中持久化并在发送响应、匹配检查响应、Webhook 和 Business Console
                    中原样返回。可使用它将 Didit 的 `request_id` 与您的用户记录进行关联。
                metadata:
                  type: object
                  nullable: true
                  description: >-
                    可选的自由格式 JSON 对象，持久化保存在会话中，并在发送响应、匹配检查响应、Webhook 以及 Business
                    Console 中回显。
              required:
                - phone_number
            examples:
              WhatsApp (default):
                summary: 通过 WhatsApp 发送的 6 位数 OTP，可自动回退到短信
                value:
                  phone_number: '+14155552671'
                  options:
                    preferred_channel: whatsapp
                    locale: en-US
                  vendor_data: user-1234
              SMS with custom code length:
                summary: 强制通过短信投递 4 位验证码
                value:
                  phone_number: '+34699999999'
                  options:
                    preferred_channel: sms
                    code_size: 4
                    locale: es
              With fraud signals:
                summary: 转发设备信号以增强反欺诈筛查
                value:
                  phone_number: '+14155552671'
                  signals:
                    ip: 192.0.2.1
                    device_id: 8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2
                    device_platform: ios
                    device_model: iPhone17,2
                    os_version: 18.0.1
                    app_version: 1.2.34
                  vendor_data: user-1234
      responses:
        '200':
          description: >-
            已确认发送。请检查 `status`：`Success` 和 `Retry` 表示验证码正在发送中；`Blocked`
            表示反欺诈层已拒绝，且验证已最终确定为 `Declined`。`request_id` 是持久化的会话 ID（在 `Retry` 时
            ID 相同）。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PhoneVerificationSendResponse'
              examples:
                Success:
                  summary: 已为新验证发送一次性验证码
                  value:
                    request_id: e39cb057-92fc-4b59-b84e-02fec29a0f24
                    status: Success
                    reason: null
                    vendor_data: user-1234
                    metadata: null
                Retry:
                  summary: 在 5 分钟窗口内向同一号码进行第二次发送：相同的 request_id
                  value:
                    request_id: e39cb057-92fc-4b59-b84e-02fec29a0f24
                    status: Retry
                    reason: null
                    vendor_data: user-1234
                    metadata: null
                Blocked:
                  summary: 反欺诈拒绝 — 验证最终判定为已拒绝，不计费
                  value:
                    request_id: 0d8af1f4-0c43-46d7-b75c-6e4e22c0a7b3
                    status: Blocked
                    reason: repeated_attempts
                    vendor_data: user-1234
                    metadata: null
        '400':
          description: >-
            验证错误。格式错误的输入会返回 DRF 的字段错误响应格式（每个有问题的字段对应一个消息数组，选项错误则嵌套在 `options`
            下）。如果号码解析成功但在发送时被拒绝（无法接通，或属于不可用的线路类型，如特服短码），则会返回 `{"detail": ...}`
            响应格式。
          content:
            application/json:
              examples:
                Invalid phone number (field error):
                  summary: 号码不是有效的 E.164 格式
                  value:
                    phone_number:
                      - Invalid phone number provided.
                Missing phone number:
                  summary: 请求体中未包含 `phone_number`
                  value:
                    phone_number:
                      - This field is required.
                Invalid options:
                  summary: '`code_size` 超出范围、`locale` 格式错误、`preferred_channel` 未知'
                  value:
                    options:
                      code_size:
                        - Ensure this value is less than or equal to 8.
                      locale:
                        - Ensure this field has no more than 5 characters.
                      preferred_channel:
                        - '"carrier_pigeon" is not a valid choice.'
                Number rejected at send time:
                  summary: 符合 E.164 格式，但该号码无法接收 OTP
                  value:
                    detail: Invalid phone number provided.
                Ineligible line type:
                  summary: 线路类型无法接收一次性验证码（例如短代码）
                  value:
                    detail: Invalid phone line type provided.
        '403':
          description: >-
            权限被拒绝。当缺少 `x-api-key` 请求头时返回（`{"detail": ...}`）；错误、已撤销或已过期的密钥返回
            `401`。当组织从未充值（防滥用控制）或其余额无法覆盖目标目的地的发送价格时，也会在任何发送之前返回（`{"error":
            ...}`）。
          content:
            application/json:
              examples:
                Missing or invalid API key:
                  summary: 缺少 `x-api-key` 请求头，或者密钥无效/已被吊销
                  value:
                    detail: You do not have permission to perform this action.
                Top-up required:
                  summary: 电话验证将保持禁用状态，直到组织完成首次充值
                  value:
                    detail: >-
                      To protect against abuse, phone verification is disabled
                      until your first top-up.
                Not enough credits:
                  summary: 组织余额不足以支付发送价格
                  value:
                    error: You don't have enough credits to perform this request.
        '429':
          description: >-
            超出速率限制。POST/PATCH/DELETE endpoint 每个 API 密钥每分钟共享 300
            次写入请求的额度（交易创建和事件摄入具有单独的 600 次额度）。此外，上游对每个电话号码每小时设置了 4
            次验证尝试的防滥用上限。响应包含
            `X-RateLimit-Limit`、`X-RateLimit-Remaining`、`X-RateLimit-Reset` 和
            `Retry-After` 响应头。注意：`X-RateLimit-*`/`Retry-After` 响应头仅伴随 300
            次/分钟写入限制的 429 响应；针对每个号码上限的 429 响应不包含速率限制响应头。
          content:
            application/json:
              examples:
                Per-number cap:
                  summary: 过去一小时内同一号码的验证尝试超过 4 次
                  value:
                    detail: >-
                      Maximum verification attempts reached for this phone
                      number. Only 4 authentication attempts are allowed per
                      hour. Try again later or use a different number.
                Write rate limit exceeded:
                  summary: 当前分钟内的写入请求超过 300 次
                  value:
                    detail: >-
                      Write request rate limit exceeded. You can make up to 300
                      requests per minute.
              schema:
                type: object
                properties:
                  detail:
                    type: string
        '500':
          description: 创建验证时发生意外故障，且并非已知的提供商服务中断。可安全重试。
          content:
            application/json:
              examples:
                Provider error:
                  summary: 无法发起一次性验证码投递
                  value:
                    detail: Error creating phone verification
              schema:
                type: object
                properties:
                  detail:
                    type: string
        '502':
          description: 电话验证服务商暂时不可用：在内置重试后仍出现 5xx 响应、超时或连接失败。可以安全重试；不收取任何费用，也不会消耗尝试次数。
          content:
            application/json:
              examples:
                Provider outage:
                  summary: 服务商未响应发送请求
                  value:
                    detail: >-
                      Phone verification is temporarily unavailable. Please try
                      again shortly.
                    code: phone_provider_unavailable
              schema:
                type: object
                properties:
                  detail:
                    type: string
                  code:
                    type: string
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: curl
          label: curl
          source: |-
            curl -X POST https://verification.didit.me/v3/phone/send/ \
              -H 'x-api-key: YOUR_API_KEY' \
              -H 'Content-Type: application/json' \
              -d '{
                "phone_number": "+14155552671",
                "options": {
                  "code_size": 6,
                  "preferred_channel": "whatsapp",
                  "locale": "en-US"
                },
                "vendor_data": "user-1234"
              }'
        - lang: python
          label: Python
          source: |-
            from didit import Didit

            # Reads DIDIT_API_KEY.
            client = Didit()

            result = client.phone.send_code(
                phone_number="+14155552671",
                options={
                    "locale": "en-US",
                    "preferred_channel": "whatsapp",
                },
                vendor_data="user-1234",
            )
            print(result)
        - lang: javascript
          label: Node.js
          source: |-
            import Didit from "@didit-protocol/sdk-node";

            // Reads DIDIT_API_KEY.
            const didit = new Didit();

            const result = await didit.phone.sendCode({
              phone_number: "+14155552671",
              options: {
                locale: "en-US",
                preferred_channel: "whatsapp",
              },
              vendor_data: "user-1234",
            });
            console.log(result);
        - lang: go
          label: Go
          source: "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.SendCode(ctx, didit.PhoneSendCodeParams{\n\tPhoneNumber: \"+14155552671\",\n\tOptions: didit.Some(didit.PhoneSendCodeParamsOptions{\n\t\tLocale:           didit.String(\"en-US\"),\n\t\tPreferredChannel: didit.Some(didit.PhoneSendCodeParamsOptionsPreferredChannel(\"whatsapp\")),\n\t}),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)"
        - lang: java
          label: Java
          source: |-
            import me.didit.Didit;
            import me.didit.types.PhoneSendCodeParams;

            // Reads DIDIT_API_KEY.
            Didit client = new Didit();

            var result = client.phone().sendCode(PhoneSendCodeParams.builder()
                    .phoneNumber("+14155552671")
                    .options(PhoneSendCodeParams.Options.builder()
                            .locale("en-US")
                            .preferredChannel(PhoneSendCodeParams.Options.PreferredChannel.of("whatsapp"))
                            .build())
                    .vendorData("user-1234")
                    .build());
            System.out.println(result);
        - lang: php
          label: PHP
          source: |-
            <?php

            require 'vendor/autoload.php';

            use Didit\DiditClient;

            // Reads DIDIT_API_KEY.
            $didit = new DiditClient();

            $result = $didit->phone->sendCode([
                'phone_number' => '+14155552671',
                'options' => [
                    'locale' => 'en-US',
                    'preferred_channel' => 'whatsapp',
                ],
                'vendor_data' => 'user-1234',
            ]);
            print_r($result->toArray());
        - lang: ruby
          label: Ruby
          source: |-
            require "didit"

            # Reads DIDIT_API_KEY.
            didit = Didit::Client.new

            result = didit.phone.send_code(
              phone_number: "+14155552671",
              options: {
                locale: "en-US",
                preferred_channel: "whatsapp",
              },
              vendor_data: "user-1234"
            )
            puts result.to_h
        - lang: csharp
          label: .NET
          source: >-
            using System;

            using System.Text.Json;

            using Didit;


            // Reads DIDIT_API_KEY.

            using var client = new DiditClient();


            var result = await client.Phone.SendCodeAsync(new
            PhoneSendCodeParams

            {
                PhoneNumber = "+14155552671",
                Options = new PhoneSendCodeParamsOptions
                {
                    Locale = "en-US",
                    PreferredChannel = new PhoneSendCodeParamsOptionsPreferredChannel("whatsapp"),
                },
                VendorData = "user-1234",
            });

            Console.WriteLine(JsonSerializer.Serialize(result));
components:
  schemas:
    PhoneVerificationSendResponse:
      type: object
      properties:
        request_id:
          type: string
          format: uuid
          description: >-
            验证的会话 id。`Retry` 发送会返回与原始发送相同的 `request_id`。此 id 会显示在 Business
            Console 中，在已完成的 `POST /v3/phone/check/` 中再次返回，并可传递给 `GET
            /v3/session/{sessionId}/decision/`。
        status:
          type: string
          enum:
            - Success
            - Retry
            - Blocked
          description: >-
            `Success`：OTP 已发送至新的验证。`Retry`：为先前发送所创建的待处理验证重新发送了
            OTP。`Blocked`：防欺诈层拒绝发送；验证立即以 `Declined` 终结，且不产生计费。
        reason:
          type: string
          nullable: true
          description: >-
            发送被 `Blocked` 的原因（例如 `repeated_attempts`、`suspicious`、`spam`）。在
            `Success` 和 `Retry` 时为 `null`。
        vendor_data:
          type: string
          nullable: true
          description: 存储在会话中的 `vendor_data` 回显（来自首次发送）。
        metadata:
          type: object
          nullable: true
          description: 存储在会话中的 `metadata` 回显（来自首次发送）。
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        您的应用 API 密钥，可在 Business Console 的“开发者 -> API
        密钥”中获取。主密钥拥有完整访问权限。指定名称的密钥可设置作用域：针对每个资源设为无权限、只读或读写，限定于特定工作流或已通过的会话、限定于
        IP 地址列表，以及设置到期日期。401 表示密钥错误、已撤销或已过期；403 表示未发送密钥、密钥无权访问该资源或操作，或请求来自其 IP
        列表之外的地址；在会话路由上返回 404 表示该会话不在该密钥允许的工作流或状态范围内。没有媒体访问权限的密钥获取到的图片、视频和 PDF
        URL 将为 null；没有会话写入权限的密钥获取到的会话链接和 token 将为 null。请参阅
        https://docs.didit.me/console/api-keys。

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.