---
url: https://textme-docs.matat.io/he/use-cases/reseller-subscribers.md
description: >-
  מודל שני שמות המשתמש, יצירה ומימון של חשבונות משנה, פעולה בשמם, וביקורת היתרה
  של כל חשבון.
---

# חשבונות משנה למשווקים

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

אם החשבון שלכם אינו משווק, הפעולות האלה מחזירות [סטטוס `511`](../reference/status-codes.md#authentication-and-permissions) ואפשר לדלג על העמוד.

## מודל שני שמות המשתמש

כל בקשה נושאת שתי זהויות:

| | איפה | מה זה |
|---|---|---|
| טוקן | כותרת `Authorization: Bearer …` | פרט הגישה **שלכם**. תמיד של המשווק. |
| `user.username` | גוף הבקשה | החשבון שהפעולה מתבצעת **עבורו**. |

בתעבורה שלכם שניהם אותו חשבון. כדי לשלוח בשם חשבון משנה, השאירו את הטוקן שלכם והגדירו את `user.username` לשלהם:

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<sms>
    <user>
        <username>israelisraeli</username>  <!-- חשבון המשנה -->
    </user>
    <source>055xxxxxxx</source>
    <destinations>
        <phone id="order-10052">5xxxxxxxx</phone>
    </destinations>
    <message>Your order is on its way.</message>
</sms>
```

```json [JSON]
{
  "sms": {
    "user": { "username": "israelisraeli" },
    "source": "055xxxxxxx",
    "destinations": {
      "phone": { "$": { "id": "order-10052" }, "_": "5xxxxxxxx" }
    },
    "message": "Your order is on its way."
  }
}
```

:::

הקרדיט נגרע מהארנק של חשבון המשנה, השליחה מופיעה בדוחות שלהם, וה[שולחים המאומתים](../endpoints/verified-senders.md) שלהם חלים. `username` שאינו אחד משלכם נכשל בסטטוס `503`.

::: warning החלפה בין השניים היא הטעות הקלאסית
`user.username` שמוגדר לחשבון שלכם בזמן שהכוונה היא לפעול עבור חשבון משנה יצליח בשמחה, ויגרע את *הקרדיט שלכם* וירשום את השליחה תחת *הדוחות שלכם*. שום דבר לא נכשל; הכסף פשוט יוצא מהכיס הלא נכון. שווה בדיקת יחידה.
:::

## צעד 1, יצירת חשבון משנה

::: 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+ או כל דפדפן מודרני. ללא תלויות
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()

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

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

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

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

	// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
```

```java [Java]
// Java 17+ באמצעות java.net.http. ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class 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());

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

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

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

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

    println!("{result}");
    Ok(())
}
```

:::

::: 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"
}
```

:::

`userDetails.amount` הוא הקרדיט הפותח, שנגרע מהיתרה שלכם. `otpPhone` הוא אופציונלי ומאפשר אימות OTP למשתמש החדש.

| Status | משמעות |
|---|---|
| `990` | הסכום עובר את הקרדיט שיש לכם. |
| `991` | הסכום אינו כולו ספרות. |
| `992` | ה-`source` המוגדר כברירת מחדל מחוץ למגבלת 11 התווים. |
| `993` | אורך הסיסמה נדחה. |
| `994` | שם המשתמש הזה כבר קיים, בחרו אחר. |
| `995` | אורך שם המשתמש נדחה. |
| `996` | אורך שם התצוגה נדחה. |

::: danger הסיסמה עוברת בגוף הבקשה כטקסט גלוי
TLS מגן עליה בתעבורה, אבל היא תגיע לכל לוג בקשות שאתם שומרים. הסתירו את `userDetails.password` לפני תיעוד, וגנרטו את הערך במקום לתת לאדם לבחור אותו:

```js
const password = crypto.randomBytes(18).toString('base64url')
```

:::

::: warning בדקו כפילות `994` לפני היצירה
אין פעולה שבודקת "האם שם המשתמש הזה קיים", וכשל ביצירה לא מספר לכם דבר נוסף. נהלו רישום משלכם של שמות המשתמש שהנפקתם, כך שהתנגשות תיתפס לפני הקריאה ולא אחריה.
:::

## צעד 2, טעינת הארנק

::: 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+ או כל דפדפן מודרני. ללא תלויות
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()

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

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

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

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

	// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
```

```java [Java]
// Java 17+ באמצעות java.net.http. ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class 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());

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

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

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

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

    println!("{result}");
    Ok(())
}
```

:::

::: 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"
}
```

:::

`amount` מעביר קרדיטים של הודעות. `amount_int` מעביר **כסף** במקום. שלחו אחד מהם, מעולם לא את שניהם.

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

אם טעינה נכשלת בטיים-אאוט, **אל תנסו אותה שוב**. קראו את היתרה עם [`getBlanceSubs`](#step-3-read-every-balance) והחליטו לפי המספר בפועל:

```js
// טיים-אאוט לא מספר לכם דבר. קראו את המצב, ואז החליטו.
const before = await subAccountBalance('username1')

