Authentication
Every TextMe API call authenticates with an API token sent as a bearer token in the Authorization header. There is no session, no token exchange at call time, and no password anywhere in the request.
POST /api HTTP/1.1
Host: my.textme.co.il
Authorization: Bearer 0zrjDjZ26Dll5dqdTBmuwwtQg8
Content-Type: application/jsonMandatory security configuration
Two things must be in place before the API will work for you at all:
- TLS 1.2 or higher. Older protocol versions are refused.
- The authorised-IP service must be enabled. In the console, open My Account under the Settings tab and turn on Enable Authorized IP Address Check, then register at least one authorised IP address. An account with the check enabled and no addresses configured cannot call the API.
The two identities in a request
| What | Where | What it means |
|---|---|---|
| API token | Authorization: Bearer … header | Proves the caller is entitled to use the API. |
user.username | Request body | Names the account the call acts for. |
For a single account these describe the same customer, and the username in the body is simply your own. They separate when a reseller manages sub-accounts: the reseller's token authenticates, and user.username selects which sub-account the operation applies to.
A token is bound to a username. Presenting a valid token with a username it was not issued for fails with status 11.
Where tokens come from
Username and password are no longer accepted
Creating a token from a username and password is not supported. The first token for an account must be created in the web console; after that, the API can mint more.
Your first token — in the console.
- Open the Settings / הגדרות tab and choose API Token Management / ניהול Token API.
- Click Create New Token / יצירת טוקן חדש.
- The token is shown once. Store it somewhere safe — it cannot be retrieved afterwards.
Every token after that — through the API. With one valid token in hand you can create more by calling getApiToken with action set to new, or read back the most recent one with action set to current.
Token rules
| Rule | Detail |
|---|---|
| Concurrent tokens | Up to 5 active per user. They all work at the same time; creating one does not invalidate another. |
| Naming | Each token needs a unique name. Tokens minted through the API are named automatically from the creation date and time. |
| Expiry | Fixed per token, returned as expiration_date. Creating a new token does not extend or renew an existing one. |
| Revocation | Delete a token in the console and it stops working immediately. |
| Scope | Any valid token can be used for any API request the account is entitled to make. |
Because tokens overlap, rotation needs no downtime: mint the replacement, deploy it, then delete the old one.
Failure cases
Authentication failures come back with HTTP 200 and a non-zero status, like every other error.
| Status | Message | When |
|---|---|---|
3 | Username or password is incorrect and API token is invalid | The token is not recognised. |
10 | Username or password is incorrect and Expired API token | The token was valid but has passed its expiration_date. |
11 | API token is valid but doesn't match username or if you have newer token you should use it instead | The token belongs to a different username, or a newer token has superseded it. |
504 | current token not found | action: current was asked for an account that has no token yet. |
511 | you have not permission for this function | Authentication succeeded, but the account is not entitled to this operation. |
{
"status": 11,
"message": "API token is valid but doesn't match username or if you have newer token you should use it instead"
}Worked example
Read back the current token for an account. It touches nothing, so it is a safe way to prove that a token, a username and an IP allowlist are all in agreement:
<?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+ / 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({
getApiToken: {
user: {
username: 'admin_username',
},
username: 'username_for_token',
action: 'current',
},
}),
})
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' => [
'getApiToken' => [
'user' => [
'username' => 'admin_username',
],
'username' => 'username_for_token',
'action' => 'current',
],
],
]);
$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', [
'getApiToken' => [
'user' => [
'username' => 'admin_username',
],
'username' => 'username_for_token',
'action' => 'current',
],
])
->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={
"getApiToken": {
"user": {
"username": "admin_username",
},
"username": "username_for_token",
"action": "current",
},
},
)
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{
"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)
}
// 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 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());
// 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 = """
{
"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();
// 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({
"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)
# 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!({
"getApiToken": {
"user": {
"username": "admin_username"
},
"username": "username_for_token",
"action": "current"
}
}))
.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(())
}A healthy account answers with the token and its expiry:
<?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>{
"status": 0,
"message": "0zrjDjZ26Dll5dqdTBmuwwtQg8",
"expiration_date": "07/06/2025 09:17:34"
}See getApiToken for the full parameter list, including how to mint a replacement token.

