Skip to content

Campaigns ​

Every send creates a campaign. A scheduled one can be called off before it goes out, either by id or by name. Birthday campaigns (recurring sends triggered by a contact's date of birth) are listed and edited here too.

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

cancel Cancel a campaign by id ​

Cancels one scheduled campaign.

Parameters ​

NameTypeDescriptionRequired
cancelobjectContains all other elements.✔️
userobjectContains the user element.✔️
usernamestringThe username of the account by which you are recognized in the system.✔️
campaign_idintThe number of your campaign.✔️

Request example ​

xml
<?xml version="1.0" encoding="UTF-8"?>
<cancel>
    <user>
        <username>Leeroy</username>
    </user>
    <campaign_id>1234</campaign_id>
</cancel>
json
{
  "cancel": {
    "user": {
      "username": "Leeroy"
    },
    "campaign_id": "1234"
  }
}
bash
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "cancel": {
    "user": {
      "username": "Leeroy"
    },
    "campaign_id": "1234"
  }
}'
js
// Node.js 18+ or any modern browser. 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({
    cancel: {
      user: {
        username: 'Leeroy',
      },
      campaign_id: '1234',
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too, so 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' => [
        'cancel' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'campaign_id' => '1234',
        ],
    ],
]);

$result = json_decode($response->getBody()->getContents(), true);

// Errors arrive as HTTP 200 too, so 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', [
        'cancel' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'campaign_id' => '1234',
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too, so 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={
        "cancel": {
            "user": {
                "username": "Leeroy",
            },
            "campaign_id": "1234",
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too, so 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{
		"cancel": map[string]any{
			"user": map[string]any{
				"username": "Leeroy",
			},
			"campaign_id": "1234",
		},
	})

	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, so 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+ using 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 TextMeCampaignCancelById {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "cancel": {
                "user": {
                  "username": "Leeroy"
                },
                "campaign_id": "1234"
              }
            }
            """;

        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, so the payload status is what counts
        System.out.println(response.body());
    }
}
csharp
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "cancel": {
        "user": {
          "username": "Leeroy"
        },
        "campaign_id": "1234"
      }
    }
    """;

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, so 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({
  "cancel" => {
    "user" => {
      "username" => "Leeroy",
    },
    "campaign_id" => "1234",
  },
})

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, so 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!({
          "cancel": {
            "user": {
              "username": "Leeroy"
            },
            "campaign_id": "1234"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // Errors arrive as HTTP 200 too, so 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"?>
<cancel>
    <status>0</status>
    <message>Campaign successfuly cancel</message>
</cancel>
json
{
  "status": 0,
  "message": "Campaign successfuly cancel"
}

Errors ​

StatusWhen
955Campaign already cancel. Already cancelled. Effectively a no-op; usually safe to ignore.
966Campaign already sent, too late.
970Campaigns was not cancelled. Contact support.
977Campaign does not belong to customer or Not exist.

cancel Cancel campaigns by name ​

Cancels every scheduled campaign whose campaign_name matches. The response reports how many were affected.

Parameters ​

NameTypeDescriptionRequired
cancelobjectContains all other elements.✔️
userobjectContains the user element.✔️
usernamestringThe username of the account by which you are recognized in the system.✔️
campaign_namestringThe name of your campaign.✔️

Request example ​

xml
<?xml version="1.0" encoding="UTF-8"?>
<cancel>
    <user>
        <username>Leeroy</username>
    </user>
    <campaign_name>My Campaign</campaign_name>
</cancel>
json
{
  "cancel": {
    "user": {
      "username": "Leeroy"
    },
    "campaign_name": "My Campaign"
  }
}
bash
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "cancel": {
    "user": {
      "username": "Leeroy"
    },
    "campaign_name": "My Campaign"
  }
}'
js
// Node.js 18+ or any modern browser. 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({
    cancel: {
      user: {
        username: 'Leeroy',
      },
      campaign_name: 'My Campaign',
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too, so 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' => [
        'cancel' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'campaign_name' => 'My Campaign',
        ],
    ],
]);

$result = json_decode($response->getBody()->getContents(), true);

