Skip to content

שליחת ה-SMS הראשון

מאפס עד הודעה שנמסרה, בחמישה צעדים. הקציבו כחמישה עשר דקות — רובן שני הדברים שדורשים אדם: הרשאת כתובת ה-IP ואימות שולח.

לפני שמתחילים

אתם צריכים שלושה דברים, ושניים מהם אינם ניתנים ליצירה דרך ה-API.

מהמאיפה זה בא
1שם משתמש בחשבון TextMeממנהל הלקוח שלכם.
2כתובת IP מורשיתממשק הניהול — הגדרות → החשבון שלי → בדיקת כתובת IP מורשית. חובה: ה-API דוחה קריאות מחשבון שהבדיקה מופעלת בו ולא רשומה בו אף כתובת.
3טוקן APIממשק הניהול, בפעם הראשונה — הגדרות → ניהול Token API → יצירת טוקן חדש. מוצג פעם אחת, אז שמרו אותו.

שמרו את הטוקן במשתני סביבה ולא בקוד. כל דוגמה באתר הזה קוראת את TEXTME_API_TOKEN:

bash
export TEXTME_API_TOKEN='0zrjDjZ26Dll5dqdTBmuwwtQg8'

נדרש TLS בגרסה 1.2 ומעלה

גרסאות TLS ישנות נדחות מיד. רוב סביבות הריצה העדכניות מתדברות על 1.2 ומעלה כברירת מחדל; התקנת PHP או Java ותיקה מאוד עשויה לא לעשות זאת.

צעד 1 — לוודא שפרטי הגישה עובדים

לפני שליחה של משהו, ודאו שהטוקן, שם המשתמש ורשימת ה-IP המורשים מסתדרים ביניהם. קריאת הטוקן הנוכחי של החשבון אינה נוגעת בדבר ונכשלת בקול רם אם אחד מהשלושה שגוי:

xml
<?xml version="1.0" encoding="UTF-8"?>
<getApiToken>
    <user>
        <username>admin_username</username>
    </user>
    <username>username_for_token</username>
    <action>current</action>
</getApiToken>
json
{
  "getApiToken": {
    "user": {
      "username": "admin_username"
    },
    "username": "username_for_token",
    "action": "current"
  }
}
bash
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"
  }
}'
js
// 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
<?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
<?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);
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={
        "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)
go
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
// 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());
    }
}
csharp
// .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);
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({
  "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
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!({
          "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(())
}

חשבון תקין עונה עם הטוקן ומועד התפוגה שלו:

xml
<?xml version="1.0" encoding="utf-8"?>
<getApiToken>
    <status>0</status>
    <message>0zrjDjZ26Dll5dqdTBmuwwtQg8</message>
    <expiration_date>07/06/2025 09:17:34</expiration_date>
</getApiToken>
json
{
  "status": 0,
  "message": "0zrjDjZ26Dll5dqdTBmuwwtQg8",
  "expiration_date": "07/06/2025 09:17:34"
}

ואם בכל זאת קיבלתם:

Statusמה זה אומר
3הטוקן אינו מזוהה — בדקו שלא נחתך בהעתקה.
10תוקף הטוקן פג. צרו חדש בממשק הניהול.
11הטוקן שייך ל-username אחר.
504לחשבון לא היה טוקן אף פעם — צרו את הראשון בממשק הניהול.
שום דבר, החיבור נדחהכתובת ה-IP הקוראת אינה ברשימה המורשית, או שהתדברות ה-TLS נכשלה.

צעד 2 — לבדוק שיש לכם שולח מאומת

ה-source של ההודעה — השם שהנמענים רואים כשולח — חייב להיות מאומת קודם. שאלו אילו כבר מאומתים:

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+ / דפדפנים — ללא תלויות
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()

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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);

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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();

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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()

# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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)
	}

	// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
	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, ללא תלויות (פענוח עם 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());

        // גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
        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();

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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)

# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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?;

    // גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

    println!("{result}");
    Ok(())
}
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"
    }
  ]
}

אם הרשימה ריקה, שלחו מספר עם verify_phone. זה שולח קישור אימות ב-SMS למספר נייד, או מבקש מכם להתקשר למספר נתון עבור קו נייח — שלב שדורש אדם, ולכן התחילו אותו עכשיו ולא בזמן הפריסה.

שמות שולח

source יכול להיות גם שם קצר באנגלית כמו DemoAPI — עד 11 תווים, אותיות וספרות בלבד. אלה מוסדרים ישירות עם TextMe ולא דרך verify_phone, כי אין קו שאפשר לשלוח אליו קישור.

