---
url: https://textme-docs.matat.io/he/endpoints/otp.md
description: >-
  send_otp ו-validate_otp: שליחת קוד חד-פעמי ב-SMS ואימותו, כאשר TextMe מייצרת,
  שומרת, סופרת ניסיונות ומפקיעה את הקוד.
---

# OTP

שולחת קוד חד-פעמי ב-SMS ומאמתת אותו בחזרה. TextMe מייצרת את הקוד, שומרת אותו מול מספר הטלפון, סופרת את הניסיונות ומפקיעה אותו, כך שאינכם צריכים לייצר או לשמור קודים בעצמכם.

```http
POST https://my.textme.co.il/api
```

שתי פעולות: [`send_otp`](#send-otp-send-a-one-time-code) מנפיקה קוד, ו-[`validate_otp`](#validate-otp-check-a-one-time-code) מאמתת אותו.

::: tip מאיפה זה מגיע
שתי הפעולות האלה מתועדות על ידי TextMe בכתובת [docs.textme.co.il/otp/](https://docs.textme.co.il/otp/), שהיא בילד תיעוד שני על אותו שרת שטבלת הנתיבים של האתר הראשי אינה מכילה. הפרמטרים, גופי הבקשה ונוסח התשובות למטה הם של הספק.

הן גם הסיבה לכך ש[טבלת קודי הסטטוס](../reference/status-codes.md#account-state) שלנו כוללת את `12` במשמעות *"When verifying OTP code: Unverified code"*.
:::

## `send_otp` שליחת קוד חד-פעמי {#send-otp-send-a-one-time-code}

מייצרת קוד, שולחת אותו ב-SMS, ומתחילה את חלון התוקף.

### פרמטרים

| שם | סוג | תיאור | חובה |
|---|---|---|---|
| `send_otp` | object | אלמנט השורש. | ✔️ |
| `user` | object | מכיל את אלמנט המשתמש. | ✔️ |
| `username` | string | שם המשתמש של החשבון שבו אתם מזוהים במערכת. | ✔️ |
| `phone` | int | היעד, בפורמט `5xxxxxxx` או `05xxxxxxx`. | ✔️ |
| `source` | string | השולח שממנו הקוד ייראה כאילו נשלח. חייב להיות [שולח מאומת](./verified-senders.md). | ✔️ |
| `app_id` | int | מפריד בין אפליקציות כשחשבון אחד משרת כמה מהן. ברירת המחדל היא `1`. | ➖ |
| `max_tries` | int | כמה ניסיונות אימות הקוד מאפשר, בין `3` ל-`5`. ברירת המחדל היא `3`. | ➖ |
| `valid_time` | int | כמה דקות הקוד נשאר בתוקף, בין `1` ל-`15`. ברירת המחדל היא `5`. | ➖ |
| `text` | string | נוסח ההודעה שלכם. **חייב להכיל `[code]`**, שיוחלף בקוד שנוצר. ברירת המחדל היא `the code is [code]`. | ➖ |

### דוגמת בקשה

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<send_otp>
    <user>
        <username>Leeroy</username>
    </user>
    <phone>5xxxxxxxx</phone>
    <source>DemoAPI</source>
    <app_id>1</app_id>
    <max_tries>3</max_tries>
    <valid_time>5</valid_time>
    <text>Your DemoAPI code is [code]</text>
</send_otp>
```

```json [JSON]
{
  "send_otp": {
    "user": {
      "username": "Leeroy"
    },
    "phone": "5xxxxxxxx",
    "source": "DemoAPI",
    "app_id": "1",
    "max_tries": "3",
    "valid_time": "5",
    "text": "Your DemoAPI code is [code]"
  }
}
```

```bash [cURL]
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "send_otp": {
    "user": {
      "username": "Leeroy"
    },
    "phone": "5xxxxxxxx",
    "source": "DemoAPI",
    "app_id": "1",
    "max_tries": "3",
    "valid_time": "5",
    "text": "Your DemoAPI code is [code]"
  }
}'
```

```js [JavaScript]
// Node.js 18+ או כל דפדפן מודרני. ללא תלויות
const response = await fetch('https://my.textme.co.il/api', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TEXTME_API_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    send_otp: {
      user: {
        username: 'Leeroy',
      },
      phone: '5xxxxxxxx',
      source: 'DemoAPI',
      app_id: '1',
      max_tries: '3',
      valid_time: '5',
      text: 'Your DemoAPI code is [code]',
    },
  }),
})

