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+ / 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({
    cancel: {
      user: {
        username: 'Leeroy',
      },
      campaign_id: '1234',
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too — the payload status is what counts
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'headers' => [
        'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('https://my.textme.co.il/api', [
    'json' => [
        'cancel' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'campaign_id' => '1234',
        ],
    ],
]);

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

// Errors arrive as HTTP 200 too — the payload status is what counts
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
php
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'cancel' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'campaign_id' => '1234',
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too — the payload status is what counts
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
python
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://my.textme.co.il/api",
    headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
    json={
        "cancel": {
            "user": {
                "username": "Leeroy",
            },
            "campaign_id": "1234",
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too — the payload status is what counts
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"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 — the payload status is what counts
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
java
// Java 17+ — java.net.http, no dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

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

var payload = """
    {
      "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 — 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 — 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 — 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+ / 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({
    cancel: {
      user: {
        username: 'Leeroy',
      },
      campaign_name: 'My Campaign',
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too — the payload status is what counts
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'headers' => [
        'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('https://my.textme.co.il/api', [
    'json' => [
        'cancel' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'campaign_name' => 'My Campaign',
        ],
    ],
]);

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

// Errors arrive as HTTP 200 too — the payload status is what counts
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
php
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'cancel' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'campaign_name' => 'My Campaign',
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too — the payload status is what counts
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
python
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://my.textme.co.il/api",
    headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
    json={
        "cancel": {
            "user": {
                "username": "Leeroy",
            },
            "campaign_name": "My Campaign",
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too — the payload status is what counts
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"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 — the payload status is what counts
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
java
// Java 17+ — java.net.http, no dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

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

var payload = """
    {
      "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 — 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 — 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 — 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+ / 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({
    get_birthday_campaigns: {
      user: {
        username: 'XXXXXX',
      },
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too — the payload status is what counts
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'headers' => [
        'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('https://my.textme.co.il/api', [
    'json' => [
        'get_birthday_campaigns' => [
            'user' => [
                'username' => 'XXXXXX',
            ],
        ],
    ],
]);

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

// Errors arrive as HTTP 200 too — the payload status is what counts
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
php
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'get_birthday_campaigns' => [
            'user' => [
                'username' => 'XXXXXX',
            ],
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too — the payload status is what counts
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
python
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://my.textme.co.il/api",
    headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
    json={
        "get_birthday_campaigns": {
            "user": {
                "username": "XXXXXX",
            },
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too — the payload status is what counts
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"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 — the payload status is what counts
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
java
// Java 17+ — java.net.http, no dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

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

var payload = """
    {
      "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 — 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 — 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 — 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+ / 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({
    edit_birthday_campaign: {
      user: {
        username: 'XXXX',
      },
      campaign_id: 'XXXX',
      message: 'XXXX',
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too — the payload status is what counts
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'headers' => [
        'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('https://my.textme.co.il/api', [
    'json' => [
        'edit_birthday_campaign' => [
            'user' => [
                'username' => 'XXXX',
            ],
            'campaign_id' => 'XXXX',
            'message' => 'XXXX',
        ],
    ],
]);

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

// Errors arrive as HTTP 200 too — the payload status is what counts
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
php
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'edit_birthday_campaign' => [
            'user' => [
                'username' => 'XXXX',
            ],
            'campaign_id' => 'XXXX',
            'message' => 'XXXX',
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too — the payload status is what counts
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
python
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://my.textme.co.il/api",
    headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
    json={
        "edit_birthday_campaign": {
            "user": {
                "username": "XXXX",
            },
            "campaign_id": "XXXX",
            "message": "XXXX",
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too — the payload status is what counts
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"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 — the payload status is what counts
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
java
// Java 17+ — java.net.http, no dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

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

var payload = """
    {
      "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 — 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 — 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 — 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.