צעד 3 — לאמת את הבקשה בלי לשלוח

הפנו את אותה קריאה אל https://my.textme.co.il/api/test והיא תפורסר, תיבדק מבחינת הרשאות ותיענה בדיוק כמו נקודת הקצה האמיתית — אבל שום דבר לא יישלח ולא ייגבה.

זו הדרך הזולה ביותר לעבוד על גוף בקשה. כשנקודת הקצה לבדיקות מחזירה status: 0, המבנה נכון.

מה נקודת הקצה לבדיקות לא אומרת לכם

היא מאשרת שהבקשה תקינה ומותרת. היא לא מאשרת שהמספר ניתן להשגה, והיא לא יוצרת shipment_id שאפשר להפיק עליו דוחות בהמשך.

צעד 4 — לשלוח

החליפו את הכתובת בחזרה ל-/api ושלחו באמת:

xml
<?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>
json
{
  "sms": {
    "user": {
      "username": "Leeroy"
    },
    "source": "DemoAPI",
    "destinations": {
      "phone": "5xxxxxxxx"
    },
    "message": "This is a sample message"
  }
}
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": {
      "phone": "5xxxxxxxx"
    },
    "message": "This is a sample message"
  }
}'
js
// 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
<?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
<?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);
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": {
                "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)
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{
				"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
// 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());
    }
}
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": {
          "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);
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" => {
      "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
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": {
              "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(())
}
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"
}

status: 0 אומר ש-TextMe קיבלה את ההודעה. ה-shipment_id מזהה את הקמפיין שנוצר.

"נתקבל" אינו "נמסר" — ואין ניסיונות חוזרים בטוחים

0 אומר שההודעה נכנסה לתור, לא שמכשיר קיבל אותה. ואם הקריאה נכשלת בטיים-אאוט בשכבת התעבורה, אינכם יודעים אם היא יצאה: ניסיון חוזר שולח הודעה שנייה, ושתיהן יימסרו וייגבו. אין מנגנון idempotency key.

הפתרון הוא צעד 5: הגדירו id על כל יעד ושאלו מה קרה במקום לשלוח שוב.

צעד 5 — לגלות מה קרה

הוסיפו תכונת id לכל יעד ותוכלו לשלוף את ההודעה המדויקת הזו בהמשך. השתמשו במשהו מהתחום שלכם — מספר הזמנה, מזהה משתמש:

xml
<destinations>
    <phone id="order-10052">5xxxxxxxx</phone>
</destinations>
json
{
  "destinations": {
    "phone": { "$": { "id": "order-10052" }, "_": "5xxxxxxxx" }
  }
}

ואז בקשו את דוח המסירה שלה. הדוחות אסינכרוניים — המפעיל עונה בשניות או דקות, ולפעמים יותר — ולכן תשאלו על חלון זמן ולא מיד:

xml
<?xml version="1.0" encoding="UTF-8"?>
<dlr>
    <user>
        <username>Leeroy</username>
    </user>
    <transactions>
        <external_id>some id 1</external_id>
        <external_id>some id 2</external_id>
    </transactions>
    <from>01/01/14 00:00</from>
    <to>01/01/14 23:59</to>
</dlr>
json
{
  "dlr": {
    "user": {
      "username": "Leeroy"
    },
    "transactions": {
      "external_id": [
        "some id 1",
        "some id 2"
      ]
    },
    "from": "01/01/14 00:00",
    "to": "01/01/14 23:59"
  }
}
bash
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "dlr": {
    "user": {
      "username": "Leeroy"
    },
    "transactions": {
      "external_id": [
        "some id 1",
        "some id 2"
      ]
    },
    "from": "01/01/14 00:00",
    "to": "01/01/14 23:59"
  }
}'
js
// 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({
    dlr: {
      user: {
        username: 'Leeroy',
      },
      transactions: {
        external_id: [
          'some id 1',
          'some id 2',
        ],
      },
      from: '01/01/14 00:00',
      to: '01/01/14 23:59',
    },
  }),
})

const result = await response.json()

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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' => [
        'dlr' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'transactions' => [
                'external_id' => [
                    'some id 1',
                    'some id 2',
                ],
            ],
            'from' => '01/01/14 00:00',
            'to' => '01/01/14 23:59',
        ],
    ],
]);

