---
url: https://textme-docs.matat.io/endpoints/verified-senders.md
description: >-
  verify_phone and getVerifiedPhones: submit numbers for sender verification and
  list the senders already approved.
---

# Verified senders

The `source` on a send (the name or number the message appears to come from) has to be verified before it can be used. These two operations submit numbers for verification and list the ones already approved.

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

Sending from an unverified source fails with [status `515`](../reference/status-codes.md#account-state).

## `verify_phone` Submit numbers for verification

Starts verification for one or more numbers. Both mobile numbers and landlines are accepted, and each is verified differently. See the response note below.

### Parameters

| Name | Type | Description | Required |
|---|---|---|---|
| `verify_phone` | object | Contains all other elements. | ✔️ |
| `user` | object | Contains the user element. | ✔️ |
| `username` | string | The username of the account by which you are recognized in the system. | ✔️ |
| `phone` | object | A number to use as an SMS source. Must be a legal mobile or landline number. Repeatable. | ✔️ |

### Request example

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<verify_phone>
    <user>
        <username>Leeroy</username>
    </user>
    <phone>5xxxxxxxx</phone>
    <phone>3xxxxxxx</phone>
</verify_phone>
```

```json [JSON]
{
  "verify_phone": {
    "user": {
      "username": "Leeroy"
    },
    "phone": [
      "5xxxxxxxx",
      "3xxxxxxx"
    ]
  }
}
```

```bash [cURL]
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "verify_phone": {
    "user": {
      "username": "Leeroy"
    },
    "phone": [
      "5xxxxxxxx",
      "3xxxxxxx"
    ]
  }
}'
```

```js [JavaScript]
// Node.js 18+ or any modern browser. No dependencies
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({
    verify_phone: {
      user: {
        username: 'Leeroy',
      },
      phone: [
        '5xxxxxxxx',
        '3xxxxxxx',
      ],
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too, so the payload status is what counts
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' => [
        'verify_phone' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'phone' => [
                '5xxxxxxxx',
                '3xxxxxxx',
            ],
        ],
    ],
]);

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

// Errors arrive as HTTP 200 too, so the payload status is what counts
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', [
        'verify_phone' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'phone' => [
                '5xxxxxxxx',
                '3xxxxxxx',
            ],
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too, so the payload status is what counts
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={
        "verify_phone": {
            "user": {
                "username": "Leeroy",
            },
            "phone": [
                "5xxxxxxxx",
                "3xxxxxxx",
            ],
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too, so the payload status is what counts
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{
		"verify_phone": map[string]any{
			"user": map[string]any{
				"username": "Leeroy",
			},
			"phone": []any{
				"5xxxxxxxx",
				"3xxxxxxx",
			},
		},
	})

	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)
	}

	// Errors arrive as HTTP 200 too, so the payload status is what counts
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

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

```java [Java]
// Java 17+ using java.net.http. No dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TextMeVerifyPhone {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "verify_phone": {
                "user": {
                  "username": "Leeroy"
                },
                "phone": [
                  "5xxxxxxxx",
                  "3xxxxxxx"
                ]
              }
            }
            """;

        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());

        // Errors arrive as HTTP 200 too, so the payload status is what counts
        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 = """
    {
      "verify_phone": {
        "user": {
          "username": "Leeroy"
        },
        "phone": [
          "5xxxxxxxx",
          "3xxxxxxx"
        ]
      }
    }
    """;

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();

// Errors arrive as HTTP 200 too, so the payload status is what counts
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({
  "verify_phone" => {
    "user" => {
      "username" => "Leeroy",
    },
    "phone" => [
      "5xxxxxxxx",
      "3xxxxxxx",
    ],
  },
})

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

result = JSON.parse(response.body)

# Errors arrive as HTTP 200 too, so the payload status is what counts
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!({
          "verify_phone": {
            "user": {
              "username": "Leeroy"
            },
            "phone": [
              "5xxxxxxxx",
              "3xxxxxxx"
            ]
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // Errors arrive as HTTP 200 too, so the payload status is what counts
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

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

:::

### Response

::: code-group

```xml [XML]
<?xml version="1.0" encoding="utf-8"?>
<verify_phone>
    <status>0</status>
    <verify_message>verification link will be send to 5******** in order to complete the verification of 3******* call to 0552458888</verify_message>
</verify_phone>
```

```json [JSON]
{
  "status": 0,
  "verify_message": "verification link will be send to 5******** in order to complete the verification of 3******* call to 0552458888"
}
```

:::

| Field | Type | Description |
|---|---|---|
| `status` | int | `0`. The numbers were accepted for verification. |
| `verify_message` | string | What happens next, per number. Note the field name: `verify_message`, not `message`. |

::: warning Accepted is not verified
`status: 0` means verification has *started*. The `verify_message` explains what each number still needs:

* **Mobile numbers** receive a verification link by SMS, which someone must follow.
* **Landlines** cannot receive SMS, so verification is completed by calling the number given in the message.

Nothing can be sent from the number until that step is done. Confirm with [`getVerifiedPhones`](#getverifiedphones-list-verified-numbers) rather than assuming.
:::

### Errors

| Status | When |
|---|---|
| `510` | invalid verify\_phone request: no phones to verify, no `phone` elements were supplied. |
| `9` | A number is too short or too long. |
| `511` | The account is not entitled to this operation. |

## `getVerifiedPhones` List verified numbers

Returns the numbers already approved as senders, with when they were assigned and last updated.

### Parameters

| Name | Type | Description | Required |
|---|---|---|---|
| `getVerifiedPhones` | object | Contains all other elements. | ✔️ |
| `user` | object | Contains the user element. | ✔️ |
| `username` | string | The username of the account by which you are recognized in the system. | ✔️ |
| `is_subs` | int | Set to `1` to include the verified numbers of your [sub-accounts](./subscribers.md). Omit the field entirely to exclude them. | ➖ |

### Request example

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<getVerifiedPhones>
    <user>
        <username>username</username>
    </user>
    <is_subs>1</is_subs>
</getVerifiedPhones>
```

