Voice (TTS)
Turns a written message into a spoken phone call. This is the sms operation with a tts block attached: same root element, same destinations, same everything — plus instructions for how to say it.
POST https://my.textme.co.il/apiIts main use is reaching handsets that cannot receive regular SMS. Kosher (filtered) phones are widespread in Israel and report DLR status 15 when texted; tts.type 2 routes those numbers to a voice call automatically while everyone else still gets an SMS.
sms Send a voice message
Parameters
Everything sms accepts, plus:
| Name | Type | Description | Required |
|---|---|---|---|
tts | object | Contains all settings for sending calls. Its presence is what turns the send into a voice send. | ✔️ |
tts.type | int | Which channel each destination gets — see the table below. | ✔️ |
tts.rate | int | Speech speed, from -10 to 10. 0 is normal. | ➖ |
tts.repeat | int | How many times the message plays during the call. Default is twice. | ➖ |
tts.voice | string | ymMale or ymFemale. Default is ymMale. | ➖ |
And the fields it shares with a text send:
| Name | Type | Description | Required |
|---|---|---|---|
user.username | string | The username of the account by which you are recognized in the system. | ✔️ |
source | string | Sender for the SMS half of the send. Maximum 11 characters. Calls always originate from a system number — source does not apply to them. | ✔️ |
destinations | object | Contains every destination. May hold multiple phone and cl_id elements. | ✔️ |
phone | string | A destination number, formatted 5xxxxxxx or 05xxxxxxx. | ✔️ |
message | string | The text that will be spoken, and texted where type calls for it. Maximum 1005 characters. | ✔️ |
tts.type
| Value | Behaviour |
|---|---|
2 | Send a regular message to a regular phone, and a voice call to a Kosher phone. |
3 | Send both a regular message and a voice call to all numbers. |
4 | Send a voice call only to all numbers. |
2 is the one you usually want: it costs a call only where a text would have failed. 3 doubles up deliberately — useful for urgent notifications. 4 skips SMS entirely.
Request example
<?xml version="1.0" encoding="UTF-8"?>
<sms>
<user>
<username>Leeroy</username>
</user>
<source>DemoAPI</source>
<destinations>
<cl_id>21518</cl_id>
<cl_id>21500</cl_id>
<phone id="external id1">5xxxxxxxx</phone>
<phone id="external id2">5xxxxxxxx</phone>
<phone>5xxxxxxxx</phone>
<phone id="">5xxxxxxxx</phone>
</destinations>
<message>This is a sample tts message</message>
<tts>
<type>4</type>
<rate>-1</rate>
<repeat>1</repeat>
<voice>ymMale</voice>
</tts>
</sms>{
"sms": {
"user": {
"username": "Leeroy"
},
"source": "DemoAPI",
"destinations": {
"cl_id": [
"21518",
"21500"
],
"phone": [
{
"$": {
"id": "external id1"
},
"_": "5xxxxxxxx"
},
{
"$": {
"id": "external id2"
},
"_": "5xxxxxxxx"
},
{
"_": "5xxxxxxxx"
},
{
"$": {
"id": ""
},
"_": "5xxxxxxxx"
}
]
},
"message": "This is a sample tts message",
"tts": {
"type": 4,
"rate": -1,
"repeat": 1,
"voice": "ymMale"
}
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"sms": {
"user": {
"username": "Leeroy"
},
"source": "DemoAPI",
"destinations": {
"cl_id": [
"21518",
"21500"
],
"phone": [
{
"$": {
"id": "external id1"
},
"_": "5xxxxxxxx"
},
{
"$": {
"id": "external id2"
},
"_": "5xxxxxxxx"
},
{
"_": "5xxxxxxxx"
},
{
"$": {
"id": ""
},
"_": "5xxxxxxxx"
}
]
},
"message": "This is a sample tts message",
"tts": {
"type": 4,
"rate": -1,
"repeat": 1,
"voice": "ymMale"
}
}
}'// 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({
sms: {
user: {
username: 'Leeroy',
},
source: 'DemoAPI',
destinations: {
cl_id: [
'21518',
'21500',
],
phone: [
{
$: {
id: 'external id1',
},
_: '5xxxxxxxx',
},
{
$: {
id: 'external id2',
},
_: '5xxxxxxxx',
},
{
_: '5xxxxxxxx',
},
{
$: {
id: '',
},
_: '5xxxxxxxx',
},
],
},
message: 'This is a sample tts message',
tts: {
type: 4,
rate: -1,
repeat: 1,
voice: 'ymMale',
},
},
}),
})
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
// 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' => [
'sms' => [
'user' => [
'username' => 'Leeroy',
],
'source' => 'DemoAPI',
'destinations' => [
'cl_id' => [
'21518',
'21500',
],
'phone' => [
[
'$' => [
'id' => 'external id1',
],
'_' => '5xxxxxxxx',
],
[
'$' => [
'id' => 'external id2',
],
'_' => '5xxxxxxxx',
],
[
'_' => '5xxxxxxxx',
],
[
'$' => [
'id' => '',
],
'_' => '5xxxxxxxx',
],
],
],
'message' => 'This is a sample tts message',
'tts' => [
'type' => 4,
'rate' => -1,
'repeat' => 1,
'voice' => 'ymMale',
],
],
],
]);
$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
use Illuminate\Support\Facades\Http;
$result = Http::withToken(config('services.textme.token'))
->acceptJson()
->post('https://my.textme.co.il/api', [
'sms' => [
'user' => [
'username' => 'Leeroy',
],
'source' => 'DemoAPI',
'destinations' => [
'cl_id' => [
'21518',
'21500',
],
'phone' => [
[
'$' => [
'id' => 'external id1',
],
'_' => '5xxxxxxxx',
],
[
'$' => [
'id' => 'external id2',
],
'_' => '5xxxxxxxx',
],
[
'_' => '5xxxxxxxx',
],
[
'$' => [
'id' => '',
],
'_' => '5xxxxxxxx',
],
],
],
'message' => 'This is a sample tts message',
'tts' => [
'type' => 4,
'rate' => -1,
'repeat' => 1,
'voice' => 'ymMale',
],
],
])
->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);# 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={
"sms": {
"user": {
"username": "Leeroy",
},
"source": "DemoAPI",
"destinations": {
"cl_id": [
"21518",
"21500",
],
"phone": [
{
"$": {
"id": "external id1",
},
"_": "5xxxxxxxx",
},
{
"$": {
"id": "external id2",
},
"_": "5xxxxxxxx",
},
{
"_": "5xxxxxxxx",
},
{
"$": {
"id": "",
},
"_": "5xxxxxxxx",
},
],
},
"message": "This is a sample tts message",
"tts": {
"type": 4,
"rate": -1,
"repeat": 1,
"voice": "ymMale",
},
},
},
)
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)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"sms": map[string]any{
"user": map[string]any{
"username": "Leeroy",
},
"source": "DemoAPI",
"destinations": map[string]any{
"cl_id": []any{
"21518",
"21500",
},
"phone": []any{
map[string]any{
"$": map[string]any{
"id": "external id1",
},
"_": "5xxxxxxxx",
},
map[string]any{
"$": map[string]any{
"id": "external id2",
},
"_": "5xxxxxxxx",
},
map[string]any{
"_": "5xxxxxxxx",
},
map[string]any{
"$": map[string]any{
"id": "",
},
"_": "5xxxxxxxx",
},
},
},
"message": "This is a sample tts message",
"tts": map[string]any{
"type": 4,
"rate": -1,
"repeat": 1,
"voice": "ymMale",
},
},
})
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 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 TextMeSendTts {
public static void main(String[] args) throws Exception {
String body = """
{
"sms": {
"user": {
"username": "Leeroy"
},
"source": "DemoAPI",
"destinations": {
"cl_id": [
"21518",
"21500"
],
"phone": [
{
"$": {
"id": "external id1"
},
"_": "5xxxxxxxx"
},
{
"$": {
"id": "external id2"
},
"_": "5xxxxxxxx"
},
{
"_": "5xxxxxxxx"
},
{
"$": {
"id": ""
},
"_": "5xxxxxxxx"
}
]
},
"message": "This is a sample tts message",
"tts": {
"type": 4,
"rate": -1,
"repeat": 1,
"voice": "ymMale"
}
}
}
""";
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());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"sms": {
"user": {
"username": "Leeroy"
},
"source": "DemoAPI",
"destinations": {
"cl_id": [
"21518",
"21500"
],
"phone": [
{
"$": {
"id": "external id1"
},
"_": "5xxxxxxxx"
},
{
"$": {
"id": "external id2"
},
"_": "5xxxxxxxx"
},
{
"_": "5xxxxxxxx"
},
{
"$": {
"id": ""
},
"_": "5xxxxxxxx"
}
]
},
"message": "This is a sample tts message",
"tts": {
"type": 4,
"rate": -1,
"repeat": 1,
"voice": "ymMale"
}
}
}
""";
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);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({
"sms" => {
"user" => {
"username" => "Leeroy",
},
"source" => "DemoAPI",
"destinations" => {
"cl_id" => [
"21518",
"21500",
],
"phone" => [
{
"$" => {
"id" => "external id1",
},
"_" => "5xxxxxxxx",
},
{
"$" => {
"id" => "external id2",
},
"_" => "5xxxxxxxx",
},
{
"_" => "5xxxxxxxx",
},
{
"$" => {
"id" => "",
},
"_" => "5xxxxxxxx",
},
],
},
"message" => "This is a sample tts message",
"tts" => {
"type" => 4,
"rate" => -1,
"repeat" => 1,
"voice" => "ymMale",
},
},
})
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// [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!({
"sms": {
"user": {
"username": "Leeroy"
},
"source": "DemoAPI",
"destinations": {
"cl_id": [
"21518",
"21500"
],
"phone": [
{
"$": {
"id": "external id1"
},
"_": "5xxxxxxxx"
},
{
"$": {
"id": "external id2"
},
"_": "5xxxxxxxx"
},
{
"_": "5xxxxxxxx"
},
{
"$": {
"id": ""
},
"_": "5xxxxxxxx"
}
]
},
"message": "This is a sample tts message",
"tts": {
"type": 4,
"rate": -1,
"repeat": 1,
"voice": "ymMale"
}
}
}))
.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 version="1.0" encoding="UTF-8"?>
<sms>
<status>0</status>
<message>SMS will be sent</message>
<shipment_id>xxxxxxx</shipment_id>
</sms>{
"status": 0,
"message": "SMS will be sent",
"shipment_id": "XXXXXXX"
}Identical to a text send: status 0, and a shipment_id naming the campaign.
Errors
Same set as sms. Voice-specific rejections surface through the ordinary validation codes — 2 for a missing tts.type, 989 for a body outside the length limit.
Field notes
The caller ID is not yours
However source is set, a TTS call arrives from a TextMe system number. Recipients cannot call back on it, and it will not match a number you have verified. Say who is calling in the first sentence of the message — the audio is all the identification the recipient gets.
Writing for the ear
The message is read aloud verbatim, so text that scans well on a screen often does not out loud:
- Spell out what matters.
ORD-10052is read as a string of characters; "order one zero zero five two" is clearer. - Drop URLs and
[link-…]placeholders — nobody can type a URL they heard, and the short-link machinery is meaningless in audio. - Keep it short.
repeatdefaults to twice, so a long message makes for a long call. - Use
ratesparingly. Negative values slow the delivery, which helps with numbers and unfamiliar names.
Cost and reporting
A voice call and an SMS are billed separately, so type 3 costs both. Balance reports the account's credit; delivery reports cover the SMS half of the send in the usual way.
Discovering which numbers need voice
You do not have to know in advance. Send normally, then read the delivery reports: destinations that come back 15 are filtered handsets. Store that fact against the contact and use type 4 for them next time — or just use type 2 for the whole audience and let the routing sort it out.