try {
  await textme(topUp('username1', 5000))
} catch (timeout) {
  const after = await subAccountBalance('username1')

  // נסו שוב רק אם ניתן להראות שהקרדיט לא נכנס.
  if (after === before) await textme(topUp('username1', 5000))
}
```

:::

## צעד 3. קריאת כל היתרות {#step-3-read-every-balance}

קריאה אחת מחזירה את היתרה של כל חשבון משנה. שימו לב לשגיאת הכתיב. אלמנט השורש הוא באמת `getBlanceSubs`:

::: 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+ או כל דפדפן מודרני. ללא תלויות
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()

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

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

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

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

	// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
```

```java [Java]
// Java 17+ באמצעות java.net.http. ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class 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());

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

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

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

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

    println!("{result}");
    Ok(())
}
```

:::

::: 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"
    }
  ]
}
```

:::

::: tip `getBlanceSubs`, לא `getBalanceSubs`
איות נכון מחזיר סטטוס `997`, *Not a valid command sent*. שימו הערה בקוד לידו כדי שאף אחד לא "יתקן" את שגיאת הכתיב.
:::

::: warning לרשומות אין `username`
הן נושאות `name` ו-`sms_user_id`, ולא את שם המשתמש להתחברות שהעברתם ל-`addSub`. כדי למפות יתרות לרשומות שלכם, שמרו את `sms_user_id` בזמן יצירת החשבון. או שמרו על `name` ייחודי ומשמעותי.
:::

סריקת יתרה נמוכה, שזה מה שהפעולה הזו משמשת לו בדרך כלל:

::: code-group

```js [JavaScript]
const { balances } = await textme({
  // כן, "Blance", זה האיות בפרוטוקול.
  getBlanceSubs: { user: { username: 'reseller' } },
})

const low = balances.filter((b) => Number(b.amount) < 500)

for (const account of low) {
  // אין `username` בתשובה. sms_user_id הוא המפתח היציב.
  await alerts.lowBalance({ id: account.sms_user_id, name: account.name, left: account.amount })
}
```

```python [Python]
# כן, "Blance", זה האיות בפרוטוקול.
balances = textme({"getBlanceSubs": {"user": {"username": "reseller"}}})["balances"]

low = [b for b in balances if int(b["amount"]) < 500]

for account in low:
    # אין `username` בתשובה. sms_user_id הוא המפתח היציב.
    alerts.low_balance(id=account["sms_user_id"], name=account["name"], left=account["amount"])
```

```php [PHP]
<?php

// כן, "Blance", זה האיות בפרוטוקול.
$balances = textme(['getBlanceSubs' => ['user' => ['username' => 'reseller']]])['balances'];

$low = array_filter($balances, fn ($b) => (int) $b['amount'] < 500);

foreach ($low as $account) {
    // אין `username` בתשובה. sms_user_id הוא המפתח היציב.
    $alerts->lowBalance($account['sms_user_id'], $account['name'], $account['amount']);
}
```

:::

## ניהול שולחים בין חשבונות משנה

חשבונות משנה מאמתים את השולחים שלהם. `is_subs: 1` ב-[`getVerifiedPhones`](../endpoints/verified-senders.md) נותן לכם מבט אחד על כולם, כאשר `USER_ID` מזהה את הבעלים של כל שורה:

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<getVerifiedPhones>
    <user>
        <username>username</username>
    </user>
    <is_subs>1</is_subs>
</getVerifiedPhones>
```

```json [JSON]
{
  "getVerifiedPhones": {
    "user": {
      "username": "username"
    },
    "is_subs": "1"
  }
}
```

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

```js [JavaScript]
// Node.js 18+ או כל דפדפן מודרני. ללא תלויות
const response = await fetch('https://my.textme.co.il/api', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TEXTME_API_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    getVerifiedPhones: {
      user: {
        username: 'username',
      },
      is_subs: '1',
    },
  }),
})

const result = await response.json()

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
```

```php [PHP]
<?php
// composer require guzzlehttp/guzzle

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

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

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

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
```

```php [Laravel]
<?php

use Illuminate\Support\Facades\Http;

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

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
```

```python [Python]
# pip install httpx
import os

import httpx

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

# גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
```

```go [Go]
package main

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

func main() {
	payload, _ := json.Marshal(map[string]any{
		"getVerifiedPhones": map[string]any{
			"user": map[string]any{
				"username": "username",
			},
			"is_subs": "1",
		},
	})

	req, _ := http.NewRequest("POST", "https://my.textme.co.il/api", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTME_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result struct {
		Status  json.Number `json:"status"`
		Message string      `json:"message"`
	}
	if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
		panic(err)
	}

	// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
```