const result = await response.json()

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
```

```php [PHP]
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'headers' => [
        'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('https://my.textme.co.il/api', [
    'json' => [
        'send_otp' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'phone' => '5xxxxxxxx',
            'source' => 'DemoAPI',
            'app_id' => '1',
            'max_tries' => '3',
            'valid_time' => '5',
            'text' => 'Your DemoAPI code is [code]',
        ],
    ],
]);

$result = json_decode($response->getBody()->getContents(), true);

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
```

```php [Laravel]
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'send_otp' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'phone' => '5xxxxxxxx',
            'source' => 'DemoAPI',
            'app_id' => '1',
            'max_tries' => '3',
            'valid_time' => '5',
            'text' => 'Your DemoAPI code is [code]',
        ],
    ])
    ->throw()
    ->json();

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
```

```python [Python]
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://my.textme.co.il/api",
    headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
    json={
        "send_otp": {
            "user": {
                "username": "Leeroy",
            },
            "phone": "5xxxxxxxx",
            "source": "DemoAPI",
            "app_id": "1",
            "max_tries": "3",
            "valid_time": "5",
            "text": "Your DemoAPI code is [code]",
        },
    },
)
response.raise_for_status()
result = response.json()

# גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
```

```go [Go]
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"send_otp": map[string]any{
			"user": map[string]any{
				"username": "Leeroy",
			},
			"phone": "5xxxxxxxx",
			"source": "DemoAPI",
			"app_id": "1",
			"max_tries": "3",
			"valid_time": "5",
			"text": "Your DemoAPI code is [code]",
		},
	})

	req, _ := http.NewRequest("POST", "https://my.textme.co.il/api", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTME_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result struct {
		Status  json.Number `json:"status"`
		Message string      `json:"message"`
	}
	if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
		panic(err)
	}

	// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
```

```java [Java]
// Java 17+ באמצעות java.net.http. ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TextMeOtpSend {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "send_otp": {
                "user": {
                  "username": "Leeroy"
                },
                "phone": "5xxxxxxxx",
                "source": "DemoAPI",
                "app_id": "1",
                "max_tries": "3",
                "valid_time": "5",
                "text": "Your DemoAPI code is [code]"
              }
            }
            """;

        HttpRequest request = HttpRequest.newBuilder(URI.create("https://my.textme.co.il/api"))
            .header("Authorization", "Bearer " + System.getenv("TEXTME_API_TOKEN"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        // גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
        System.out.println(response.body());
    }
}
```

```csharp [C#]
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "send_otp": {
        "user": {
          "username": "Leeroy"
        },
        "phone": "5xxxxxxxx",
        "source": "DemoAPI",
        "app_id": "1",
        "max_tries": "3",
        "valid_time": "5",
        "text": "Your DemoAPI code is [code]"
      }
    }
    """;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("TEXTME_API_TOKEN"));

var response = await http.PostAsync("https://my.textme.co.il/api",
    new StringContent(payload, Encoding.UTF8, "application/json"));

var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
var status = result.GetProperty("status").ToString();

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
    var message = result.GetProperty("message").ToString();
    throw new Exception($"TextMe {status}: {message}");
}

Console.WriteLine(result);
```

```ruby [Ruby]
require "net/http"
require "json"

uri = URI("https://my.textme.co.il/api")

request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('TEXTME_API_TOKEN')}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
  "send_otp" => {
    "user" => {
      "username" => "Leeroy",
    },
    "phone" => "5xxxxxxxx",
    "source" => "DemoAPI",
    "app_id" => "1",
    "max_tries" => "3",
    "valid_time" => "5",
    "text" => "Your DemoAPI code is [code]",
  },
})

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

result = JSON.parse(response.body)

# גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?