```json [JSON]
{
  "getVerifiedPhones": {
    "user": {
      "username": "username"
    },
    "is_subs": "1"
  }
}
```

```bash [cURL]
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "getVerifiedPhones": {
    "user": {
      "username": "username"
    },
    "is_subs": "1"
  }
}'
```

```js [JavaScript]
// Node.js 18+ or any modern browser. No dependencies
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({
    getVerifiedPhones: {
      user: {
        username: 'username',
      },
      is_subs: '1',
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too, so the payload status is what counts
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' => [
        'getVerifiedPhones' => [
            'user' => [
                'username' => 'username',
            ],
            'is_subs' => '1',
        ],
    ],
]);

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

// Errors arrive as HTTP 200 too, so the payload status is what counts
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', [
        'getVerifiedPhones' => [
            'user' => [
                'username' => 'username',
            ],
            'is_subs' => '1',
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too, so the payload status is what counts
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={
        "getVerifiedPhones": {
            "user": {
                "username": "username",
            },
            "is_subs": "1",
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too, so the payload status is what counts
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{
		"getVerifiedPhones": map[string]any{
			"user": map[string]any{
				"username": "username",
			},
			"is_subs": "1",
		},
	})

	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)
	}

	// Errors arrive as HTTP 200 too, so the payload status is what counts
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

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

```java [Java]
// Java 17+ using java.net.http. No dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TextMeVerifiedPhones {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "getVerifiedPhones": {
                "user": {
                  "username": "username"
                },
                "is_subs": "1"
              }
            }
            """;

        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());

        // Errors arrive as HTTP 200 too, so the payload status is what counts
        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 = """
    {
      "getVerifiedPhones": {
        "user": {
          "username": "username"
        },
        "is_subs": "1"
      }
    }
    """;

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();

// Errors arrive as HTTP 200 too, so the payload status is what counts
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({
  "getVerifiedPhones" => {
    "user" => {
      "username" => "username",
    },
    "is_subs" => "1",
  },
})

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

result = JSON.parse(response.body)

# Errors arrive as HTTP 200 too, so the payload status is what counts
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!({
          "getVerifiedPhones": {
            "user": {
              "username": "username"
            },
            "is_subs": "1"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // Errors arrive as HTTP 200 too, so the payload status is what counts
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

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

:::

### Response

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<getVerifiedPhones>
    <status>0</status>
    <message></message>
    <verified_phones>
        <verified_phone>
            <PHONE>xxxxxxxxxx</PHONE>
            <USER_ID>xxx</USER_ID>
            <ASSIGNMENT_DATE>03/12/14 12:33</ASSIGNMENT_DATE>
            <LAST_UPDATE>03/12/14 12:33</LAST_UPDATE>
        </verified_phone>
    </verified_phones>
</getVerifiedPhones>
```

```json [JSON]
{
  "status": 0,
  "message": "",
  "verified_phones": [
    {
      "PHONE": "xxxxxxxxxx",
      "USER_ID": "XXXX",
      "ASSIGNMENT_DATE": "03/12/14 12:33",
      "LAST_UPDATE": "03/12/14 12:33"
    }
  ]
}
```

:::

| Field | Type | Description |
|---|---|---|
| `verified_phones` | array | One entry per verified number. |
| `PHONE` | string | The verified number. |
| `USER_ID` | string | The account it belongs to. Useful when `is_subs` is `1`. |
| `ASSIGNMENT_DATE` | string | When it was verified, `dd/mm/yy hh:mm`. |
| `LAST_UPDATE` | string | When the record last changed. |

::: tip Field names are upper case here
As with [birthday campaigns](./campaigns.md), this response uses `PHONE`, `USER_ID`, `ASSIGNMENT_DATE` and `LAST_UPDATE` rather than the lower-case style used elsewhere.
:::

### Errors

| Status | When |
|---|---|
| `3`, `10`, `11` | Token invalid, expired, or belonging to another username. |
| `511` | The account is not entitled to this operation. |

## Field notes

### Numbers and alphanumeric senders

A `source` can be a number (`0551234567`) or a short alphanumeric name (`DemoAPI`). Up to 11 characters, letters and digits only, no `+`.

`verify_phone` covers the numeric case: it proves you control the line. Alphanumeric sender names are arranged with TextMe directly rather than through this operation, because there is no line to send a verification link to.

### Verify before you need it

Verification involves a human (following a link, or placing a call) so it is not something to discover at deploy time. Call `getVerifiedPhones` as a startup check and fail loudly if the sender your configuration expects is missing. That turns a silent run of `515` failures into one clear error before any traffic moves.

### Sub-accounts

A reseller's sub-accounts verify their own senders. `is_subs: 1` gives the parent a single view across all of them, with `USER_ID` identifying the owner of each. Sub-account management is covered in [Subscribers](./subscribers.md).

### Voice calls do not use it

A [TTS](./tts.md) call always originates from a TextMe system number, whatever `source` says. Verification governs the SMS half of a send only.
