Skip to content

Notifications (SMS)

Configure and operate customer-facing SMS notifications: the SMS provider credentials, per-shipping-method message templates, a one-off test send, and a searchable delivery log.

Scope

These endpoints are customer-scoped, not license-scoped — they act on the authenticated merchant account behind your client credentials and cover all of its shipments. They do not read a license_key.


GET /notifications/sms-settings

Return the caller account's SMS provider settings (a singleton — always 200). Auth: client credentials.

Parameters

None.

Example request

bash
curl --location 'https://app.shipos.co.il/api/v2/notifications/sms-settings' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies
const response = await fetch(
  'https://app.shipos.co.il/api/v2/notifications/sms-settings',
  {
    headers: {
      'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
      'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
      Accept: 'application/json',
    },
  },
)

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data: settings } = await response.json()

console.log(settings.sender_name, settings.configured)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$settings = json_decode(
    $client->get('notifications/sms-settings')->getBody()->getContents(),
    true,
)['data'];

echo $settings['sender_name'], ' configured: ',
    var_export($settings['configured'], true), PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$settings = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->get('https://app.shipos.co.il/api/v2/notifications/sms-settings')
    ->throw()
    ->json('data');

logger()->info($settings['sender_name'], ['configured' => $settings['configured']]);
python
# pip install httpx
import os

import httpx

response = httpx.get(
    "https://app.shipos.co.il/api/v2/notifications/sms-settings",
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
)
response.raise_for_status()
settings = response.json()["data"]

print(settings["sender_name"], settings["configured"])
go
package main

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

type smsSettingsResponse struct {
	Data struct {
		SenderName string `json:"sender_name"`
		Configured bool   `json:"configured"`
	} `json:"data"`
}

func main() {
	req, _ := http.NewRequest("GET",
		"https://app.shipos.co.il/api/v2/notifications/sms-settings", nil)
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")

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

	var payload smsSettingsResponse
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	fmt.Println(payload.Data.SenderName, payload.Data.Configured)
}
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 ShipOsSmsSettings {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/notifications/sms-settings"))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .build();

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

        if (response.statusCode() != 200) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":{...}} — map with Jackson/Gson
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var payload = await http.GetFromJsonAsync<JsonDocument>("notifications/sms-settings")
    ?? throw new InvalidOperationException("Empty response");
var settings = payload.RootElement.GetProperty("data");

Console.WriteLine(settings.GetProperty("sender_name").GetString());
Console.WriteLine(settings.GetProperty("configured").GetBoolean());
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/notifications/sms-settings")
request = Net::HTTP::Get.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"

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

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

settings = JSON.parse(response.body).fetch("data")

puts "#{settings["sender_name"]} configured: #{settings["configured"]}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload: Value = reqwest::Client::new()
        .get("https://app.shipos.co.il/api/v2/notifications/sms-settings")
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    let settings = &payload["data"];
    println!("{} {}", settings["sender_name"], settings["configured"]);
    Ok(())
}

Response 200

json
{
  "data": {
    "id": 42,
    "username": "shipos_dafni",
    "sender_name": "DafniHair",
    "has_token": true,
    "configured": true,
    "created_at": "2026-01-01T00:00:00.000000Z",
    "updated_at": "2026-07-20T12:30:00.000000Z"
  }
}

Fields

FieldTypeDescription
idintSMS settings row id.
usernamestring | nullProvider account username.
sender_namestring | nullSMS sender name shown to recipients.
has_tokenboolWhether an API token is stored (the token itself is never returned).
configuredbooltrue when both username and token are present, i.e. sending is set up.
created_atstringCreation timestamp.
updated_atstringUpdate timestamp.

Credentials never returned

The provider token (and any password) is write-only. The response only signals presence via has_token / configured.

Errors

StatuscodeWhen
401unauthenticatedMissing or invalid client credentials.

PUT /notifications/sms-settings

Persist the caller account's SMS provider settings and return the updated singleton (always 200). Auth: client credentials.

Parameters

Body

FieldTypeRequiredDescription
usernamestringyesProvider account username. Max 255 chars.
tokenstringyesProvider API token (write-only; stored, never returned).
sender_namestringyesSMS sender name. Max 255 chars.

Example request