pp result
```

```rust [Rust]
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result: Value = reqwest::Client::new()
        .post("https://my.textme.co.il/api")
        .bearer_auth(std::env::var("TEXTME_API_TOKEN")?)
        .json(&json!({
          "send_otp": {
            "user": {
              "username": "Leeroy"
            },
            "phone": "5xxxxxxxx",
            "source": "DemoAPI",
            "app_id": "1",
            "max_tries": "3",
            "valid_time": "5",
            "text": "Your DemoAPI code is [code]"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

    println!("{result}");
    Ok(())
}
```

:::

### תשובה

::: code-group

```xml [XML]
<?xml version="1.0" encoding="utf-8"?>
<sms>
    <status>0</status>
    <code>448431</code>
    <message>The code is : 448431 and is valid for 5 minutes</message>
</sms>
```

```json [JSON]
{
  "status": 0,
  "code": "448431",
  "message": "The code is : 448431 and is valid for 5 minutes"
}
```

:::

`status` בערך `0` אומר שהקוד נוצר וה-SMS נתקבל לשליחה.

שימו לב למבנה התשובה: אלמנט השורש הוא `sms` ולא `send_otp`, ו**הקוד שנוצר חוזר בשדה `code`**, עם אותו ערך שחוזר גם בתוך `message`.

::: danger התשובה מכילה את הקוד
`send_otp` מחזירה לקורא את הקוד בטקסט גלוי. כל דבר שמתעד את גוף התשובה, כלי APM, מדווח שגיאות, לוג דיבאג, מתעד קוד חד-פעמי פעיל עבור אותו מספר טלפון.

הסתירו את `code` ואת `message` לפני תיעוד. אינכם צריכים אף אחד מהערכים כדי להשלים את התהליך: המכשיר מקבל את הקוד, ו-`validate_otp` אומרת לכם אם המשתמש הקליד אותו נכון.
:::

### שגיאות

| Status | מתי |
|---|---|
| `2` | חסר אלמנט חובה; ה-`message` מציין איזה. |
| `4` | אין מספיק יתרה לשליחת ה-SMS. |
| `9` | ה-`phone` קצר או ארוך מדי. |
| `515` | ה-`source` אינו שולח מאומת. |
| `989` | ה-`text` ריק או עובר את מגבלת אורך ההודעה. |

## `validate_otp` אימות קוד חד-פעמי {#validate-otp-check-a-one-time-code}

מאמתת קוד שהמשתמש הקליד מול הקוד השמור עבור אותו טלפון ואותו `app_id`.

### פרמטרים

| שם | סוג | תיאור | חובה |
|---|---|---|---|
| `validate_otp` | object | אלמנט השורש. | ✔️ |
| `user` | object | מכיל את אלמנט המשתמש. | ✔️ |
| `username` | string | שם המשתמש של החשבון שבו אתם מזוהים במערכת. | ✔️ |
| `phone` | int | המספר שאליו נשלח הקוד, בפורמט `5xxxxxxx` או `05xxxxxxx`. | ✔️ |
| `code` | int | הקוד בן שש הספרות לאימות. | ✔️ |
| `app_id` | int | חייב להתאים ל-`app_id` שנשלח בזמן השליחה. ברירת המחדל היא `1`. | ➖ |
| `service_type` | string | הערוץ שהנפיק את הקוד, `sms` או `whatsapp`. ברירת המחדל היא `sms`. | ➖ |

### דוגמת בקשה

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<validate_otp>
    <user>
        <username>Leeroy</username>
    </user>
    <phone>5xxxxxxxx</phone>
    <app_id>1</app_id>
    <code>407526</code>
    <service_type>sms</service_type>
</validate_otp>
```

```json [JSON]
{
  "validate_otp": {
    "user": {
      "username": "Leeroy"
    },
    "phone": "5xxxxxxxx",
    "app_id": "1",
    "code": "407526",
    "service_type": "sms"
  }
}
```

```bash [cURL]
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "validate_otp": {
    "user": {
      "username": "Leeroy"
    },
    "phone": "5xxxxxxxx",
    "app_id": "1",
    "code": "407526",
    "service_type": "sms"
  }
}'
```

```js [JavaScript]
// Node.js 18+ או כל דפדפן מודרני. ללא תלויות
const response = await fetch('https://my.textme.co.il/api', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TEXTME_API_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    validate_otp: {
      user: {
        username: 'Leeroy',
      },
      phone: '5xxxxxxxx',
      app_id: '1',
      code: '407526',
      service_type: 'sms',
    },
  }),
})

const result = await response.json()

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
```

```php [PHP]
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'headers' => [
        'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('https://my.textme.co.il/api', [
    'json' => [
        'validate_otp' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'phone' => '5xxxxxxxx',
            'app_id' => '1',
            'code' => '407526',
            'service_type' => 'sms',
        ],
    ],
]);

$result = json_decode($response->getBody()->getContents(), true);

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
```

```php [Laravel]
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'validate_otp' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'phone' => '5xxxxxxxx',
            'app_id' => '1',
            'code' => '407526',
            'service_type' => 'sms',
        ],
    ])
    ->throw()
    ->json();

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
```

```python [Python]
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://my.textme.co.il/api",
    headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
    json={
        "validate_otp": {
            "user": {
                "username": "Leeroy",
            },
            "phone": "5xxxxxxxx",
            "app_id": "1",
            "code": "407526",
            "service_type": "sms",
        },
    },
)
response.raise_for_status()
result = response.json()

# גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
```

```go [Go]
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"validate_otp": map[string]any{
			"user": map[string]any{
				"username": "Leeroy",
			},
			"phone": "5xxxxxxxx",
			"app_id": "1",
			"code": "407526",
			"service_type": "sms",
		},
	})

	req, _ := http.NewRequest("POST", "https://my.textme.co.il/api", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTME_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result struct {
		Status  json.Number `json:"status"`
		Message string      `json:"message"`
	}
	if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
		panic(err)
	}

	// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
```

```java [Java]
// Java 17+ באמצעות java.net.http. ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TextMeOtpValidate {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "validate_otp": {
                "user": {
                  "username": "Leeroy"
                },
                "phone": "5xxxxxxxx",
                "app_id": "1",
                "code": "407526",
                "service_type": "sms"
              }
            }
            """;

        HttpRequest request = HttpRequest.newBuilder(URI.create("https://my.textme.co.il/api"))
            .header("Authorization", "Bearer " + System.getenv("TEXTME_API_TOKEN"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        // גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
        System.out.println(response.body());
    }
}
```

```csharp [C#]
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "validate_otp": {
        "user": {
          "username": "Leeroy"
        },
        "phone": "5xxxxxxxx",
        "app_id": "1",
        "code": "407526",
        "service_type": "sms"
      }
    }
    """;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("TEXTME_API_TOKEN"));

var response = await http.PostAsync("https://my.textme.co.il/api",
    new StringContent(payload, Encoding.UTF8, "application/json"));

var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
var status = result.GetProperty("status").ToString();

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
    var message = result.GetProperty("message").ToString();
    throw new Exception($"TextMe {status}: {message}");
}

Console.WriteLine(result);
```

```ruby [Ruby]
require "net/http"
require "json"

uri = URI("https://my.textme.co.il/api")

request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('TEXTME_API_TOKEN')}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
  "validate_otp" => {
    "user" => {
      "username" => "Leeroy",
    },
    "phone" => "5xxxxxxxx",
    "app_id" => "1",
    "code" => "407526",
    "service_type" => "sms",
  },
})

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

result = JSON.parse(response.body)

# גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?

pp result
```

```rust [Rust]
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result: Value = reqwest::Client::new()
        .post("https://my.textme.co.il/api")
        .bearer_auth(std::env::var("TEXTME_API_TOKEN")?)
        .json(&json!({
          "validate_otp": {
            "user": {
              "username": "Leeroy"
            },
            "phone": "5xxxxxxxx",
            "app_id": "1",
            "code": "407526",
            "service_type": "sms"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

    println!("{result}");
    Ok(())
}
```

:::

### תשובה

::: code-group

```xml [XML]
<?xml version="1.0" encoding="utf-8"?>
<sms>
    <status>0</status>
    <message>The code successfully validated</message>
</sms>
```

```json [JSON]
{
  "status": 0,
  "message": "The code successfully validated"
}
```

:::

### שגיאות

| Status | מתי |
|---|---|
| `12` | **הקוד לא אומת.** המשמעות המתועדת של `12` בפעולה הזו היא *Unverified code*: קוד שגוי, קוד שפג תוקפו, או ניסיונות שנגמרו. |
| `2` | חסר אלמנט חובה; ה-`message` מציין איזה. |

::: danger סטטוס 12 הוא זה שצריך להתנות עליו
ל-`12` שתי משמעויות שאינן קשורות, תלוי בפעולה: *אין מספיק כסף* בשליחת הודעה, ו*קוד לא מאומת* כאן. קראו אותו מול הקריאה שביצעתם, מעולם לא בפני עצמו. ראו [קודי סטטוס](../reference/status-codes.md#account-state).
:::

## הערות על השדות

### `app_id` מפריד בין האפליקציות שלכם

חשבון TextMe אחד יכול לשרת כמה מוצרים. מתן `app_id` נפרד לכל אחד שומר על עצמאות הקודים, כך שקוד שהונפק לאפליקציה `1000` אינו ניתן לאימות מול אפליקציה `1001`, גם עבור אותו מספר טלפון.

הערך חייב להיות זהה בין `send_otp` ל-`validate_otp`. אי-התאמה אינה ניתנת להבחנה מקוד שגוי: תקבלו סטטוס `12`.

### כוונו את `max_tries` ואת `valid_time` יחד

ברירת המחדל היא שלושה ניסיונות בתוך חמש דקות. שניהם מגבילים את אותו סיכון משני כיוונים:

* `valid_time` (1 עד 15 דקות) מגביל כמה זמן קוד שדלף נשאר שימושי. קצר יותר בטוח יותר, אבל קצר מדי ומשתמשים שעוברים לאפליקציה אחרת לקרוא את ה-SMS יחזרו לקוד שפג.
* `max_tries` (3 עד 5) מגביל ניחושים. קוד בן שש ספרות עם חמישה ניסיונות בטוח בהחלט.

הניסיונות נספרים בצד השרת, כך שאינכם צריכים לעקוב אחריהם.

### כתבו את ההודעה כך שהקוד יימצא

`text` חייב להכיל `[code]`. אם תשמיטו אותו, הנמען יקבל הודעה בלי קוד בתוכה.

שימו את הקוד בתחילת ההודעה ושמרו על נוסח פשוט, כי מכשירים ומנהלי סיסמאות מזהים קודים טוב יותר בהודעות קצרות. אם אתם משתמשים ב-SMS Retriever של Google, שדה [`tag`](./send.md#parameters) בשליחת `sms` רגילה הוא המנגנון לתחילית `<#>`; ל-`send_otp` אין מקבילה, ולכן תהליך שזקוק לזה צריך לשלוח את הקוד עם [`sms`](./send.md) ולבצע אימות עצמאי.

### מה זה מחליף

בלי שתי הפעולות האלה הייתם שולחים קוד עם [`sms`](./send.md), שומרים אותו בעצמכם, מפקיעים אותו בעצמכם וסופרים ניסיונות בעצמכם. `send_otp` מעבירה את כל זה לצד השרת: יצירה, שמירה, הפקעה וספירת ניסיונות. אתם עדיין מקבלים את הקוד בתשובה, כך שכלי תמיכה יכול להציג אותו אם עמדת הרגולציה שלכם מאפשרת, אבל התייחסו לזה כבחירה מודעת ולא כברירת מחדל.

### מה לא מכוסה כאן

TextMe מתעדת גם קריאת OTP ב-WhatsApp בנקודת קצה נפרדת (`whatsapp-api/send-otp-whatsapp`, טוקן bearer, JSON בלבד, בלי XML), לצד API מלא ל-WhatsApp. שניהם עדיין אינם מכוסים באתר הזה. שניהם נמצאים מחוץ ל-API של ה-SMS שהעמוד הזה שייך לו, והשדה `service_type` שלמעלה הוא מה שמקשר בין שני ערוצי ה-OTP.
