# Verify Users

Phone and email verification with zero vendor setup

Verify phone numbers and email addresses instantly — no Twilio account, no vendor onboarding, just a single API call.

## Quick Example

**Axios:**
```typescript
    import { withSapiom } from "@sapiom/axios";
    import axios from "axios";

    const client = withSapiom(axios.create(), {
      apiKey: process.env.SAPIOM_API_KEY,
    });

    const baseUrl = "https://prelude.services.sapiom.ai";

    // Step 1: Send a verification code
    const { data: sendData } = await client.post(`${baseUrl}/verifications`, {
      target: {
        type: "phone_number",
        value: "+15551234567",
      },
    });

    console.log("Verification sent:", sendData.id);

    // Step 2: Check the code (after user enters it)
    const { data: checkData } = await client.post(`${baseUrl}/verifications/check`, {
      verificationRequestId: sendData.id,
      code: "123456", // code entered by user
    });

    console.log("Verified:", checkData.status === "success");
    ```
  
**Fetch:**
```typescript
    import { createFetch } from "@sapiom/fetch";

    const fetch = createFetch({
      apiKey: process.env.SAPIOM_API_KEY,
    });

    const baseUrl = "https://prelude.services.sapiom.ai";

    // Step 1: Send a verification code
    const sendResponse = await fetch(`${baseUrl}/verifications`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        target: {
          type: "phone_number",
          value: "+15551234567",
        },
      }),
    });

    const sendData = await sendResponse.json();
    console.log("Verification sent:", sendData.id);

    // Step 2: Check the code (after user enters it)
    const checkResponse = await fetch(`${baseUrl}/verifications/check`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        verificationRequestId: sendData.id,
        code: "123456", // code entered by user
      }),
    });

    const checkData = await checkResponse.json();
    console.log("Verified:", checkData.status === "success");
    ```
  
## How It Works

When you send a verification request, Sapiom routes it to [Prelude](https://prelude.so), a verification service that handles SMS and email delivery. The SDK automatically handles payment negotiation so you don't need a separate Prelude account.

The verification flow has two steps:

1. **Send a code** — Call the `/verifications` endpoint with a phone number or email. The user receives a 6-digit code.
2. **Check the code** — When the user enters the code, call `/verifications/check` to verify it. This endpoint is free and rate-limited.

Verification codes expire after 10 minutes. If the user doesn't receive the code or it expires, send a new verification request.

## Provider

Powered by [Prelude](https://prelude.so). Prelude handles global SMS delivery with high deliverability and fraud prevention built in.

## API Reference

### Send Verification Code

**Endpoint:** `POST https://prelude.services.sapiom.ai/verifications`

Send a verification code to a phone number or email address.

#### Request

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `target.type` | string | Yes | `phone_number` or `email_address` |
| `target.value` | string | Yes | Phone number (E.164 format) or email address |
| `signals` | object | No | Additional signals for fraud detection |
| `options` | object | No | Verification options |
| `metadata` | object | No | Custom metadata to store with request |

**Phone number format:** Use international E.164 format with country code (e.g., `+15551234567` for US, `+442071234567` for UK).

**Phone:**
```json
    {
      "target": {
        "type": "phone_number",
        "value": "+15551234567"
      }
    }
    ```
  
**Email:**
```json
    {
      "target": {
        "type": "email_address",
        "value": "user@example.com"
      }
    }
    ```
  
#### Response

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending"
}
```

Save the `id` — you'll need it to check the code.

### Check Verification Code

**Endpoint:** `POST https://prelude.services.sapiom.ai/verifications/check`

Verify the code entered by the user. This endpoint is free but rate-limited to prevent brute-force attacks.

#### Request

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `verificationRequestId` | string | Yes | The `id` from the send response |
| `code` | string | Yes | 4-8 digit verification code |

```json
{
  "verificationRequestId": "550e8400-e29b-41d4-a716-446655440000",
  "code": "123456"
}
```

#### Response

```json
{
  "id": "660e8400-e29b-41d4-a716-446655440001",
  "status": "success"
}
```

| Status | Description |
|--------|-------------|
| `success` | Code is correct, verification complete |
| `pending` | Verification still in progress |
| `failure` | Code is incorrect |

### Error Codes

| Code | Description |
|------|-------------|
| 400 | Invalid phone number, email, or code format |
| 402 | Payment required — ensure you're using the Sapiom SDK |
| 404 | Verification request not found |
| 410 | Verification code has expired |
| 422 | Invalid verification code (wrong code entered) |
| 429 | Rate limit exceeded on check endpoint |

## Complete Example

**Axios:**
```typescript
    import { withSapiom } from "@sapiom/axios";
    import axios from "axios";

    const client = withSapiom(axios.create(), {
      apiKey: process.env.SAPIOM_API_KEY,
    });

    const baseUrl = "https://prelude.services.sapiom.ai";

    async function verifyPhoneNumber(phoneNumber: string, userCode: string) {
      // Step 1: Send verification code
      const sendResponse = await client.post(`${baseUrl}/verifications`, {
        target: {
          type: "phone_number",
          value: phoneNumber,
        },
      });

      const verificationId = sendResponse.data.id;
      console.log(`Verification code sent to ${phoneNumber}`);

      // Step 2: Check the code (after user enters it)
      const checkResponse = await client.post(`${baseUrl}/verifications/check`, {
        verificationRequestId: verificationId,
        code: userCode,
      });

      return checkResponse.data.status === "success";
    }

    // Usage
    const isVerified = await verifyPhoneNumber("+15551234567", "123456");
    console.log("Verified:", isVerified);
    ```
  
**Fetch:**
```typescript
    import { createFetch } from "@sapiom/fetch";

    const fetch = createFetch({
      apiKey: process.env.SAPIOM_API_KEY,
    });

    const baseUrl = "https://prelude.services.sapiom.ai";

    async function verifyPhoneNumber(phoneNumber: string, userCode: string) {
      // Step 1: Send verification code
      const sendResponse = await fetch(`${baseUrl}/verifications`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          target: {
            type: "phone_number",
            value: phoneNumber,
          },
        }),
      });

      const sendData = await sendResponse.json();
      const verificationId = sendData.id;
      console.log(`Verification code sent to ${phoneNumber}`);

      // Step 2: Check the code (after user enters it)
      const checkResponse = await fetch(`${baseUrl}/verifications/check`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          verificationRequestId: verificationId,
          code: userCode,
        }),
      });

      const checkData = await checkResponse.json();
      return checkData.status === "success";
    }

    // Usage
    const isVerified = await verifyPhoneNumber("+15551234567", "123456");
    console.log("Verified:", isVerified);
    ```
  
## Pricing

| Operation | Cost |
|-----------|------|
| Send verification code | $0.015 (1.5 cents) |
| Check verification code | Free |

> **Using Python?:** See [Service Proxy](/md/service-proxy.md) for REST API access without the Node.js SDK.