Skip to content

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.

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

NameTypeDescriptionRequired
verify_phoneobjectContains all other elements.✔️
userobjectContains the user element.✔️
usernamestringThe username of the account by which you are recognized in the system.✔️
phoneobjectA number to use as an SMS source. Must be a legal mobile or landline number. Repeatable.✔️

Request example

xml
<?xml version="1.0" encoding="UTF-8"?>
<verify_phone>
    <user>
        <username>Leeroy</username>
    </user>
    <phone>5xxxxxxxx</phone>
    <phone>3xxxxxxx</phone>
</verify_phone>
json
{
  "verify_phone": {
    "user": {
      "username": "Leeroy"
    },
    "phone": [
      "5xxxxxxxx",
      "3xxxxxxx"
    ]
  }
}
bash
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
// Node.js 18+ / browsers — 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 — the payload status is what counts
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
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 — the payload status is what counts
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
php
<?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 — the payload status is what counts
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
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 — the payload status is what counts
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
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 — 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 17+ — 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 — the payload status is what counts
        System.out.println(response.body());
    }
}
csharp
// .NET 8+ — 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 — 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
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 — the payload status is what counts
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?

pp result
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 — the payload status is what counts
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

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

Response

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
{
  "status": 0,
  "verify_message": "verification link will be send to 5******** in order to complete the verification of 3******* call to 0552458888"
}
FieldTypeDescription
statusint0 — the numbers were accepted for verification.
verify_messagestringWhat 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

StatusWhen
510invalid verify_phone request: no phones to verify — no phone elements were supplied.
9A number is too short or too long.
511The 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

NameTypeDescriptionRequired
getVerifiedPhonesobjectContains all other elements.✔️
userobjectContains the user element.✔️
usernamestringThe username of the account by which you are recognized in the system.✔️
is_subsintSet to 1 to include the verified numbers of your sub-accounts. Omit the field entirely to exclude them.

Request example

xml
<?xml version="1.0" encoding="UTF-8"?>
<getVerifiedPhones>
    <user>
        <username>username</username>
    </user>
    <is_subs>1</is_subs>
</getVerifiedPhones>
json
{
  "getVerifiedPhones": {
    "user": {
      "username": "username"
    },
    "is_subs": "1"
  }
}
bash
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
// Node.js 18+ / browsers — 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 — the payload status is what counts
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
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 — the payload status is what counts
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
php
<?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 — the payload status is what counts
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
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 — the payload status is what counts
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
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 — 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 17+ — 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 — the payload status is what counts
        System.out.println(response.body());
    }
}
csharp
// .NET 8+ — 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 — 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
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 — the payload status is what counts
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?

pp result
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 — the payload status is what counts
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

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

Response

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
{
  "status": 0,
  "message": "",
  "verified_phones": [
    {
      "PHONE": "xxxxxxxxxx",
      "USER_ID": "XXXX",
      "ASSIGNMENT_DATE": "03/12/14 12:33",
      "LAST_UPDATE": "03/12/14 12:33"
    }
  ]
}
FieldTypeDescription
verified_phonesarrayOne entry per verified number.
PHONEstringThe verified number.
USER_IDstringThe account it belongs to — useful when is_subs is 1.
ASSIGNMENT_DATEstringWhen it was verified, dd/mm/yy hh:mm.
LAST_UPDATEstringWhen 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

StatusWhen
3, 10, 11Token invalid, expired, or belonging to another username.
511The 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.

Voice calls do not use it

A TTS call always originates from a TextMe system number, whatever source says. Verification governs the SMS half of a send only.