bash
curl --location --request PUT 'https://app.shipos.co.il/api/v2/notifications/sms-settings' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
  "username": "shipos_dafni",
  "token": "provider-api-token-value",
  "sender_name": "DafniHair"
}'
js
// Node.js 18+ / browsers — no dependencies
const response = await fetch(
  'https://app.shipos.co.il/api/v2/notifications/sms-settings',
  {
    method: 'PUT',
    headers: {
      'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
      'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      username: 'shipos_dafni',
      token: 'provider-api-token-value',
      sender_name: 'DafniHair',
    }),
  },
)

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data: settings } = await response.json()

console.log(settings.sender_name, settings.configured)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->put('notifications/sms-settings', [
    'json' => [
        'username' => 'shipos_dafni',
        'token' => 'provider-api-token-value',
        'sender_name' => 'DafniHair',
    ],
]);

$settings = json_decode($response->getBody()->getContents(), true)['data'];

echo $settings['sender_name'], ' configured: ',
    var_export($settings['configured'], true), PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$settings = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->put('https://app.shipos.co.il/api/v2/notifications/sms-settings', [
        'username' => 'shipos_dafni',
        'token' => 'provider-api-token-value',
        'sender_name' => 'DafniHair',
    ])
    ->throw()
    ->json('data');

logger()->info($settings['sender_name'], ['configured' => $settings['configured']]);
python
# pip install httpx
import os

import httpx

response = httpx.put(
    "https://app.shipos.co.il/api/v2/notifications/sms-settings",
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
    json={
        "username": "shipos_dafni",
        "token": "provider-api-token-value",
        "sender_name": "DafniHair",
    },
)
response.raise_for_status()
settings = response.json()["data"]

print(settings["sender_name"], settings["configured"])
go
package main

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

