---
url: https://textme-docs.matat.io/endpoints/subscribers.md
description: >-
  addSub, updateAmountSub and getBlanceSubs: the reseller operations that
  create, fund and audit sub-accounts.
---

# Subscribers

Reseller operations. An account that resells TextMe capacity can create sub-accounts, fund their wallets from its own credit, and read their balances.

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

If your account does not resell, these return [status `511`](../reference/status-codes.md#authentication-and-permissions) and you can skip the page.

::: tip Two usernames, two roles
Throughout this page, `user.username` is **your** account (the reseller doing the work) while `userDetails.username` is the **sub-account** being acted on. Getting them the wrong way round is the most common mistake here.
:::

## `addSub` Create a sub-account

Creates a sub-account with login credentials, a default sender and an opening credit balance drawn from yours.

### Parameters

| Name | Type | Description | Required |
|---|---|---|---|
| `addSub` | object | Contains all other elements. | ✔️ |
| `user` | object | Contains the user element. | ✔️ |
| `user.username` | string | Your username. The reseller account. | ✔️ |
| `userDetails` | object | The details of the user you want to add. | ✔️ |
| `userDetails.name` | string | Display name for the new user. | ✔️ |
| `userDetails.username` | string | Login username for the new user. | ✔️ |
| `userDetails.password` | string | Password for the new user. | ✔️ |
| `userDetails.source` | string | Default sender for the new user. | ✔️ |
| `userDetails.amount` | int | Credits to grant on creation, taken from your balance. | ✔️ |
| `userDetails.otpPhone` | string | Phone number for OTP authentication. | ➖ |

### Request example

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<addSub>
    <user>
        <username>xxxxxx</username>
    </user>
    <userDetails>
        <name>israel israeli</name>
        <username>israelisraeli</username>
        <password>israelisraeli</password>
        <source>055xxxxxxx</source>
        <amount>70000</amount>
        <otpPhone>5xxxxxxxx</otpPhone>
    </userDetails>
</addSub>
```

```json [JSON]
{
  "addSub": {
    "user": {
      "username": "xxxxxx"
    },
    "userDetails": {
      "name": "israel israeli",
      "username": "israelisraeli",
      "password": "israelisraeli",
      "source": "055xxxxxxx",
      "amount": "70000",
      "otpPhone": "5xxxxxxxx"
    }
  }
}
```

```bash [cURL]
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "addSub": {
    "user": {
      "username": "xxxxxx"
    },
    "userDetails": {
      "name": "israel israeli",
      "username": "israelisraeli",
      "password": "israelisraeli",
      "source": "055xxxxxxx",
      "amount": "70000",
      "otpPhone": "5xxxxxxxx"
    }
  }
}'
```

```js [JavaScript]
// 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({
    addSub: {
      user: {
        username: 'xxxxxx',
      },
      userDetails: {
        name: 'israel israeli',
        username: 'israelisraeli',
        password: 'israelisraeli',
        source: '055xxxxxxx',
        amount: '70000',
        otpPhone: '5xxxxxxxx',
      },
    },
  }),
})

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]
<?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' => [
        'addSub' => [
            'user' => [
                'username' => 'xxxxxx',
            ],
            'userDetails' => [
                'name' => 'israel israeli',
                'username' => 'israelisraeli',
                'password' => 'israelisraeli',
                'source' => '055xxxxxxx',
                'amount' => '70000',
                'otpPhone' => '5xxxxxxxx',
            ],
        ],
    ],
]);

$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 [Laravel]
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'addSub' => [
            'user' => [
                'username' => 'xxxxxx',
            ],
            'userDetails' => [
                'name' => 'israel israeli',
                'username' => 'israelisraeli',
                'password' => 'israelisraeli',
                'source' => '055xxxxxxx',
                'amount' => '70000',
                'otpPhone' => '5xxxxxxxx',
            ],
        ],
    ])
    ->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 [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={
        "addSub": {
            "user": {
                "username": "xxxxxx",
            },
            "userDetails": {
                "name": "israel israeli",
                "username": "israelisraeli",
                "password": "israelisraeli",
                "source": "055xxxxxxx",
                "amount": "70000",
                "otpPhone": "5xxxxxxxx",
            },
        },
    },
)
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 [Go]
package main

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

func main() {
	payload, _ := json.Marshal(map[string]any{
		"addSub": map[string]any{
			"user": map[string]any{
				"username": "xxxxxx",
			},
			"userDetails": map[string]any{
				"name": "israel israeli",
				"username": "israelisraeli",
				"password": "israelisraeli",
				"source": "055xxxxxxx",
				"amount": "70000",
				"otpPhone": "5xxxxxxxx",
			},
		},
	})

	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]