$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
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'dlr' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'transactions' => [
                'external_id' => [
                    'some id 1',
                    'some id 2',
                ],
            ],
            'from' => '01/01/14 00:00',
            'to' => '01/01/14 23:59',
        ],
    ])
    ->throw()
    ->json();

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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={
        "dlr": {
            "user": {
                "username": "Leeroy",
            },
            "transactions": {
                "external_id": [
                    "some id 1",
                    "some id 2",
                ],
            },
            "from": "01/01/14 00:00",
            "to": "01/01/14 23:59",
        },
    },
)
response.raise_for_status()
result = response.json()

# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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{
		"dlr": map[string]any{
			"user": map[string]any{
				"username": "Leeroy",
			},
			"transactions": map[string]any{
				"external_id": []any{
					"some id 1",
					"some id 2",
				},
			},
			"from": "01/01/14 00:00",
			"to": "01/01/14 23:59",
		},
	})

	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
// 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 TextMeDlr {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "dlr": {
                "user": {
                  "username": "Leeroy"
                },
                "transactions": {
                  "external_id": [
                    "some id 1",
                    "some id 2"
                  ]
                },
                "from": "01/01/14 00:00",
                "to": "01/01/14 23:59"
              }
            }
            """;

        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());
    }
}
csharp
// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "dlr": {
        "user": {
          "username": "Leeroy"
        },
        "transactions": {
          "external_id": [
            "some id 1",
            "some id 2"
          ]
        },
        "from": "01/01/14 00:00",
        "to": "01/01/14 23:59"
      }
    }
    """;

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);
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({
  "dlr" => {
    "user" => {
      "username" => "Leeroy",
    },
    "transactions" => {
      "external_id" => [
        "some id 1",
        "some id 2",
      ],
    },
    "from" => "01/01/14 00:00",
    "to" => "01/01/14 23:59",
  },
})

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
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!({
          "dlr": {
            "user": {
              "username": "Leeroy"
            },
            "transactions": {
              "external_id": [
                "some id 1",
                "some id 2"
              ]
            },
            "from": "01/01/14 00:00",
            "to": "01/01/14 23:59"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

    println!("{result}");
    Ok(())
}
xml
<?xml version="1.0" encoding="UTF-8"?>
<dlr>
    <status>0</status>
    <message></message>
    <transactions>
        <transaction>
            <external_id>1391438285</external_id>
            <status>102</status>
            <he_message>הגיע ליעד</he_message>
            <en_message>Delivered</en_message>
            <date>03/02/14 16:38</date>
            <shipment_id>12345678</shipment_id>
        </transaction>
    </transactions>
</dlr>
json
{
  "status": 0,
  "message": "all is well!",
  "transactions": [
    {
      "external_id": "1391438285",
      "source": "Test",
      "phone": "5XXXXXXXX",
      "status": "102",
      "message_he": "הגיע ליעד",
      "en_message": "Delivered",
      "shipment_id": "XXXXXXX",
      "date": "02/05/23 10:22",
      "operator": "Telzar"
    }
  ]
}

כל רשומה נושאת status מאוצר המילים של סטטוסי מסירה, שהוא סולם שונה מ-status הבקשה שמעליו. גם 102 וגם 0 אומרים "נמסר".

להפסיק לתשאל

כשזה עובד, רשמו כתובת Push ו-TextMe תשלח לכם כל דוח ב-POST ברגע שהוא נוצר, במקום שאתם תשאלו. זה המבנה שרוב האינטגרציות בייצור מגיעות אליו — ראו מעקב אחר מסירה.

הרצף המלא

ממשק הניהול  →  הרשאת IP, יצירת הטוקן הראשון
getApiToken     →  פרטי הגישה אומתו                  (action: current)
getVerifiedPhones → יש שולח זמין
/api/test       →  הבקשה נתקבלה, שום דבר לא נשלח
/api  sms       →  status 0, shipment_id              (id מוגדר לכל יעד)
dlr             →  status 102, en_message "Delivered"

מה יכול להשתבש

Statusמשמעותפתרון
2חסר שדה חובהה-message מציין איזה.
4אין מספיק יתרהבדקו עם balance.
5מחוץ לשעות השליחה המותרותנסו שוב בתוך החלון של החשבון.
8כל היעדים ברשימת החסימהזה לא באג — הקהל ביקש להסירו. ראו הסרה ורגולציה.
9יעד פגוםפרמטו מספרים כ-5xxxxxxxx או 05xxxxxxx.
515השולח אינו מאומתצעד 2.
989ההודעה ריקה או עוברת 1005 תוויםקצרו אותה.

הרשימה המלאה נמצאת בקודי סטטוס.

הצעד הבא