func main() {
	body, _ := json.Marshal(map[string]string{
		"username":    "shipos_dafni",
		"token":       "provider-api-token-value",
		"sender_name": "DafniHair",
	})

	req, _ := http.NewRequest("PUT",
		"https://app.shipos.co.il/api/v2/notifications/sms-settings",
		bytes.NewReader(body))
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

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

	var payload struct {
		Data struct {
			SenderName string `json:"sender_name"`
			Configured bool   `json:"configured"`
		} `json:"data"`
	}
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	fmt.Println(payload.Data.SenderName, payload.Data.Configured)
}
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 ShipOsUpdateSmsSettings {
    public static void main(String[] args) throws Exception {
        String body = """
            {"username":"shipos_dafni","token":"provider-api-token-value","sender_name":"DafniHair"}
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/notifications/sms-settings"))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .header("Content-Type", "application/json")
            .PUT(HttpRequest.BodyPublishers.ofString(body))
            .build();

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

        if (response.statusCode() != 200) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":{...}} — map with Jackson/Gson
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var response = await http.PutAsJsonAsync("notifications/sms-settings", new
{
    username = "shipos_dafni",
    token = "provider-api-token-value",
    sender_name = "DafniHair",
});
response.EnsureSuccessStatusCode();

var payload = await response.Content.ReadFromJsonAsync<JsonDocument>()
    ?? throw new InvalidOperationException("Empty response");
var settings = payload.RootElement.GetProperty("data");

Console.WriteLine(settings.GetProperty("sender_name").GetString());
Console.WriteLine(settings.GetProperty("configured").GetBoolean());
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/notifications/sms-settings")
request = Net::HTTP::Put.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
  username: "shipos_dafni",
  token: "provider-api-token-value",
  sender_name: "DafniHair",
})

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

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

settings = JSON.parse(response.body).fetch("data")

puts "#{settings["sender_name"]} configured: #{settings["configured"]}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload: Value = reqwest::Client::new()
        .put("https://app.shipos.co.il/api/v2/notifications/sms-settings")
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .json(&serde_json::json!({
            "username": "shipos_dafni",
            "token": "provider-api-token-value",
            "sender_name": "DafniHair",
        }))
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    let settings = &payload["data"];
    println!("{} {}", settings["sender_name"], settings["configured"]);
    Ok(())
}

Response 200

json
{
  "data": {
    "id": 42,
    "username": "shipos_dafni",
    "sender_name": "DafniHair",
    "has_token": true,
    "configured": true,
    "created_at": "2026-01-01T00:00:00.000000Z",
    "updated_at": "2026-07-29T10:00:00.000000Z"
  }
}

Errors

StatuscodeWhen
401unauthenticatedMissing or invalid client credentials.
422validation_failedusername, token, or sender_name missing or too long.

POST /notifications/test-sms

Send a one-off test SMS through the account's configured provider. Auth: client credentials.

Parameters

Body

FieldTypeRequiredDescription
phonestringyesDestination phone number. Max 20 chars.
messagestringyesMessage body (HTML allowed; rendered to plain text before sending). Max 1000 chars.

Example request

bash
curl --location --request POST 'https://app.shipos.co.il/api/v2/notifications/test-sms' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
  "phone": "0501234567",
  "message": "Test message from ShipOS"
}'
js
// Node.js 18+ / browsers — no dependencies
const response = await fetch(
  'https://app.shipos.co.il/api/v2/notifications/test-sms',
  {
    method: 'POST',
    headers: {
      'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
      'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      phone: '0501234567',
      message: 'Test message from ShipOS',
    }),
  },
)

// 424 still returns a `data` payload — the provider simply refused.
const { data: result } = await response.json()

console.log(result.sent, result.provider_status, result.message)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

// http_errors => false so the 424 "provider rejected" payload is readable.
$response = $client->post('notifications/test-sms', [
    'json' => [
        'phone' => '0501234567',
        'message' => 'Test message from ShipOS',
    ],
    'http_errors' => false,
]);

$result = json_decode($response->getBody()->getContents(), true)['data'];

echo var_export($result['sent'], true), ' ', $result['message'], PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->post('https://app.shipos.co.il/api/v2/notifications/test-sms', [
        'phone' => '0501234567',
        'message' => 'Test message from ShipOS',
    ])
    ->throwIfStatus(fn (int $status) => $status !== 424)
    ->json('data');

logger()->info($result['message'], ['sent' => $result['sent']]);
python
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://app.shipos.co.il/api/v2/notifications/test-sms",
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
    json={"phone": "0501234567", "message": "Test message from ShipOS"},
)
if response.status_code != 424:  # 424 = provider rejected, still a data payload
    response.raise_for_status()
result = response.json()["data"]

print(result["sent"], result["provider_status"], result["message"])
go
package main

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

func main() {
	body, _ := json.Marshal(map[string]string{
		"phone":   "0501234567",
		"message": "Test message from ShipOS",
	})

	req, _ := http.NewRequest("POST",
		"https://app.shipos.co.il/api/v2/notifications/test-sms",
		bytes.NewReader(body))
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

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

	// 200 and 424 both carry a data payload.
	var payload struct {
		Data struct {
			Sent           bool   `json:"sent"`
			ProviderStatus string `json:"provider_status"`
			Message        string `json:"message"`
		} `json:"data"`
	}
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	fmt.Println(payload.Data.Sent, payload.Data.ProviderStatus, payload.Data.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 ShipOsTestSms {
    public static void main(String[] args) throws Exception {
        String body = """
            {"phone":"0501234567","message":"Test message from ShipOS"}
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/notifications/test-sms"))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

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

        // 200 = accepted, 424 = provider rejected (both are data payloads).
        if (response.statusCode() != 200 && response.statusCode() != 424) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":{...}} — map with Jackson/Gson
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var response = await http.PostAsJsonAsync("notifications/test-sms", new
{
    phone = "0501234567",
    message = "Test message from ShipOS",
});

// 424 FailedDependency = provider rejected, still a data payload.
if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.FailedDependency)
{
    response.EnsureSuccessStatusCode();
}

var payload = await response.Content.ReadFromJsonAsync<JsonDocument>()
    ?? throw new InvalidOperationException("Empty response");
var result = payload.RootElement.GetProperty("data");

Console.WriteLine($"{result.GetProperty("sent").GetBoolean()} " +
    $"{result.GetProperty("message").GetString()}");
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/notifications/test-sms")
request = Net::HTTP::Post.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = JSON.dump({ phone: "0501234567", message: "Test message from ShipOS" })

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

# 424 = provider rejected, still a data payload.
unless response.is_a?(Net::HTTPSuccess) || response.code == "424"
  raise "ShipOS error: #{response.body}"
end

result = JSON.parse(response.body).fetch("data")

puts "#{result["sent"]} #{result["provider_status"]} #{result["message"]}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 424 also returns a data payload — the provider refused the send.
    let payload: Value = reqwest::Client::new()
        .post("https://app.shipos.co.il/api/v2/notifications/test-sms")
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .json(&serde_json::json!({
            "phone": "0501234567",
            "message": "Test message from ShipOS",
        }))
        .send()
        .await?
        .json()
        .await?;

    let result = &payload["data"];
    println!("{} {}", result["sent"], result["message"]);
    Ok(())
}

Response 200