```java [Java]
// Java 17+ באמצעות java.net.http. ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TextMeVerifiedPhones {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "getVerifiedPhones": {
                "user": {
                  "username": "username"
                },
                "is_subs": "1"
              }
            }
            """;

        HttpRequest request = HttpRequest.newBuilder(URI.create("https://my.textme.co.il/api"))
            .header("Authorization", "Bearer " + System.getenv("TEXTME_API_TOKEN"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        // גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
        System.out.println(response.body());
    }
}
```

```csharp [C#]
// .NET 8+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "getVerifiedPhones": {
        "user": {
          "username": "username"
        },
        "is_subs": "1"
      }
    }
    """;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("TEXTME_API_TOKEN"));

var response = await http.PostAsync("https://my.textme.co.il/api",
    new StringContent(payload, Encoding.UTF8, "application/json"));

var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
var status = result.GetProperty("status").ToString();

// גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
    var message = result.GetProperty("message").ToString();
    throw new Exception($"TextMe {status}: {message}");
}

Console.WriteLine(result);
```

```ruby [Ruby]
require "net/http"
require "json"

uri = URI("https://my.textme.co.il/api")

request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('TEXTME_API_TOKEN')}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
  "getVerifiedPhones" => {
    "user" => {
      "username" => "username",
    },
    "is_subs" => "1",
  },
})

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

result = JSON.parse(response.body)

# גם שגיאה חוזרת כ-HTTP 200, לכן הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?

pp result
```

```rust [Rust]
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result: Value = reqwest::Client::new()
        .post("https://my.textme.co.il/api")
        .bearer_auth(std::env::var("TEXTME_API_TOKEN")?)
        .json(&json!({
          "getVerifiedPhones": {
            "user": {
              "username": "username"
            },
            "is_subs": "1"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

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

    println!("{result}");
    Ok(())
}
```

:::

::: code-group

```xml [XML]
<?xml version="1.0" encoding="UTF-8"?>
<getVerifiedPhones>
    <status>0</status>
    <message></message>
    <verified_phones>
        <verified_phone>
            <PHONE>xxxxxxxxxx</PHONE>
            <USER_ID>xxx</USER_ID>
            <ASSIGNMENT_DATE>03/12/14 12:33</ASSIGNMENT_DATE>
            <LAST_UPDATE>03/12/14 12:33</LAST_UPDATE>
        </verified_phone>
    </verified_phones>
</getVerifiedPhones>
```

```json [JSON]
{
  "status": 0,
  "message": "",
  "verified_phones": [
    {
      "PHONE": "xxxxxxxxxx",
      "USER_ID": "XXXX",
      "ASSIGNMENT_DATE": "03/12/14 12:33",
      "LAST_UPDATE": "03/12/14 12:33"
    }
  ]
}
```

:::

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

## מה שה-API אינו מכסה

| מה שרוצים | המציאות |
|---|---|
| להחזיר קרדיט מחשבון משנה | אין פעולה. הקרדיט זורם בכיוון אחד. |
| למחוק או להשעות חשבון משנה | ממשק הניהול בלבד. |
| לשנות סיסמה של חשבון משנה | ממשק הניהול בלבד. |
| לקבל רשימה של חשבונות המשנה שלכם | הדבר הקרוב ביותר הוא `getBlanceSubs`, שמחזירה `name` ו-`sms_user_id` אבל לא שמות משתמש. |
| לקרוא יתרה של חשבון משנה אחד | קראו את כולן וסננו, או קראו ל-[`balance`](../endpoints/balance.md) עם `user.username` שלהם. |

מכיוון שאין פעולת רשימה שמחזירה שמות משתמש, **נהלו רישום משלכם**: שם משתמש, `sms_user_id`, ומה העברתם. שחזור שלו מה-API בהמשך אינו אפשרי.

## צ'ק-ליסט הצטרפות

1. שמרו את שם המשתמש ברישום שלכם, כך ש-`994` לא יפתיע.
2. גנרטו את הסיסמה; אף פעם לא לתעד אותה.
3. `addSub` עם `amount` פותח מתון.
4. שמרו את החשבון שהוחזר מול `sms_user_id` מסריקת `getBlanceSubs`.
5. בקשו מהם לאמת שולח, ואז ודאו עם `getVerifiedPhones` ו-`is_subs: 1`.
6. שלחו הודעת בדיקה עם `user.username` שלהם וודאו שהיא נגרעת מ*היתרה שלהם*.
7. הוסיפו אותם לסריקת היתרה הנמוכה שלכם.

## הצעד הבא

* **[נקודת הקצה של מנויים](../endpoints/subscribers.md)**: תיעוד הפעולות המלא
* **[מספרי שולח מאומתים](../endpoints/verified-senders.md)**: מה כל חשבון משנה צריך לעשות לפני שליחה
* **[אימות](../guide/authentication.md)**: מודל שתי הזהויות בפירוט