// Errors arrive as HTTP 200 too, so 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', [
        'cancel' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'campaign_name' => 'My Campaign',
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too, so 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={
        "cancel": {
            "user": {
                "username": "Leeroy",
            },
            "campaign_name": "My Campaign",
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too, so 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{
		"cancel": map[string]any{
			"user": map[string]any{
				"username": "Leeroy",
			},
			"campaign_name": "My Campaign",
		},
	})

	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, so 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+ using 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 TextMeCampaignCancelByName {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "cancel": {
                "user": {
                  "username": "Leeroy"
                },
                "campaign_name": "My Campaign"
              }
            }
            """;

        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, so the payload status is what counts
        System.out.println(response.body());
    }
}
csharp
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "cancel": {
        "user": {
          "username": "Leeroy"
        },
        "campaign_name": "My Campaign"
      }
    }
    """;

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, so 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({
  "cancel" => {
    "user" => {
      "username" => "Leeroy",
    },
    "campaign_name" => "My Campaign",
  },
})

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, so 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!({
          "cancel": {
            "user": {
              "username": "Leeroy"
            },
            "campaign_name": "My Campaign"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // Errors arrive as HTTP 200 too, so 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"?>
<cancel>
    <status>0</status>
    <message>Campaigns successfully cancel</message>
    <count>1</count>
</cancel>
json
{
  "status": 0,
  "message": "Campaigns successfully cancel",
  "count": 1
}
FieldTypeDescription
statusint0 on success.
messagestringCampaigns successfully cancel.
countintHow many campaigns were cancelled.

Name matching is the whole selector

There is no confirmation step and no dry run. Every pending campaign carrying that name is cancelled, which is exactly why splitting a large run across several bulk calls with a shared campaign_name is a good idea: one request stops all of it.

The corollary is that a generic name such as newsletter will one day cancel more than you meant. Make names unique per run. newsletter-2026-08-17 rather than newsletter.

Errors ​

Same as cancelling by id, with count 0 and status 0 when nothing matched.

get_birthday_campaigns List birthday campaigns ​

Parameters ​

NameTypeDescriptionRequired
get_birthday_campaignsobjectContains all other elements.✔️
userobjectContains the user element.✔️
usernamestringThe username of the account by which you are recognized in the system.✔️

Request example ​

xml
<?xml version="1.0" encoding="UTF-8"?>
<get_birthday_campaigns>
    <user>
        <username>XXXXXX</username>
    </user>
</get_birthday_campaigns>
json
{
  "get_birthday_campaigns": {
    "user": {
      "username": "XXXXXX"
    }
  }
}
bash
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "get_birthday_campaigns": {
    "user": {
      "username": "XXXXXX"
    }
  }
}'
js
// Node.js 18+ or any modern browser. 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({
    get_birthday_campaigns: {
      user: {
        username: 'XXXXXX',
      },
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too, so 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' => [
        'get_birthday_campaigns' => [
            'user' => [
                'username' => 'XXXXXX',
            ],
        ],
    ],
]);

$result = json_decode($response->getBody()->getContents(), true);

// Errors arrive as HTTP 200 too, so 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', [
        'get_birthday_campaigns' => [
            'user' => [
                'username' => 'XXXXXX',
            ],
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too, so 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={
        "get_birthday_campaigns": {
            "user": {
                "username": "XXXXXX",
            },
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too, so 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{
		"get_birthday_campaigns": map[string]any{
			"user": map[string]any{
				"username": "XXXXXX",
			},
		},
	})

	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, so 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+ using 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 TextMeBirthdayList {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "get_birthday_campaigns": {
                "user": {
                  "username": "XXXXXX"
                }
              }
            }
            """;

        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, so the payload status is what counts
        System.out.println(response.body());
    }
}
csharp
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "get_birthday_campaigns": {
        "user": {
          "username": "XXXXXX"
        }
      }
    }
    """;

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, so 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({
  "get_birthday_campaigns" => {
    "user" => {
      "username" => "XXXXXX",
    },
  },
})

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, so 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!({
          "get_birthday_campaigns": {
            "user": {
              "username": "XXXXXX"
            }
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // Errors arrive as HTTP 200 too, so 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></message>
    <birthday_campaigns>
        <birthday_campaign>
            <CAMPAIGN_ID>XXXX</CAMPAIGN_ID>
            <CAMPAIGN_NAME>XXXX</CAMPAIGN_NAME>
            <CREATED_ON>18/10/23 14:40:39</CREATED_ON>
            <ACTIVE_DESTINATIONS>X</ACTIVE_DESTINATIONS>
        </birthday_campaign>
    </birthday_campaigns>
</sms>
json
{
  "status": 0,
  "message": "",
  "birthday_campaigns": {
    "birthday_campaign": [
      {
        "CAMPAIGN_ID": "xxxxx",
        "CAMPAIGN_NAME": "xxxxx",
        "CREATED_ON": "18/10/23 14:40:39",
        "ACTIVE_DESTINATIONS": ""
      }
    ]
  }
}
FieldTypeDescription
birthday_campaigns.birthday_campaignarrayOne entry per campaign.
CAMPAIGN_IDstringThe campaign's id. What edit_birthday_campaign needs.
CAMPAIGN_NAMEstringIts name.
CREATED_ONstringWhen it was created, dd/mm/yy hh:mm:ss.
ACTIVE_DESTINATIONSstringHow many contacts it currently targets.

Field names are upper case here

Unlike the rest of the API, birthday campaign fields come back as CAMPAIGN_ID, CAMPAIGN_NAME and so on. Case-sensitive parsers need to expect it.

Errors ​

StatusWhen
3, 10, 11Token invalid, expired, or belonging to another username.
511The account is not entitled to this operation.

edit_birthday_campaign Change the message ​

Replaces the message body of an existing birthday campaign. The campaign's schedule, audience and name are untouched.

Parameters ​

NameTypeDescriptionRequired
edit_birthday_campaignobjectContains all other elements.✔️
userobjectContains the user element.✔️
usernamestringThe username of the account by which you are recognized in the system.✔️
campaign_idobjectThe campaign_id you want to edit.✔️
messageobjectThe content of the new message.✔️

Request example ​

xml
<?xml version="1.0" encoding="UTF-8"?>
<edit_birthday_campaign>
    <user>
        <username>XXXX</username>
    </user>
    <campaign_id>XXXX</campaign_id>
    <message>XXXX</message>
</edit_birthday_campaign>
json
{
  "edit_birthday_campaign": {
    "user": {
      "username": "XXXX"
    },
    "campaign_id": "XXXX",
    "message": "XXXX"
  }
}
bash
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "edit_birthday_campaign": {
    "user": {
      "username": "XXXX"
    },
    "campaign_id": "XXXX",
    "message": "XXXX"
  }
}'
js
// Node.js 18+ or any modern browser. 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({
    edit_birthday_campaign: {
      user: {
        username: 'XXXX',
      },
      campaign_id: 'XXXX',
      message: 'XXXX',
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too, so 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' => [
        'edit_birthday_campaign' => [
            'user' => [
                'username' => 'XXXX',
            ],
            'campaign_id' => 'XXXX',
            'message' => 'XXXX',
        ],
    ],
]);

$result = json_decode($response->getBody()->getContents(), true);

// Errors arrive as HTTP 200 too, so 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', [
        'edit_birthday_campaign' => [
            'user' => [
                'username' => 'XXXX',
            ],
            'campaign_id' => 'XXXX',
            'message' => 'XXXX',
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too, so 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={
        "edit_birthday_campaign": {
            "user": {
                "username": "XXXX",
            },
            "campaign_id": "XXXX",
            "message": "XXXX",
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too, so 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{
		"edit_birthday_campaign": map[string]any{
			"user": map[string]any{
				"username": "XXXX",
			},
			"campaign_id": "XXXX",
			"message": "XXXX",
		},
	})

	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, so 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+ using 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 TextMeBirthdayEdit {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "edit_birthday_campaign": {
                "user": {
                  "username": "XXXX"
                },
                "campaign_id": "XXXX",
                "message": "XXXX"
              }
            }
            """;

        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, so the payload status is what counts
        System.out.println(response.body());
    }
}
csharp
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "edit_birthday_campaign": {
        "user": {
          "username": "XXXX"
        },
        "campaign_id": "XXXX",
        "message": "XXXX"
      }
    }
    """;

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, so 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({
  "edit_birthday_campaign" => {
    "user" => {
      "username" => "XXXX",
    },
    "campaign_id" => "XXXX",
    "message" => "XXXX",
  },
})

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, so 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!({
          "edit_birthday_campaign": {
            "user": {
              "username": "XXXX"
            },
            "campaign_id": "XXXX",
            "message": "XXXX"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // Errors arrive as HTTP 200 too, so 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>birthday campaign successfully updated</message>
</sms>
json
{
  "status": 0,
  "message": "birthday campaign successfully updated"
}

Errors ​

StatusWhen
977The campaign does not exist, or belongs to another account.
989The new message is empty or over 1005 characters.
511The account is not entitled to this operation.

Field notes ​

What can and cannot be cancelled ​

Cancellation only reaches campaigns that have not gone out. A send with no timing is dispatched immediately and is past cancelling by the time you could call. 966. Scheduling with timing is what buys you the window.

Which handle should you keep? ​

A send returns shipment_id; cancellation takes campaign_id. In practice they refer to the same campaign, and the more robust habit is to set a unique campaign_name on every send and cancel by name. It works across a run split into several calls, and it does not depend on having stored an id.

Birthday campaigns are created in the console ​

The API lists them and rewrites their message. Creating one, setting its schedule, or choosing its audience is a console operation.