json
{
  "data": {
    "sent": true,
    "provider_status": "OK",
    "message": "Message accepted"
  }
}
FieldTypeDescription
sentboolWhether the provider accepted the send.
provider_statusstringRaw provider status.
messagestringProvider-returned message describing the outcome.

Response 424 (provider rejected)

When the provider rejects the send, the endpoint returns HTTP 424 Failed Dependency — but still as a normal data payload (not an error envelope), so you can distinguish "we tried, the provider said no" from a request-level 4xx:

json
{
  "data": {
    "sent": false,
    "provider_status": "REJECTED",
    "message": "Insufficient balance"
  }
}

Errors

StatuscodeWhen
401unauthenticatedMissing or invalid client credentials.
422validation_failedphone or message missing or too long.
424Provider rejected the send. Returned as a data payload with sent: false (see above), not an error envelope.

GET /notifications/sms-logs

List the caller account's SMS logs, optionally filtered. Paginated. Auth: client credentials.

Parameters

Query

FieldTypeRequiredDescription
filter[shipping_id]intnoRestrict to logs for a single shipping.
filter[success]booleannoRestrict to successful (true) or failed (false) sends.
per_pageintnoItems per page. Default 25, capped at 100.

Example request

bash
curl --location 'https://app.shipos.co.il/api/v2/notifications/sms-logs?filter[success]=false&per_page=50' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies
const query = new URLSearchParams({
  'filter[success]': 'false',
  per_page: '50',
})

const response = await fetch(
  `https://app.shipos.co.il/api/v2/notifications/sms-logs?${query}`,
  {
    headers: {
      'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
      'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
      Accept: 'application/json',
    },
  },
)

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data: logs, meta } = await response.json()

console.log(meta.total)
for (const log of logs) {
  console.log(log.phone, log.success, log.provider.message)
}
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->get('notifications/sms-logs', [
    'query' => [
        'filter' => ['success' => 'false'],
        'per_page' => 50,
    ],
]);

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

echo $payload['meta']['total'], PHP_EOL;
foreach ($payload['data'] as $log) {
    echo $log['phone'], ' ', var_export($log['success'], true), PHP_EOL;
}
php
<?php

use Illuminate\Support\Facades\Http;

$payload = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->get('https://app.shipos.co.il/api/v2/notifications/sms-logs', [
        'filter' => ['success' => 'false'],
        'per_page' => 50,
    ])
    ->throw()
    ->json();

logger()->info('SMS logs', ['total' => $payload['meta']['total']]);
foreach ($payload['data'] as $log) {
    logger()->info($log['phone'], ['success' => $log['success']]);
}
python
# pip install httpx
import os

import httpx

response = httpx.get(
    "https://app.shipos.co.il/api/v2/notifications/sms-logs",
    params={"filter[success]": "false", "per_page": 50},
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
)
response.raise_for_status()
payload = response.json()

print(payload["meta"]["total"])
for log in payload["data"]:
    print(log["phone"], log["success"], log["provider"]["message"])
go
package main

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

type smsLogsResponse struct {
	Data []struct {
		Phone   string `json:"phone"`
		Success bool   `json:"success"`
	} `json:"data"`
	Meta struct {
		Total int `json:"total"`
	} `json:"meta"`
}

func main() {
	query := url.Values{}
	query.Set("filter[success]", "false")
	query.Set("per_page", "50")

	req, _ := http.NewRequest("GET",
		"https://app.shipos.co.il/api/v2/notifications/sms-logs?"+query.Encode(), nil)
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")

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

	var payload smsLogsResponse
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	fmt.Println(payload.Meta.Total)
	for _, log := range payload.Data {
		fmt.Println(log.Phone, log.Success)
	}
}
java
// Java 17+ — java.net.http, no dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

