Skip to content

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.

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

Its 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:

NameTypeDescriptionRequired
ttsobjectContains all settings for sending calls. Its presence is what turns the send into a voice send.✔️
tts.typeintWhich channel each destination gets — see the table below.✔️
tts.rateintSpeech speed, from -10 to 10. 0 is normal.
tts.repeatintHow many times the message plays during the call. Default is twice.
tts.voicestringymMale or ymFemale. Default is ymMale.

And the fields it shares with a text send:

NameTypeDescriptionRequired
user.usernamestringThe username of the account by which you are recognized in the system.✔️
sourcestringSender for the SMS half of the send. Maximum 11 characters. Calls always originate from a system numbersource does not apply to them.✔️
destinationsobjectContains every destination. May hold multiple phone and cl_id elements.✔️
phonestringA destination number, formatted 5xxxxxxx or 05xxxxxxx.✔️
messagestringThe text that will be spoken, and texted where type calls for it. Maximum 1005 characters.✔️

tts.type

ValueBehaviour
2Send a regular message to a regular phone, and a voice call to a Kosher phone.
3Send both a regular message and a voice call to all numbers.
4Send 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
<?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>
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"
    }
  }
}
bash
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"
    }
  }
}'
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({
    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
<?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
<?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);
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={
        "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)
go
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
// 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());
    }
}
csharp
// .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);
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({
  "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
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!({
          "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
<?xml version="1.0" encoding="UTF-8"?>
<sms>
    <status>0</status>
    <message>SMS will be sent</message>
    <shipment_id>xxxxxxx</shipment_id>
</sms>
json
{
  "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-10052 is 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. repeat defaults to twice, so a long message makes for a long call.
  • Use rate sparingly. 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.