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.
POST https://my.textme.co.il/apiSending from an unverified source fails with status 515.
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
<?xml version="1.0" encoding="UTF-8"?>
<verify_phone>
<user>
<username>Leeroy</username>
</user>
<phone>5xxxxxxxx</phone>
<phone>3xxxxxxx</phone>
</verify_phone>{
"verify_phone": {
"user": {
"username": "Leeroy"
},
"phone": [
"5xxxxxxxx",
"3xxxxxxx"
]
}
}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"
]
}
}'// 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
// 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
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);# 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)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 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());
}
}// .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);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// [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
<?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>{
"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. |
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 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. Omit the field entirely to exclude them. | ➖ |
Request example
<?xml version="1.0" encoding="UTF-8"?>
<getVerifiedPhones>
<user>
<username>username</username>
</user>
<is_subs>1</is_subs>
</getVerifiedPhones>{
"getVerifiedPhones": {
"user": {
"username": "username"
},
"is_subs": "1"
}
}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"
}
}'// 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
// 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
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);# 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)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 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());
}
}// .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);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// [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
<?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>{
"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. |
Field names are upper case here
As with birthday campaigns, 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. An alphanumeric name has no line to receive a verification link, so it does not go through this operation. Registering one is self-service, in the console, rather than something TextMe arranges by hand.
Registering an alphanumeric sender
On the campaign screen, the Actions row beside the sender dropdown has a green +. It opens a three-step wizard, "Strong verification process for defining a text sender number":
- Sender name in text. Up to 11 characters, English only. Digits may appear inside the name.
- Upload identification documents. Two slots, and the step cannot be passed without a file. What it asks for is proof of the relationship between the name and the account holder: an ID card, driving licence or passport where the name is a person, or, where the name is a business, a corporate ownership document with a sworn affidavit by a lawyer.
- Approval of Terms of Use.
The identity check is required by a Ministry of Communications decision, not by TextMe, so it cannot be waived or shortened. No turnaround time is stated in the wizard.
Registering a business name needs a lawyer
Step 2 is the expensive one. If the sender name is a company name rather than the account holder's own, proving the link means a corporate ownership document and a sworn affidavit. That is a per-business cost in money and calendar time, and it is incurred once per sender name, not once per account. Anyone onboarding many businesses should price it in before promising them a branded sender.
:::
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.