public class ShipOsSmsLogs {
    public static void main(String[] args) throws Exception {
        String query = URLEncoder.encode("filter[success]", StandardCharsets.UTF_8)
            + "=false&per_page=50";

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/notifications/sms-logs?" + query))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .build();

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

        if (response.statusCode() != 200) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":[...]} — map with Jackson/Gson
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;
using System.Web;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var query = HttpUtility.ParseQueryString(string.Empty);
query["filter[success]"] = "false";
query["per_page"] = "50";

var payload = await http.GetFromJsonAsync<JsonDocument>(
        $"notifications/sms-logs?{query}")
    ?? throw new InvalidOperationException("Empty response");

Console.WriteLine(payload.RootElement.GetProperty("meta").GetProperty("total").GetInt32());
foreach (var log in payload.RootElement.GetProperty("data").EnumerateArray())
{
    Console.WriteLine($"{log.GetProperty("phone").GetString()} " +
        $"{log.GetProperty("success").GetBoolean()}");
}
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/notifications/sms-logs")
uri.query = URI.encode_www_form("filter[success]" => "false", "per_page" => 50)

request = Net::HTTP::Get.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"

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

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

payload = JSON.parse(response.body)

puts payload.dig("meta", "total")
payload["data"].each do |log|
  puts "#{log["phone"]} #{log["success"]}"
end
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload: Value = reqwest::Client::new()
        .get("https://app.shipos.co.il/api/v2/notifications/sms-logs")
        .query(&[("filter[success]", "false"), ("per_page", "50")])
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    println!("{}", payload["meta"]["total"]);
    if let Some(logs) = payload["data"].as_array() {
        for log in logs {
            println!("{} {}", log["phone"], log["success"]);
        }
    }
    Ok(())
}

Response 200

json
{
  "data": [
    {
      "id": 90321,
      "shipment_id": "b7e2c1a0-...",
      "shipping_method": "hfd",
      "phone": "0501234567",
      "message": "Your order is on its way!",
      "success": true,
      "state": "sent",
      "provider": { "status": "OK", "message": "Delivered" },
      "created_at": "2026-07-29T09:00:00.000000Z",
      "updated_at": "2026-07-29T09:00:02.000000Z"
    }
  ],
  "links": { "first": "...", "last": "...", "prev": null, "next": "..." },
  "meta": { "current_page": 1, "per_page": 50, "total": 1 }
}

Fields

FieldTypeDescription
idintLog entry id.
shipment_idstring | absentThe related shipment UUID. Present only when the shipping relation is loaded.
shipping_methodstring | nullCarrier / shipping method the SMS relates to.
phonestringDestination phone.
messagestringMessage body sent.
successboolWhether the send succeeded.
statestring | nullInternal delivery state.
providerobjectProvider outcome: status and message.
created_atstringLog timestamp.
updated_atstringUpdate timestamp.

Internal fields (raw provider request/response payloads, internal FKs, soft-delete timestamp) are excluded from the public contract.

Errors

StatuscodeWhen
401unauthenticatedMissing or invalid client credentials.

GET /notifications/sms-templates

List all SMS templates belonging to the caller account. Auth: client credentials.

Parameters

None.

Example request

bash
curl --location 'https://app.shipos.co.il/api/v2/notifications/sms-templates' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies
const response = await fetch(
  'https://app.shipos.co.il/api/v2/notifications/sms-templates',
  {
    headers: {
      'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
      'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
      Accept: 'application/json',
    },
  },
)

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data: templates } = await response.json()

for (const template of templates) {
  console.log(template.id, template.shipping_method, template.active)
}
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$templates = json_decode(
    $client->get('notifications/sms-templates')->getBody()->getContents(),
    true,
)['data'];

foreach ($templates as $template) {
    echo $template['id'], ' ', $template['shipping_method'], ' ',
        var_export($template['active'], true), PHP_EOL;
}
php
<?php

use Illuminate\Support\Facades\Http;

$templates = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->get('https://app.shipos.co.il/api/v2/notifications/sms-templates')
    ->throw()
    ->json('data');

foreach ($templates as $template) {
    logger()->info($template['shipping_method'], ['active' => $template['active']]);
}
python
# pip install httpx
import os

import httpx

response = httpx.get(
    "https://app.shipos.co.il/api/v2/notifications/sms-templates",
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
)
response.raise_for_status()
templates = response.json()["data"]

for template in templates:
    print(template["id"], template["shipping_method"], template["active"])
go
package main

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

type smsTemplatesResponse struct {
	Data []struct {
		ID             int    `json:"id"`
		ShippingMethod string `json:"shipping_method"`
		Active         bool   `json:"active"`
	} `json:"data"`
}

