מבוא
ה-API של TextMe שולח הודעות SMS והודעות קוליות, מנהל את רשימות התפוצה ורשימות החסימה שמאחוריהן, מתזמן ומבטל קמפיינים, ומדווח על מה שנמסר. זה API קטן עם מבנה לא שגרתי: כתובת אחת, ואלמנט השורש של גוף הבקשה הוא שבוחר את הפעולה.
כתובת הבסיס
POST https://my.textme.co.il/apiאין נתיב נפרד לכל פעולה. POST /api עם שורש <sms> שולח הודעה; אותה כתובת בדיוק עם שורש <dlr> שולפת דוחות מסירה. תיעוד נקודות הקצה מסודר לפי אלמנט השורש הזה.
כתובת שנייה מאמתת בקשות בלי לבצע אותן:
POST https://my.textme.co.il/api/testהיא עונה בדיוק כמו נקודת הקצה האמיתית אבל לא מבצעת דבר — שום הודעה לא נשלחת, לא נגרעים קרדיטים, ואף רשימה לא משתנה. כדאי להשתמש בה בזמן שאתם עדיין מגבשים את גוף הבקשה.
בנוסף קיים ממשק SOAP המכסה ארבע פעולות בכתובת https://my.textme.co.il/soap?wsdl, וממשק Push שדוחף דוחות לכתובת שבשליטתכם.
מבנה של קריאה
כל בקשה נושאת שלושה דברים: טוקן bearer בכותרת, user.username בגוף הבקשה, ואלמנט שורש שמציין את הפעולה.
POST /api HTTP/1.1
Host: my.textme.co.il
Authorization: Bearer {api_token}
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<sms> <!-- הפעולה -->
<user>
<username>Leeroy</username> <!-- החשבון שבשמו פועלים -->
</user>
<source>DemoAPI</source>
<destinations>
<phone>5xxxxxxxx</phone>
</destinations>
<message>Hello</message>
</sms>POST /api HTTP/1.1
Host: my.textme.co.il
Authorization: Bearer {api_token}
Content-Type: application/json
{
"sms": {
"user": { "username": "Leeroy" },
"source": "DemoAPI",
"destinations": { "phone": "5xxxxxxxx" },
"message": "Hello"
}
}הטוקן מוכיח מי קורא; ה-username קובע עבור איזה חשבון הקריאה מתבצעת. בדרך כלל מדובר באותו חשבון — הם נפרדים כאשר משווק פועל בשם חשבון משנה.
מוסכמות
| מוסכמה | הכלל |
|---|---|
| קידוד | UTF-8 בכל מקום. גוף הודעה בעברית ותשובות בעברית הם דבר שגרתי. |
| מספרי טלפון | 5xxxxxxxx או 05xxxxxxx. גם מספרים נייחים נכתבים באותו אופן (3xxxxxxx). |
| תאריכים | dd/mm/yy hh:mm — שנה בשתי ספרות, שעון 24 שעות. בחלק משדות הדוחות מצורפות גם שניות. |
שולח (source) | עד 11 תווים, אותיות באנגלית וספרות בלבד, בלי +. חייב להיות שולח מאומת. |
| אורך הודעה | עד 1005 תווים. |
| ערכים בוליאניים | נשלחים כמחרוזות 1 / 0, לא true / false. |
| הצלחה | status בערך 0. כל ערך אחר הוא כשל — ראו קודי סטטוס. |
סטטוס ה-HTTP הוא לא הסטטוס של ה-API
בקשה שנדחתה חוזרת גם היא כ-HTTP 200. השדה status שבגוף התשובה הוא זה שמספר מה קרה, ולכן כל דוגמה באתר הזה בודקת אותו לפני כל דבר אחר.
אימות בשורה אחת
שלחו Authorization: Bearer {api_token} בכל בקשה. טוקנים נוצרים בממשק הניהול של TextMe, ולאחר מכן אפשר להחליף אותם דרך ה-API; עד חמישה תקפים במקביל. הפירוט המלא נמצא באימות.
תחילה מהירה
1. ודאו שהטוקן עובד. בקשו את הטוקן הנוכחי של החשבון — קריאה זולה שנכשלת בקול רם אם משהו בפרטי הגישה שגוי.
<?xml version="1.0" encoding="UTF-8"?>
<getApiToken>
<user>
<username>admin_username</username>
</user>
<username>username_for_token</username>
<action>current</action>
</getApiToken>{
"getApiToken": {
"user": {
"username": "admin_username"
},
"username": "username_for_token",
"action": "current"
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"getApiToken": {
"user": {
"username": "admin_username"
},
"username": "username_for_token",
"action": "current"
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
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({
getApiToken: {
user: {
username: 'admin_username',
},
username: 'username_for_token',
action: 'current',
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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' => [
'getApiToken' => [
'user' => [
'username' => 'admin_username',
],
'username' => 'username_for_token',
'action' => 'current',
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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', [
'getApiToken' => [
'user' => [
'username' => 'admin_username',
],
'username' => 'username_for_token',
'action' => 'current',
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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={
"getApiToken": {
"user": {
"username": "admin_username",
},
"username": "username_for_token",
"action": "current",
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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{
"getApiToken": map[string]any{
"user": map[string]any{
"username": "admin_username",
},
"username": "username_for_token",
"action": "current",
},
})
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)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeTokenCurrent {
public static void main(String[] args) throws Exception {
String body = """
{
"getApiToken": {
"user": {
"username": "admin_username"
},
"username": "username_for_token",
"action": "current"
}
}
""";
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());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
System.out.println(response.body());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"getApiToken": {
"user": {
"username": "admin_username"
},
"username": "username_for_token",
"action": "current"
}
}
""";
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();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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({
"getApiToken" => {
"user" => {
"username" => "admin_username",
},
"username" => "username_for_token",
"action" => "current",
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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!({
"getApiToken": {
"user": {
"username": "admin_username"
},
"username": "username_for_token",
"action": "current"
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}2. שלחו הודעה.
<?xml version="1.0" encoding="UTF-8"?>
<sms>
<user>
<username>Leeroy</username>
</user>
<source>DemoAPI</source>
<destinations>
<phone>5xxxxxxxx</phone>
</destinations>
<message>This is a sample message</message>
</sms>{
"sms": {
"user": {
"username": "Leeroy"
},
"source": "DemoAPI",
"destinations": {
"phone": "5xxxxxxxx"
},
"message": "This is a sample message"
}
}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": {
"phone": "5xxxxxxxx"
},
"message": "This is a sample message"
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
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: {
phone: '5xxxxxxxx',
},
message: 'This is a sample message',
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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' => [
'phone' => '5xxxxxxxx',
],
'message' => 'This is a sample message',
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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' => [
'phone' => '5xxxxxxxx',
],
'message' => 'This is a sample message',
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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": {
"phone": "5xxxxxxxx",
},
"message": "This is a sample message",
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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{
"phone": "5xxxxxxxx",
},
"message": "This is a sample message",
},
})
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)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeSendMinimal {
public static void main(String[] args) throws Exception {
String body = """
{
"sms": {
"user": {
"username": "Leeroy"
},
"source": "DemoAPI",
"destinations": {
"phone": "5xxxxxxxx"
},
"message": "This is a sample message"
}
}
""";
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());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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": {
"phone": "5xxxxxxxx"
},
"message": "This is a sample message"
}
}
""";
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();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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" => {
"phone" => "5xxxxxxxx",
},
"message" => "This is a sample message",
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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": {
"phone": "5xxxxxxxx"
},
"message": "This is a sample message"
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}status בערך 0 אומר שההודעה נתקבלה, ו-shipment_id מציין את הקמפיין שנוצר:
<?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"
}איך מתמצאים כאן
- תרחישי שימוש — מדריכים מקצה לקצה: ההודעה הראשונה, שליחה מרובה עם פרסונליזציה, התאמת נתוני מסירה, וטיפול בהסרות מרשימת תפוצה.
- נקודות קצה — עמוד לכל משפחת פעולות, עם כל הפרמטרים ושני פורמטי הבקשה.
- נתוני עזר — טבלות קודי הסטטוס, אוצר המילים של סטטוסי המסירה, וחוזה ה-SOAP.