// 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 TextMeSubscriberAdd {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "addSub": {
                "user": {
                  "username": "xxxxxx"
                },
                "userDetails": {
                  "name": "israel israeli",
                  "username": "israelisraeli",
                  "password": "israelisraeli",
                  "source": "055xxxxxxx",
                  "amount": "70000",
                  "otpPhone": "5xxxxxxxx"
                }
              }
            }
            """;

        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 [C#]
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "addSub": {
        "user": {
          "username": "xxxxxx"
        },
        "userDetails": {
          "name": "israel israeli",
          "username": "israelisraeli",
          "password": "israelisraeli",
          "source": "055xxxxxxx",
          "amount": "70000",
          "otpPhone": "5xxxxxxxx"
        }
      }
    }
    """;

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 [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({
  "addSub" => {
    "user" => {
      "username" => "xxxxxx",
    },
    "userDetails" => {
      "name" => "israel israeli",
      "username" => "israelisraeli",
      "password" => "israelisraeli",
      "source" => "055xxxxxxx",
      "amount" => "70000",
      "otpPhone" => "5xxxxxxxx",
    },
  },
})

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 [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!({
          "addSub": {
            "user": {
              "username": "xxxxxx"
            },
            "userDetails": {
              "name": "israel israeli",
              "username": "israelisraeli",
              "password": "israelisraeli",
              "source": "055xxxxxxx",
              "amount": "70000",
              "otpPhone": "5xxxxxxxx"
            }
          }
        }))
        .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

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<addSub>
    <status>0</status>
    <message>The user was created successfully</message>
</addSub>
```

```json [JSON]
{
  "status": 0,
  "message": "The user was created successfully"
}
```

:::

### Errors

| Status | When |
|---|---|
| `990` | `amount` exceeds the credit you hold. |
| `991` | `amount` contains something other than digits. |
| `992` | `source` is too long or too short. |
| `993` | `password` is too long or too short. |
| `994` | `username` already exists. |
| `995` | `username` is too long or too short. |
| `996` | `name` is too long or too short. |
| `511` | Your account is not entitled to create sub-accounts. |

## `updateAmountSub` Top up a wallet

Moves credit into a sub-account's wallet.

### Parameters

| Name | Type | Description | Required |
|---|---|---|---|
| `updateAmountSub` | object | Contains all other elements. | ✔️ |
| `user` | object | Contains the user element. | ✔️ |
| `user.username` | string | Your username. The reseller account. | ✔️ |
| `userDetails` | object | The details of the user you want to update. | ✔️ |
| `userDetails.username` | string | The sub-account's internal username. | ✔️ |
| `userDetails.amount` | int | Credits to grant. | ✔️ |
| `userDetails.amount_int` | int | Use this instead of `amount` to update the sub-account's **money** balance rather than its message credits. Send it without an `amount` element. | ➖ |

::: warning `amount` and `amount_int` are alternatives
`amount` moves message credits; `amount_int` moves money. Send one or the other, never both. `amount_int` is documented as replacing `amount`, not supplementing it.
:::

### Request example

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<updateAmountSub>
    <user>
        <username>xxxxxx</username>
    </user>
    <userDetails>
        <username>username1</username>
        <amount>70000</amount>
    </userDetails>
</updateAmountSub>
```

```json [JSON]
{
  "updateAmountSub": {
    "user": {
      "username": "xxxxxx"
    },
    "userDetails": {
      "username": "username1",
      "amount": "70000"
    }
  }
}
```

```bash [cURL]
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "updateAmountSub": {
    "user": {
      "username": "xxxxxx"
    },
    "userDetails": {
      "username": "username1",
      "amount": "70000"
    }
  }
}'
```

```js [JavaScript]
// 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({
    updateAmountSub: {
      user: {
        username: 'xxxxxx',
      },
      userDetails: {
        username: 'username1',
        amount: '70000',
      },
    },
  }),
})

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]
<?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' => [
        'updateAmountSub' => [
            'user' => [
                'username' => 'xxxxxx',
            ],
            'userDetails' => [
                'username' => 'username1',
                'amount' => '70000',
            ],
        ],
    ],
]);

$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 [Laravel]
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'updateAmountSub' => [
            'user' => [
                'username' => 'xxxxxx',
            ],
            'userDetails' => [
                'username' => 'username1',
                'amount' => '70000',
            ],
        ],
    ])
    ->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 [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={
        "updateAmountSub": {
            "user": {
                "username": "xxxxxx",
            },
            "userDetails": {
                "username": "username1",
                "amount": "70000",
            },
        },
    },
)
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 [Go]
package main

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

func main() {
	payload, _ := json.Marshal(map[string]any{
		"updateAmountSub": map[string]any{
			"user": map[string]any{
				"username": "xxxxxx",
			},
			"userDetails": map[string]any{
				"username": "username1",
				"amount": "70000",
			},
		},
	})

	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]
// 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 TextMeSubscriberWallet {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "updateAmountSub": {
                "user": {
                  "username": "xxxxxx"
                },
                "userDetails": {
                  "username": "username1",
                  "amount": "70000"
                }
              }
            }
            """;

        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 [C#]
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "updateAmountSub": {
        "user": {
          "username": "xxxxxx"
        },
        "userDetails": {
          "username": "username1",
          "amount": "70000"
        }
      }
    }
    """;

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 [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({
  "updateAmountSub" => {
    "user" => {
      "username" => "xxxxxx",
    },
    "userDetails" => {
      "username" => "username1",
      "amount" => "70000",
    },
  },
})

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 [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!({
          "updateAmountSub": {
            "user": {
              "username": "xxxxxx"
            },
            "userDetails": {
              "username": "username1",
              "amount": "70000"
            }
          }
        }))
        .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

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<updateAmountSub>
    <status>0</status>
    <message>Wallet successfully updated</message>
</updateAmountSub>
```

```json [JSON]
{
  "status": 0,
  "message": "Wallet successfully updated"
}
```

:::

### Errors

| Status | When |
|---|---|
| `990` | The amount exceeds the credit you hold. |
| `991` | The amount contains something other than digits. |
| `503` | `userDetails.username` is not one of your sub-accounts. |
| `511` | Your account is not entitled to this operation. |

::: danger Not idempotent
There is no request id and no deduplication. Calling this twice grants the credit twice. If a call times out, read the balance with [`getBlanceSubs`](#getblancesubs-read-every-balance) before retrying, never blind-retry a top-up.
:::

## `getBlanceSubs` Read every balance

Returns the balance of every sub-account in one call.

::: tip The name is misspelled on the wire
The root element really is `getBlanceSubs`. "Blance", not "Balance". Spelling it correctly returns [status `997`](../reference/status-codes.md#parsing-and-routing), *Not a valid command sent*.
:::

### Parameters

| Name | Type | Description | Required |
|---|---|---|---|
| `getBlanceSubs` | object | Contains all other elements. | ✔️ |
| `user` | object | Contains the user element. | ✔️ |
| `username` | string | Your username. The reseller account. | ✔️ |

### Request example

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<getBlanceSubs>
    <user>
        <username>xxxxxx</username>
    </user>
</getBlanceSubs>
```

```json [JSON]
{
  "getBlanceSubs": {
    "user": {
      "username": "xxxxxx"
    }
  }
}
```

```bash [cURL]
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "getBlanceSubs": {
    "user": {
      "username": "xxxxxx"
    }
  }
}'
```

```js [JavaScript]
// 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({
    getBlanceSubs: {
      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]
<?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' => [
        'getBlanceSubs' => [
            '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 [Laravel]
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'getBlanceSubs' => [
            '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 [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={
        "getBlanceSubs": {
            "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 [Go]
package main

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

func main() {
	payload, _ := json.Marshal(map[string]any{
		"getBlanceSubs": 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]
// 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 TextMeSubscriberBalances {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "getBlanceSubs": {
                "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 [C#]
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "getBlanceSubs": {
        "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 [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({
  "getBlanceSubs" => {
    "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 [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!({
          "getBlanceSubs": {
            "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

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<getBlanceSubs>
    <status>0</status>
    <message></message>
    <balances>
        <balance>
            <amount>80</amount>
            <sms_user_id>xxx</sms_user_id>
            <name>name1</name>
        </balance>
        <balance>
            <amount>50</amount>
            <sms_user_id>xxx</sms_user_id>
            <name>name2</name>
        </balance>
    </balances>
</getBlanceSubs>
```

```json [JSON]
{
  "status": 0,
  "message": "",
  "balances": [
    {
      "amount": "80",
      "sms_user_id": "XXXX",
      "name": "name1"
    },
    {
      "amount": "50",
      "sms_user_id": "XXXX",
      "name": "name2"
    }
  ]
}
```

:::

| Field | Type | Description |
|---|---|---|
| `balances` | array | One entry per sub-account. |
| `balances[].amount` | string | Credits remaining. |
| `balances[].sms_user_id` | string | The sub-account's internal id. |
| `balances[].name` | string | Its display name. |

::: warning The response has no `username`
Entries carry `name` and `sms_user_id`, but not the login username you used in `addSub`. If you need to map balances back to your own records, store `sms_user_id`, or keep the `name` unique and meaningful.
:::

### Errors

| Status | When |
|---|---|
| `3`, `10`, `11` | Token invalid, expired, or belonging to another username. |
| `511` | Your account is not entitled to this operation. |

## Field notes

### Credit flows one way through the API

`addSub` and `updateAmountSub` move credit from you to a sub-account. There is no operation that pulls it back. Grant in the amounts you actually intend to hand over, and top up more often rather than in large blocks.

### Acting on a sub-account's behalf

Your token authenticates; `user.username` chooses who the operation is *for*. Set it to a sub-account's username and you can send, read reports and manage lists on their behalf. See [Authentication](../guide/authentication.md#the-two-identities-in-a-request). The account must be one of yours, or the call fails with `503`.

Sub-accounts verify their own senders; [`getVerifiedPhones` with `is_subs: 1`](./verified-senders.md#getverifiedphones-list-verified-numbers) gives you a view across all of them.

### Passwords go over the wire

`addSub` carries a plaintext password. It is TLS-protected in transit, but it will end up in any request log you keep. Redact `userDetails.password` before logging, and generate the value rather than reusing one a human picked.