func main() {
	req, _ := http.NewRequest("GET",
		"https://app.shipos.co.il/api/v2/notifications/sms-templates", nil)
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")

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

	var payload smsTemplatesResponse
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	for _, template := range payload.Data {
		fmt.Println(template.ID, template.ShippingMethod, template.Active)
	}
}
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 ShipOsSmsTemplates {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/notifications/sms-templates"))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .build();

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

        if (response.statusCode() != 200) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":[...]} — map with Jackson/Gson
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var payload = await http.GetFromJsonAsync<JsonDocument>("notifications/sms-templates")
    ?? throw new InvalidOperationException("Empty response");

foreach (var template in payload.RootElement.GetProperty("data").EnumerateArray())
{
    Console.WriteLine($"{template.GetProperty("id").GetInt32()} " +
        $"{template.GetProperty("shipping_method").GetString()} " +
        $"{template.GetProperty("active").GetBoolean()}");
}
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/notifications/sms-templates")
request = Net::HTTP::Get.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"

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

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

templates = JSON.parse(response.body).fetch("data")

templates.each do |template|
  puts "#{template["id"]} #{template["shipping_method"]} #{template["active"]}"
end
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload: Value = reqwest::Client::new()
        .get("https://app.shipos.co.il/api/v2/notifications/sms-templates")
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    if let Some(templates) = payload["data"].as_array() {
        for template in templates {
            println!(
                "{} {} {}",
                template["id"], template["shipping_method"], template["active"]
            );
        }
    }
    Ok(())
}

Response 200

json
{
  "data": [
    {
      "id": 15,
      "shipping_method": "hfd",
      "delivery_type": "delivered",
      "message": "Hi {name}, your order has arrived.",
      "active": true,
      "created_at": "2026-05-01T00:00:00.000000Z",
      "updated_at": "2026-07-10T00:00:00.000000Z"
    }
  ]
}

Fields

FieldTypeDescription
idintTemplate id (used as the {template} path param below).
shipping_methodstringCarrier / shipping method the template applies to.
delivery_typestring | nullDelivery stage the template fires for.
messagestringTemplate body.
activeboolWhether the template is enabled (maps to the model status).
created_atstringCreation timestamp.
updated_atstringUpdate timestamp.

Errors

StatuscodeWhen
401unauthenticatedMissing or invalid client credentials.

POST /notifications/sms-templates

Create or update an SMS template for the caller account (upsert). Returns 201. Auth: client credentials.

Parameters

Body

FieldTypeRequiredDescription
shipping_methodstringyesCarrier / shipping method the template applies to. Max 64 chars.
messagestringyesTemplate body. Max 1000 chars.

Example request

bash
curl --location --request POST 'https://app.shipos.co.il/api/v2/notifications/sms-templates' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
  "shipping_method": "hfd",
  "message": "Hi {name}, your order has arrived."
}'
js
// Node.js 18+ / browsers — no dependencies
const response = await fetch(
  'https://app.shipos.co.il/api/v2/notifications/sms-templates',
  {
    method: 'POST',
    headers: {
      'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
      'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      shipping_method: 'hfd',
      message: 'Hi {name}, your order has arrived.',
    }),
  },
)

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data: template } = await response.json()

console.log(template.id, template.shipping_method, template.active)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('notifications/sms-templates', [
    'json' => [
        'shipping_method' => 'hfd',
        'message' => 'Hi {name}, your order has arrived.',
    ],
]);

$template = json_decode($response->getBody()->getContents(), true)['data'];

echo $template['id'], ' ', $template['shipping_method'], PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$template = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->post('https://app.shipos.co.il/api/v2/notifications/sms-templates', [
        'shipping_method' => 'hfd',
        'message' => 'Hi {name}, your order has arrived.',
    ])
    ->throw()
    ->json('data');

logger()->info('Template upserted', ['id' => $template['id']]);
python
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://app.shipos.co.il/api/v2/notifications/sms-templates",
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
    json={
        "shipping_method": "hfd",
        "message": "Hi {name}, your order has arrived.",
    },
)
response.raise_for_status()
template = response.json()["data"]

print(template["id"], template["shipping_method"], template["active"])
go
package main

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

func main() {
	body, _ := json.Marshal(map[string]string{
		"shipping_method": "hfd",
		"message":         "Hi {name}, your order has arrived.",
	})

	req, _ := http.NewRequest("POST",
		"https://app.shipos.co.il/api/v2/notifications/sms-templates",
		bytes.NewReader(body))
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

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

	var payload struct {
		Data struct {
			ID             int    `json:"id"`
			ShippingMethod string `json:"shipping_method"`
			Active         bool   `json:"active"`
		} `json:"data"`
	}
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	fmt.Println(payload.Data.ID, payload.Data.ShippingMethod, payload.Data.Active)
}
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 ShipOsCreateSmsTemplate {
    public static void main(String[] args) throws Exception {
        String body = """
            {"shipping_method":"hfd","message":"Hi {name}, your order has arrived."}
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/notifications/sms-templates"))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

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

        if (response.statusCode() != 201) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":{...}} — map with Jackson/Gson
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var response = await http.PostAsJsonAsync("notifications/sms-templates", new
{
    shipping_method = "hfd",
    message = "Hi {name}, your order has arrived.",
});
response.EnsureSuccessStatusCode();

var payload = await response.Content.ReadFromJsonAsync<JsonDocument>()
    ?? throw new InvalidOperationException("Empty response");
var template = payload.RootElement.GetProperty("data");

Console.WriteLine($"{template.GetProperty("id").GetInt32()} " +
    $"{template.GetProperty("shipping_method").GetString()}");
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/notifications/sms-templates")
request = Net::HTTP::Post.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
  shipping_method: "hfd",
  message: "Hi {name}, your order has arrived.",
})

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

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

template = JSON.parse(response.body).fetch("data")

puts "#{template["id"]} #{template["shipping_method"]} #{template["active"]}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload: Value = reqwest::Client::new()
        .post("https://app.shipos.co.il/api/v2/notifications/sms-templates")
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .json(&serde_json::json!({
            "shipping_method": "hfd",
            "message": "Hi {name}, your order has arrived.",
        }))
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    let template = &payload["data"];
    println!("{} {}", template["id"], template["shipping_method"]);
    Ok(())
}

Response 201

json
{
  "data": {
    "id": 15,
    "shipping_method": "hfd",
    "delivery_type": "delivered",
    "message": "Hi {name}, your order has arrived.",
    "active": true,
    "created_at": "2026-07-29T10:00:00.000000Z",
    "updated_at": "2026-07-29T10:00:00.000000Z"
  }
}

Errors

StatuscodeWhen
401unauthenticatedMissing or invalid client credentials.
422validation_failedshipping_method or message missing or too long.

PUT /notifications/sms-templates/{template}/toggle

Flip a template's enabled state (active). Auth: client credentials.

Parameters

Path

FieldTypeRequiredDescription
templateintyesThe template id.

Example request

bash
curl --location --request PUT 'https://app.shipos.co.il/api/v2/notifications/sms-templates/15/toggle' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies
const templateId = 15

const response = await fetch(
  `https://app.shipos.co.il/api/v2/notifications/sms-templates/${templateId}/toggle`,
  {
    method: 'PUT',
    headers: {
      'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
      'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
      Accept: 'application/json',
    },
  },
)

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data: template } = await response.json()

console.log(template.id, template.active)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$templateId = 15;

$template = json_decode(
    $client->put("notifications/sms-templates/{$templateId}/toggle")
        ->getBody()->getContents(),
    true,
)['data'];

echo $template['id'], ' active: ', var_export($template['active'], true), PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$templateId = 15;

$template = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->put("https://app.shipos.co.il/api/v2/notifications/sms-templates/{$templateId}/toggle")
    ->throw()
    ->json('data');

logger()->info('Template toggled', ['active' => $template['active']]);
python
# pip install httpx
import os

import httpx

template_id = 15

response = httpx.put(
    f"https://app.shipos.co.il/api/v2/notifications/sms-templates/{template_id}/toggle",
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
)
response.raise_for_status()
template = response.json()["data"]

print(template["id"], template["active"])
go
package main

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

func main() {
	templateID := 15

	req, _ := http.NewRequest("PUT", fmt.Sprintf(
		"https://app.shipos.co.il/api/v2/notifications/sms-templates/%d/toggle",
		templateID), nil)
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")

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

	var payload struct {
		Data struct {
			ID     int  `json:"id"`
			Active bool `json:"active"`
		} `json:"data"`
	}
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	fmt.Println(payload.Data.ID, payload.Data.Active)
}
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 ShipOsToggleSmsTemplate {
    public static void main(String[] args) throws Exception {
        int templateId = 15;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/notifications/sms-templates/"
                + templateId + "/toggle"))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .PUT(HttpRequest.BodyPublishers.noBody())
            .build();

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

        if (response.statusCode() != 200) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":{...}} — map with Jackson/Gson
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var templateId = 15;

var response = await http.PutAsync($"notifications/sms-templates/{templateId}/toggle", null);
response.EnsureSuccessStatusCode();

var payload = await response.Content.ReadFromJsonAsync<JsonDocument>()
    ?? throw new InvalidOperationException("Empty response");
var template = payload.RootElement.GetProperty("data");

Console.WriteLine($"{template.GetProperty("id").GetInt32()} " +
    $"{template.GetProperty("active").GetBoolean()}");
ruby
require "net/http"
require "json"

template_id = 15

uri = URI("https://app.shipos.co.il/api/v2/notifications/sms-templates/#{template_id}/toggle")
request = Net::HTTP::Put.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"

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

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

template = JSON.parse(response.body).fetch("data")

puts "#{template["id"]} active: #{template["active"]}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let template_id = 15;

    let payload: Value = reqwest::Client::new()
        .put(format!(
            "https://app.shipos.co.il/api/v2/notifications/sms-templates/{template_id}/toggle"
        ))
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    let template = &payload["data"];
    println!("{} {}", template["id"], template["active"]);
    Ok(())
}

Response 200

json
{
  "data": {
    "id": 15,
    "shipping_method": "hfd",
    "delivery_type": "delivered",
    "message": "Hi {name}, your order has arrived.",
    "active": false,
    "created_at": "2026-05-01T00:00:00.000000Z",
    "updated_at": "2026-07-29T10:05:00.000000Z"
  }
}

Errors

StatuscodeWhen
401unauthenticatedMissing or invalid client credentials.
404not_foundNo template with that id belongs to the caller account.

DELETE /notifications/sms-templates/

Delete a template owned by the caller account. Auth: client credentials.

Parameters

Path

FieldTypeRequiredDescription
templateintyesThe template id.

Example request

bash
curl --location --request DELETE 'https://app.shipos.co.il/api/v2/notifications/sms-templates/15' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies
const templateId = 15

const response = await fetch(
  `https://app.shipos.co.il/api/v2/notifications/sms-templates/${templateId}`,
  {
    method: 'DELETE',
    headers: {
      'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
      'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
      Accept: 'application/json',
    },
  },
)

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data } = await response.json()

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

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$templateId = 15;

$result = json_decode(
    $client->delete("notifications/sms-templates/{$templateId}")
        ->getBody()->getContents(),
    true,
)['data'];

echo var_export($result['deleted'], true), PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$templateId = 15;

$result = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->delete("https://app.shipos.co.il/api/v2/notifications/sms-templates/{$templateId}")
    ->throw()
    ->json('data');

logger()->info('Template deleted', ['deleted' => $result['deleted']]);
python
# pip install httpx
import os

import httpx

template_id = 15

response = httpx.delete(
    f"https://app.shipos.co.il/api/v2/notifications/sms-templates/{template_id}",
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
)
response.raise_for_status()

print(response.json()["data"]["deleted"])
go
package main

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

func main() {
	templateID := 15

	req, _ := http.NewRequest("DELETE", fmt.Sprintf(
		"https://app.shipos.co.il/api/v2/notifications/sms-templates/%d",
		templateID), nil)
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")

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

	var payload struct {
		Data struct {
			Deleted bool `json:"deleted"`
		} `json:"data"`
	}
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	fmt.Println(payload.Data.Deleted)
}
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 ShipOsDeleteSmsTemplate {
    public static void main(String[] args) throws Exception {
        int templateId = 15;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/notifications/sms-templates/"
                + templateId))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .DELETE()
            .build();

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

        if (response.statusCode() != 200) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":{"deleted":true}}
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var templateId = 15;

var response = await http.DeleteAsync($"notifications/sms-templates/{templateId}");
response.EnsureSuccessStatusCode();

var payload = await response.Content.ReadFromJsonAsync<JsonDocument>()
    ?? throw new InvalidOperationException("Empty response");

Console.WriteLine(payload.RootElement.GetProperty("data")
    .GetProperty("deleted").GetBoolean());
ruby
require "net/http"
require "json"

template_id = 15

uri = URI("https://app.shipos.co.il/api/v2/notifications/sms-templates/#{template_id}")
request = Net::HTTP::Delete.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"

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

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

puts JSON.parse(response.body).dig("data", "deleted")
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let template_id = 15;

    let payload: Value = reqwest::Client::new()
        .delete(format!(
            "https://app.shipos.co.il/api/v2/notifications/sms-templates/{template_id}"
        ))
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    println!("{}", payload["data"]["deleted"]);
    Ok(())
}

Response 200

json
{ "data": { "deleted": true } }

Errors

StatuscodeWhen
401unauthenticatedMissing or invalid client credentials.
404not_foundNo template with that id belongs to the caller account.