Skip to content

Create your first shipment

This walkthrough takes you from a fresh set of API credentials to a created shipment with a printable label and live tracking. It uses the four core endpoints — Account, Licenses, Shipments, and Tracking — end to end.

Before you start

You need your API client credentials — a client_id and client_secret pair. Every authenticated request sends them as headers:

X-Client-Id: {client_id}
X-Client-Secret: {client_secret}
Accept: application/json

Requests with a body also send Content-Type: application/json. All examples use the base URL https://app.shipos.co.il/api/v2. See the Authentication guide for details.

Step 1 — Verify your credentials

Call GET /account. It returns the merchant account behind your credentials, with all carrier accounts (licenses) embedded — a successful response proves your headers are correct.

bash
curl --location 'https://app.shipos.co.il/api/v2/account' \
--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/account', {
  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: account } = await response.json()

console.log(account.company_name, '—', account.licenses.length, 'license(s)')
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',
    ],
]);

$account = json_decode(
    $client->get('account')->getBody()->getContents(),
    true,
)['data'];

echo $account['company_name'], ' — ', count($account['licenses']), ' license(s)', PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$account = 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/account')
    ->throw()
    ->json('data');

logger()->info($account['company_name'], ['licenses' => count($account['licenses'])]);
python
# pip install httpx
import os

import httpx

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

print(account["company_name"], "—", len(account["licenses"]), "license(s)")
go
package main

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

type accountResponse struct {
	Data struct {
		CompanyName string            `json:"company_name"`
		Licenses    []json.RawMessage `json:"licenses"`
	} `json:"data"`
}

func main() {
	req, _ := http.NewRequest("GET", "https://app.shipos.co.il/api/v2/account", 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 account accountResponse
	if err := json.NewDecoder(res.Body).Decode(&account); err != nil {
		panic(err)
	}

	fmt.Println(account.Data.CompanyName, "—", len(account.Data.Licenses), "license(s)")
}
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 VerifyCredentials {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/account"))
            .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>("account")
    ?? throw new InvalidOperationException("Empty response");
var account = payload.RootElement.GetProperty("data");

Console.WriteLine($"{account.GetProperty("company_name").GetString()} — " +
    $"{account.GetProperty("licenses").GetArrayLength()} license(s)");
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/account")
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)

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

puts "#{account["company_name"]}#{account["licenses"].size} license(s)"
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/account")
        .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 account = &payload["data"];
    let licenses = account["licenses"].as_array().map_or(0, Vec::len);
    println!("{} — {} license(s)", account["company_name"], licenses);
    Ok(())
}

A 200 comes back with the standard { "data": ... } envelope:

json
{
  "data": {
    "id": 1042,
    "name": "Dana Levi",
    "email": "dana@example.co.il",
    "phone": "0521234567",
    "locale": "he",
    "company_name": "Herzl Fashion Ltd",
    "enable_api": 1,
    "can_use_collection_points": true,
    "label_method": "A4",
    "label_type": null,
    "licenses": [ ... ]
  }
}

If you get a 401 instead, the credentials are wrong or missing — see Errors.

Step 2 — Find your license_key

Every shipment is created under one license (a carrier account). Write endpoints select it with the license_key body field; GET endpoints use a license_key query parameter. The value is the key field returned by GET /licenses:

bash
curl --location 'https://app.shipos.co.il/api/v2/licenses' \
--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/licenses', {
  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: licenses } = await response.json()

for (const license of licenses) {
  console.log(license.key, '→', license.company.carrier.name, license.is_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',
    ],
]);

$licenses = json_decode(
    $client->get('licenses')->getBody()->getContents(),
    true,
)['data'];

foreach ($licenses as $license) {
    echo $license['key'], ' → ', $license['company']['carrier']['name'], PHP_EOL;
}
php
<?php

use Illuminate\Support\Facades\Http;

$licenses = 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/licenses')
    ->throw()
    ->json('data');

foreach ($licenses as $license) {
    logger()->info($license['key'].' → '.$license['company']['carrier']['name']);
}
python
# pip install httpx
import os

import httpx

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

for lic in response.json()["data"]:
    print(lic["key"], "→", lic["company"]["carrier"]["name"], lic["is_active"])
go
package main

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

type licensesResponse struct {
	Data []struct {
		Key      string `json:"key"`
		IsActive bool   `json:"is_active"`
		Company  struct {
			Carrier struct {
				Name string `json:"name"`
			} `json:"carrier"`
		} `json:"company"`
	} `json:"data"`
}

func main() {
	req, _ := http.NewRequest("GET", "https://app.shipos.co.il/api/v2/licenses", 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 licensesResponse
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	for _, license := range payload.Data {
		fmt.Println(license.Key, "→", license.Company.Carrier.Name, license.IsActive)
	}
}
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 ListLicenses {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/licenses"))
            .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":[{"key":"a1b2c3d4e5f6",...}]}
    }
}
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>("licenses")
    ?? throw new InvalidOperationException("Empty response");

foreach (var license in payload.RootElement.GetProperty("data").EnumerateArray())
{
    var carrier = license.GetProperty("company").GetProperty("carrier");
    Console.WriteLine(
        $"{license.GetProperty("key").GetString()} → {carrier.GetProperty("name").GetString()}");
}
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/licenses")
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)

JSON.parse(response.body).fetch("data").each do |license|
  puts "#{license["key"]}#{license.dig("company", "carrier", "name")}"
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/licenses")
        .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(licenses) = payload["data"].as_array() {
        for license in licenses {
            println!(
                "{} → {}",
                license["key"], license["company"]["carrier"]["name"]
            );
        }
    }
    Ok(())
}
json
{
  "data": [
    {
      "id": 812,
      "key": "a1b2c3d4e5f6",            // ← this is your license_key
      "name": "HFD main account",
      "company": {
        "id": 3391,
        "name": "Baldar Logistics Ltd",
        "carrier": { "id": 4, "name": "HFD" }
      },
      "commitment_day": 3,
      "multiple_shipment": true,
      "is_active": true,
      "is_expired": false,
      "expires_at": "2027-01-31T00:00:00.000000Z",
      "settings": [ ... ]
    }
  ]
}

When can you omit license_key?

If your account has exactly one active license, it is used by default and you can leave license_key out. With more than one active license, omitting it returns a 422; an inactive, expired, or not-owned key returns a 403. Full rules in Licenses & license_key.

Step 3 — Create the shipment

POST /shipments takes a {ship_data, order} envelope:

  • ship_data — the carrier-facing payload: who to deliver to, where, and how. contact_name, contact_phone, type, return, and packages are required; street and city are required unless you set ship_data.pickup (a pickup-point delivery).
  • order — the order snapshot persisted alongside the shipment, including an order.shipping recipient block (required) and optional order_items.

For ship_data.type, 1 = regular delivery and 2 = collection (return); for ship_data.return, 1 = single trip and 2 = round trip. For a normal home delivery use type: "1", return: "1".

Send an Idempotency-Key header — any unique string per logical shipment (a UUID works well). If the request times out or your process crashes, retrying with the same key safely returns the already-created shipment instead of creating (and paying for) a duplicate. See Idempotency.

bash
curl --location 'https://app.shipos.co.il/api/v2/shipments' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f' \
--data '{
  "license_key": "a1b2c3d4e5f6",
  "ship_data": {
    "contact_name": "Dana Levi",
    "contact_phone": "0521234567",
    "contact_mail": "dana@example.co.il",
    "street": "Herzl",
    "number": "10",
    "city": "Tel Aviv",
    "entrance": "B",
    "floor": "3",
    "apartment": "12",
    "type": "1",
    "return": "1",
    "packages": 1,
    "note": "Leave with the doorman if not home"
  },
  "order": {
    "id": "1042",
    "number": "1042",
    "status": "processing",
    "currency": "ILS",
    "total": 249.90,
    "source": "api",
    "shipping": {
      "first_name": "Dana",
      "last_name": "Levi",
      "phone": "0521234567",
      "email": "dana@example.co.il",
      "address_1": "Herzl",
      "address_2": "10",
      "city": "Tel Aviv",
      "postcode": "6688312",
      "country": "IL"
    },
    "order_items": [
      {
        "sku": "TSHIRT-M-BLK",
        "name": "Cotton T-Shirt (M, Black)",
        "quantity": 2,
        "price": 89.95,
        "total": 179.90
      },
      {
        "sku": "SOCKS-3PK",
        "name": "Socks 3-Pack",
        "quantity": 1,
        "price": 70.00,
        "total": 70.00
      }
    ]
  }
}'
js
// Node.js 18+ / browsers — no dependencies
const res = await fetch('https://app.shipos.co.il/api/v2/shipments', {
  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',
    'Idempotency-Key': '4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f',
  },
  body: JSON.stringify({
    license_key: 'a1b2c3d4e5f6',
    ship_data: {
      contact_name: 'Dana Levi',
      contact_phone: '0521234567',
      contact_mail: 'dana@example.co.il',
      street: 'Herzl',
      number: '10',
      city: 'Tel Aviv',
      entrance: 'B',
      floor: '3',
      apartment: '12',
      type: '1',
      return: '1',
      packages: 1,
      note: 'Leave with the doorman if not home',
    },
    order: {
      id: '1042',
      number: '1042',
      status: 'processing',
      currency: 'ILS',
      total: 249.9,
      source: 'api',
      shipping: {
        first_name: 'Dana',
        last_name: 'Levi',
        phone: '0521234567',
        email: 'dana@example.co.il',
        address_1: 'Herzl',
        address_2: '10',
        city: 'Tel Aviv',
        postcode: '6688312',
        country: 'IL',
      },
      order_items: [
        { sku: 'TSHIRT-M-BLK', name: 'Cotton T-Shirt (M, Black)', quantity: 2, price: 89.95, total: 179.9 },
        { sku: 'SOCKS-3PK', name: 'Socks 3-Pack', quantity: 1, price: 70.0, total: 70.0 },
      ],
    },
  }),
})

const { data: shipment } = await res.json() // res.status === 201
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client(['base_uri' => 'https://app.shipos.co.il/api/v2/']);

$response = $client->post('shipments', [
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
        'Idempotency-Key' => '4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f',
    ],
    'json' => [
        'license_key' => 'a1b2c3d4e5f6',
        'ship_data' => [
            'contact_name' => 'Dana Levi',
            'contact_phone' => '0521234567',
            'contact_mail' => 'dana@example.co.il',
            'street' => 'Herzl',
            'number' => '10',
            'city' => 'Tel Aviv',
            'entrance' => 'B',
            'floor' => '3',
            'apartment' => '12',
            'type' => '1',
            'return' => '1',
            'packages' => 1,
            'note' => 'Leave with the doorman if not home',
        ],
        'order' => [
            'id' => '1042',
            'number' => '1042',
            'status' => 'processing',
            'currency' => 'ILS',
            'total' => 249.90,
            'source' => 'api',
            'shipping' => [
                'first_name' => 'Dana',
                'last_name' => 'Levi',
                'phone' => '0521234567',
                'email' => 'dana@example.co.il',
                'address_1' => 'Herzl',
                'address_2' => '10',
                'city' => 'Tel Aviv',
                'postcode' => '6688312',
                'country' => 'IL',
            ],
            'order_items' => [
                ['sku' => 'TSHIRT-M-BLK', 'name' => 'Cotton T-Shirt (M, Black)', 'quantity' => 2, 'price' => 89.95, 'total' => 179.90],
                ['sku' => 'SOCKS-3PK', 'name' => 'Socks 3-Pack', 'quantity' => 1, 'price' => 70.00, 'total' => 70.00],
            ],
        ],
    ],
]);

$shipment = json_decode((string) $response->getBody(), true)['data'];
php
<?php

use Illuminate\Support\Facades\Http;

$shipment = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
    'Idempotency-Key' => '4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f',
])
    ->acceptJson()
    ->post('https://app.shipos.co.il/api/v2/shipments', [
        'license_key' => 'a1b2c3d4e5f6',
        'ship_data' => [
            'contact_name' => 'Dana Levi',
            'contact_phone' => '0521234567',
            'contact_mail' => 'dana@example.co.il',
            'street' => 'Herzl',
            'number' => '10',
            'city' => 'Tel Aviv',
            'entrance' => 'B',
            'floor' => '3',
            'apartment' => '12',
            'type' => '1',
            'return' => '1',
            'packages' => 1,
            'note' => 'Leave with the doorman if not home',
        ],
        'order' => [
            'id' => '1042',
            'number' => '1042',
            'status' => 'processing',
            'currency' => 'ILS',
            'total' => 249.90,
            'source' => 'api',
            'shipping' => [
                'first_name' => 'Dana',
                'last_name' => 'Levi',
                'phone' => '0521234567',
                'email' => 'dana@example.co.il',
                'address_1' => 'Herzl',
                'address_2' => '10',
                'city' => 'Tel Aviv',
                'postcode' => '6688312',
                'country' => 'IL',
            ],
            'order_items' => [
                ['sku' => 'TSHIRT-M-BLK', 'name' => 'Cotton T-Shirt (M, Black)', 'quantity' => 2, 'price' => 89.95, 'total' => 179.90],
                ['sku' => 'SOCKS-3PK', 'name' => 'Socks 3-Pack', 'quantity' => 1, 'price' => 70.00, 'total' => 70.00],
            ],
        ],
    ])
    ->throw()
    ->json('data');
python
# pip install httpx
import os

import httpx

payload = {
    "license_key": "a1b2c3d4e5f6",
    "ship_data": {
        "contact_name": "Dana Levi",
        "contact_phone": "0521234567",
        "contact_mail": "dana@example.co.il",
        "street": "Herzl",
        "number": "10",
        "city": "Tel Aviv",
        "entrance": "B",
        "floor": "3",
        "apartment": "12",
        "type": "1",
        "return": "1",
        "packages": 1,
        "note": "Leave with the doorman if not home",
    },
    "order": {
        "id": "1042",
        "number": "1042",
        "status": "processing",
        "currency": "ILS",
        "total": 249.90,
        "source": "api",
        "shipping": {
            "first_name": "Dana",
            "last_name": "Levi",
            "phone": "0521234567",
            "email": "dana@example.co.il",
            "address_1": "Herzl",
            "address_2": "10",
            "city": "Tel Aviv",
            "postcode": "6688312",
            "country": "IL",
        },
        "order_items": [
            {"sku": "TSHIRT-M-BLK", "name": "Cotton T-Shirt (M, Black)", "quantity": 2, "price": 89.95, "total": 179.90},
            {"sku": "SOCKS-3PK", "name": "Socks 3-Pack", "quantity": 1, "price": 70.00, "total": 70.00},
        ],
    },
}

response = httpx.post(
    "https://app.shipos.co.il/api/v2/shipments",
    json=payload,
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
        "Idempotency-Key": "4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f",
    },
    timeout=60.0,
)
response.raise_for_status()  # 201
shipment = response.json()["data"]

print(shipment["uuid"], shipment["tracking_code"])
go
package main

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

func main() {
	payload := map[string]any{
		"license_key": "a1b2c3d4e5f6",
		"ship_data": map[string]any{
			"contact_name":  "Dana Levi",
			"contact_phone": "0521234567",
			"contact_mail":  "dana@example.co.il",
			"street":        "Herzl",
			"number":        "10",
			"city":          "Tel Aviv",
			"entrance":      "B",
			"floor":         "3",
			"apartment":     "12",
			"type":          "1",
			"return":        "1",
			"packages":      1,
			"note":          "Leave with the doorman if not home",
		},
		"order": map[string]any{
			"id":       "1042",
			"number":   "1042",
			"status":   "processing",
			"currency": "ILS",
			"total":    249.90,
			"source":   "api",
			"shipping": map[string]any{
				"first_name": "Dana",
				"last_name":  "Levi",
				"phone":      "0521234567",
				"email":      "dana@example.co.il",
				"address_1":  "Herzl",
				"address_2":  "10",
				"city":       "Tel Aviv",
				"postcode":   "6688312",
				"country":    "IL",
			},
			"order_items": []map[string]any{
				{"sku": "TSHIRT-M-BLK", "name": "Cotton T-Shirt (M, Black)", "quantity": 2, "price": 89.95, "total": 179.90},
				{"sku": "SOCKS-3PK", "name": "Socks 3-Pack", "quantity": 1, "price": 70.00, "total": 70.00},
			},
		},
	}

	body, _ := json.Marshal(payload)
	req, _ := http.NewRequest("POST", "https://app.shipos.co.il/api/v2/shipments", 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")
	req.Header.Set("Idempotency-Key", "4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f")

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

	var payloadOut struct {
		Data struct {
			UUID         string `json:"uuid"`
			TrackingCode string `json:"tracking_code"`
		} `json:"data"`
	}
	if err := json.NewDecoder(res.Body).Decode(&payloadOut); err != nil {
		panic(err)
	}

	fmt.Println(res.StatusCode, payloadOut.Data.UUID, payloadOut.Data.TrackingCode)
}
java
// Java 17+ — java.net.http, no dependencies (build JSON with Jackson/Gson in real code)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class CreateShipment {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "license_key": "a1b2c3d4e5f6",
              "ship_data": {
                "contact_name": "Dana Levi",
                "contact_phone": "0521234567",
                "contact_mail": "dana@example.co.il",
                "street": "Herzl",
                "number": "10",
                "city": "Tel Aviv",
                "entrance": "B",
                "floor": "3",
                "apartment": "12",
                "type": "1",
                "return": "1",
                "packages": 1,
                "note": "Leave with the doorman if not home"
              },
              "order": {
                "id": "1042",
                "number": "1042",
                "status": "processing",
                "currency": "ILS",
                "total": 249.90,
                "source": "api",
                "shipping": {
                  "first_name": "Dana",
                  "last_name": "Levi",
                  "phone": "0521234567",
                  "email": "dana@example.co.il",
                  "address_1": "Herzl",
                  "address_2": "10",
                  "city": "Tel Aviv",
                  "postcode": "6688312",
                  "country": "IL"
                },
                "order_items": [
                  {"sku": "TSHIRT-M-BLK", "name": "Cotton T-Shirt (M, Black)", "quantity": 2, "price": 89.95, "total": 179.90},
                  {"sku": "SOCKS-3PK", "name": "Socks 3-Pack", "quantity": 1, "price": 70.00, "total": 70.00}
                ]
              }
            }
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/shipments"))
            .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")
            .header("Idempotency-Key", "4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f")
            .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":{"uuid":...,"tracking_code":...}}
    }
}
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"));
http.DefaultRequestHeaders.Add("Idempotency-Key",
    "4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f");

var payload = new
{
    license_key = "a1b2c3d4e5f6",
    ship_data = new
    {
        contact_name = "Dana Levi",
        contact_phone = "0521234567",
        contact_mail = "dana@example.co.il",
        street = "Herzl",
        number = "10",
        city = "Tel Aviv",
        entrance = "B",
        floor = "3",
        apartment = "12",
        type = "1",
        @return = "1", // serialized as "return"
        packages = 1,
        note = "Leave with the doorman if not home",
    },
    order = new
    {
        id = "1042",
        number = "1042",
        status = "processing",
        currency = "ILS",
        total = 249.90m,
        source = "api",
        shipping = new
        {
            first_name = "Dana",
            last_name = "Levi",
            phone = "0521234567",
            email = "dana@example.co.il",
            address_1 = "Herzl",
            address_2 = "10",
            city = "Tel Aviv",
            postcode = "6688312",
            country = "IL",
        },
        order_items = new object[]
        {
            new { sku = "TSHIRT-M-BLK", name = "Cotton T-Shirt (M, Black)", quantity = 2, price = 89.95m, total = 179.90m },
            new { sku = "SOCKS-3PK", name = "Socks 3-Pack", quantity = 1, price = 70.00m, total = 70.00m },
        },
    },
};

var response = await http.PostAsJsonAsync("shipments", payload);
response.EnsureSuccessStatusCode(); // 201

var created = (await response.Content.ReadFromJsonAsync<JsonDocument>())!
    .RootElement.GetProperty("data");

Console.WriteLine($"{created.GetProperty("uuid").GetString()} " +
    $"{created.GetProperty("tracking_code").GetString()}");
ruby
require "net/http"
require "json"

payload = {
  license_key: "a1b2c3d4e5f6",
  ship_data: {
    contact_name: "Dana Levi",
    contact_phone: "0521234567",
    contact_mail: "dana@example.co.il",
    street: "Herzl",
    number: "10",
    city: "Tel Aviv",
    entrance: "B",
    floor: "3",
    apartment: "12",
    type: "1",
    return: "1",
    packages: 1,
    note: "Leave with the doorman if not home"
  },
  order: {
    id: "1042",
    number: "1042",
    status: "processing",
    currency: "ILS",
    total: 249.90,
    source: "api",
    shipping: {
      first_name: "Dana",
      last_name: "Levi",
      phone: "0521234567",
      email: "dana@example.co.il",
      address_1: "Herzl",
      address_2: "10",
      city: "Tel Aviv",
      postcode: "6688312",
      country: "IL"
    },
    order_items: [
      { sku: "TSHIRT-M-BLK", name: "Cotton T-Shirt (M, Black)", quantity: 2, price: 89.95, total: 179.90 },
      { sku: "SOCKS-3PK", name: "Socks 3-Pack", quantity: 1, price: 70.00, total: 70.00 }
    ]
  }
}

uri = URI("https://app.shipos.co.il/api/v2/shipments")
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["Idempotency-Key"] = "4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f"
request.body = JSON.dump(payload)

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

raise "ShipOS error: #{response.body}" unless response.code == "201"

shipment = JSON.parse(response.body).fetch("data")
puts shipment["uuid"], shipment["tracking_code"]
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 payload = json!({
        "license_key": "a1b2c3d4e5f6",
        "ship_data": {
            "contact_name": "Dana Levi",
            "contact_phone": "0521234567",
            "contact_mail": "dana@example.co.il",
            "street": "Herzl",
            "number": "10",
            "city": "Tel Aviv",
            "entrance": "B",
            "floor": "3",
            "apartment": "12",
            "type": "1",
            "return": "1",
            "packages": 1,
            "note": "Leave with the doorman if not home"
        },
        "order": {
            "id": "1042",
            "number": "1042",
            "status": "processing",
            "currency": "ILS",
            "total": 249.90,
            "source": "api",
            "shipping": {
                "first_name": "Dana",
                "last_name": "Levi",
                "phone": "0521234567",
                "email": "dana@example.co.il",
                "address_1": "Herzl",
                "address_2": "10",
                "city": "Tel Aviv",
                "postcode": "6688312",
                "country": "IL"
            },
            "order_items": [
                {"sku": "TSHIRT-M-BLK", "name": "Cotton T-Shirt (M, Black)", "quantity": 2, "price": 89.95, "total": 179.90},
                {"sku": "SOCKS-3PK", "name": "Socks 3-Pack", "quantity": 1, "price": 70.00, "total": 70.00}
            ]
        }
    });

    let created: Value = reqwest::Client::new()
        .post("https://app.shipos.co.il/api/v2/shipments")
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .header("Idempotency-Key", "4f9d2c6a-7b1e-4e3a-9c5d-8a0b1c2d3e4f")
        .json(&payload)
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    let shipment = &created["data"];
    println!("{} {}", shipment["uuid"], shipment["tracking_code"]);
    Ok(())
}

A 201 Created returns the full shipment object:

json
{
  "data": {
    "uuid": "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88",
    "tracking_code": "66747921",
    "carrier": { "id": 4, "name": "HFD" },
    "status": null,
    "service_type": "1",
    "is_active": true,
    "recipient": {
      "name": "Dana Levi",
      "phone": "0521234567",
      "company": null,
      "address": {
        "street": "Herzl",
        "number": "10",
        "city": "Tel Aviv",
        "state": null,
        "zip": "6688312",
        "country": "IL"
      }
    },
    "pickup_point_id": null,
    "packages": 1,
    "cod": null,
    "order": { "id": "1042", "number": "1042" },
    "references": { "external_id": null },
    "short_tracking_code": null,
    "label_generated": false,
    "collection_status": null,
    "collected_at": null,
    "ready_at": null,
    "created_at": "2026-07-29T09:14:00.000000Z",
    "updated_at": "2026-07-29T09:14:00.000000Z"
  }
}

Persist these fields on your side:

FieldWhy
uuidThe shipment's API identifier — needed for every follow-up call (/shipments/{shipment}, /status, /label, /cancel).
tracking_codeThe carrier tracking code. Also the code for the public tracking endpoint, and what your customer sees.
short_tracking_codeShort code used in pickup-point flows (may be null for home deliveries).
label_generatedWhether a label has already been rendered — pair with Step 4.

Step 4 — Get the label

GET /shipments/{shipment}/label returns label metadata with a download URL — not the PDF bytes themselves:

bash
curl --location 'https://app.shipos.co.il/api/v2/shipments/9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88/label' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies
const uuid = '9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88'

const response = await fetch(
  `https://app.shipos.co.il/api/v2/shipments/${uuid}/label`,
  {
    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: label } = await response.json()

console.log(label.format, label.url) // fetch label.url to download the PDF
php
<?php
// composer require guzzlehttp/guzzle

$uuid = '9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88';

$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',
    ],
]);

$label = json_decode(
    $client->get("shipments/{$uuid}/label")->getBody()->getContents(),
    true,
)['data'];

echo $label['format'], ' ', $label['url'], PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$uuid = '9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88';

$label = 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/shipments/{$uuid}/label")
    ->throw()
    ->json('data');

// Download the PDF bytes — the label URL needs no credentials
$pdf = Http::get($label['url'])->throw()->body();
python
# pip install httpx
import os

import httpx

uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"

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

pdf = httpx.get(label["url"], follow_redirects=True).content
print(label["format"], len(pdf), "bytes")
go
package main

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

type labelResponse struct {
	Data struct {
		ShipmentID string `json:"shipment_id"`
		Format     string `json:"format"`
		URL        string `json:"url"`
	} `json:"data"`
}

func main() {
	uuid := "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"
	url := fmt.Sprintf("https://app.shipos.co.il/api/v2/shipments/%s/label", uuid)

	req, _ := http.NewRequest("GET", url, 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 label labelResponse
	if err := json.NewDecoder(res.Body).Decode(&label); err != nil {
		panic(err)
	}

	fmt.Println(label.Data.Format, label.Data.URL)
}
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 GetLabel {
    public static void main(String[] args) throws Exception {
        String uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88";

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/shipments/" + uuid + "/label"))
            .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":{"format":"pdf","url":...}}
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

var uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88";

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>($"shipments/{shipment}/label")
    ?? throw new InvalidOperationException("Empty response");
var label = payload.RootElement.GetProperty("data");

Console.WriteLine($"{label.GetProperty("format").GetString()} " +
    $"{label.GetProperty("url").GetString()}");
ruby
require "net/http"
require "json"

uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"

uri = URI("https://app.shipos.co.il/api/v2/shipments/#{uuid}/label")
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)

label = JSON.parse(response.body).fetch("data")
puts label["format"], label["url"]
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 uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88";

    let payload: Value = reqwest::Client::new()
        .get(format!(
            "https://app.shipos.co.il/api/v2/shipments/{shipment}/label"
        ))
        .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 label = &payload["data"];
    println!("{} {}", label["format"], label["url"]);
    Ok(())
}
json
{
  "data": {
    "shipment_id": "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88",
    "format": "pdf",
    "url": "https://app.shipos.co.il/shipping/label/9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"
  }
}

Fetch url to download the PDF label. Note the URL lives outside /api/v2 and does not require the client-credential headers — the capability is knowing the shipment uuid, so treat the link itself as sensitive.

Printing many labels at once

POST /shipments/labels with a list of uuids returns one combined-PDF URL for all of them — see the Shipments reference.

Step 5 — Track it

Two options, depending on who is asking.

Authenticated (your backend): GET /shipments/{shipment}/status re-fetches the status from the carrier and returns the refreshed full shipment object (same shape as Step 3's response, with status filled in):

bash
curl --location 'https://app.shipos.co.il/api/v2/shipments/9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88/status' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies
const uuid = '9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88'

const response = await fetch(
  `https://app.shipos.co.il/api/v2/shipments/${uuid}/status`,
  {
    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: shipment } = await response.json()

console.log(shipment.status?.description, shipment.status?.is_delivered)
php
<?php
// composer require guzzlehttp/guzzle

$uuid = '9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88';

$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',
    ],
]);

$shipment = json_decode(
    $client->get("shipments/{$uuid}/status")->getBody()->getContents(),
    true,
)['data'];

echo $shipment['status']['description'] ?? 'no status yet', PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$uuid = '9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88';

$shipment = 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/shipments/{$uuid}/status")
    ->throw()
    ->json('data');

logger()->info('Shipment status', [
    'code' => data_get($shipment, 'status.code'),
    'delivered' => data_get($shipment, 'status.is_delivered'),
]);
python
# pip install httpx
import os

import httpx

uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"

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

status = shipment.get("status") or {}
print(status.get("description"), status.get("is_delivered"))
go
package main

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

type statusResponse struct {
	Data struct {
		TrackingCode string `json:"tracking_code"`
		Status       *struct {
			Code        string `json:"code"`
			Description string `json:"description"`
			IsDelivered bool   `json:"is_delivered"`
		} `json:"status"`
	} `json:"data"`
}

func main() {
	uuid := "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"
	url := fmt.Sprintf("https://app.shipos.co.il/api/v2/shipments/%s/status", uuid)

	req, _ := http.NewRequest("GET", url, 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 statusResponse
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	if payload.Data.Status != nil {
		fmt.Println(payload.Data.Status.Description, payload.Data.Status.IsDelivered)
	}
}
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 RefreshStatus {
    public static void main(String[] args) throws Exception {
        String uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88";

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/shipments/" + uuid + "/status"))
            .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":{...,"status":{...}}}
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

var uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88";

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>($"shipments/{shipment}/status")
    ?? throw new InvalidOperationException("Empty response");
var shipment = payload.RootElement.GetProperty("data");

if (shipment.TryGetProperty("status", out var status) &&
    status.ValueKind == JsonValueKind.Object)
{
    Console.WriteLine($"{status.GetProperty("description").GetString()} " +
        $"{status.GetProperty("is_delivered").GetBoolean()}");
}
ruby
require "net/http"
require "json"

uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"

uri = URI("https://app.shipos.co.il/api/v2/shipments/#{uuid}/status")
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)

shipment = JSON.parse(response.body).fetch("data")
puts shipment.dig("status", "description"), shipment.dig("status", "is_delivered")
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 uuid = "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88";

    let payload: Value = reqwest::Client::new()
        .get(format!(
            "https://app.shipos.co.il/api/v2/shipments/{shipment}/status"
        ))
        .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 status = &payload["data"]["status"];
    println!("{} {}", status["description"], status["is_delivered"]);
    Ok(())
}

PII-free status: GET /tracking/{shipping_code} takes the tracking_code and returns a status view with no recipient details. It requires your client credentials, so call it from your server — never from a browser, where the secret would be exposed. To show buyers their own tracking, render it from your backend or link them to the tracker in the pickup SMS:

bash
curl --location 'https://app.shipos.co.il/api/v2/tracking/66747921' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies, no credentials
const trackingCode = '66747921'

const response = await fetch(
  `https://app.shipos.co.il/api/v2/tracking/${trackingCode}`,
  { headers: { Accept: 'application/json' } },
)

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

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

console.log(tracking.status.description, tracking.status.is_delivered)
php
<?php
// composer require guzzlehttp/guzzle — public endpoint, no credentials

$trackingCode = '66747921';

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

$tracking = json_decode(
    $client->get("tracking/{$trackingCode}")->getBody()->getContents(),
    true,
)['data'];

echo $tracking['status']['description'], PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$trackingCode = '66747921';

// Public endpoint — no client credentials
$tracking = Http::acceptJson()
    ->get("https://app.shipos.co.il/api/v2/tracking/{$trackingCode}")
    ->throw()
    ->json('data');

logger()->info('Tracking', [
    'code' => data_get($tracking, 'status.code'),
    'delivered' => data_get($tracking, 'status.is_delivered'),
]);
python
# pip install httpx — public endpoint, no credentials
import httpx

tracking_code = "66747921"

response = httpx.get(
    f"https://app.shipos.co.il/api/v2/tracking/{tracking_code}",
    headers={"Accept": "application/json"},
)
response.raise_for_status()
tracking = response.json()["data"]

print(tracking["status"]["description"], tracking["status"]["is_delivered"])
go
package main

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

type trackingResponse struct {
	Data struct {
		TrackingCode string `json:"tracking_code"`
		Status       struct {
			Code        string `json:"code"`
			Description string `json:"description"`
			IsDelivered bool   `json:"is_delivered"`
		} `json:"status"`
	} `json:"data"`
}

func main() {
	trackingCode := "66747921"
	url := fmt.Sprintf("https://app.shipos.co.il/api/v2/tracking/%s", trackingCode)

	// Public endpoint — no credential headers
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Accept", "application/json")

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

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

	fmt.Println(payload.Data.Status.Description, payload.Data.Status.IsDelivered)
}
java
// Java 17+ — java.net.http, public endpoint, no credentials
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class PublicTracking {
    public static void main(String[] args) throws Exception {
        String trackingCode = "66747921";

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/tracking/" + trackingCode))
            .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":{"tracking_code":...,"status":{...}}}
    }
}
csharp
// .NET 8+ — System.Net.Http.Json, public endpoint, no credentials
using System.Net.Http.Json;
using System.Text.Json;

var trackingCode = "66747921";

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};

var payload = await http.GetFromJsonAsync<JsonDocument>($"tracking/{trackingCode}")
    ?? throw new InvalidOperationException("Empty response");
var status = payload.RootElement.GetProperty("data").GetProperty("status");

Console.WriteLine($"{status.GetProperty("description").GetString()} " +
    $"{status.GetProperty("is_delivered").GetBoolean()}");
ruby
require "net/http"
require "json"

tracking_code = "66747921"

# Public endpoint — no credential headers
uri = URI("https://app.shipos.co.il/api/v2/tracking/#{tracking_code}")
request = Net::HTTP::Get.new(uri)
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)

tracking = JSON.parse(response.body).fetch("data")
puts tracking.dig("status", "description"), tracking.dig("status", "is_delivered")
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 tracking_code = "66747921";

    // Public endpoint — no credential headers
    let payload: Value = reqwest::Client::new()
        .get(format!(
            "https://app.shipos.co.il/api/v2/tracking/{tracking_code}"
        ))
        .header("Accept", "application/json")
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    let status = &payload["data"]["status"];
    println!("{} {}", status["description"], status["is_delivered"]);
    Ok(())
}
json
{
  "data": {
    "tracking_code": "66747921",
    "carrier": { "id": 4, "name": "HFD" },
    "status": {
      "code": "3",
      "description": "Out for delivery",
      "is_delivered": false
    },
    "is_active": true,
    "updated_at": "2026-07-30T07:41:12.000000Z"
  }
}

See the Tracking reference for details.

What can go wrong

All errors use the envelope { "error": { "code", "message", "status", "details" } }. The ones you will meet on this flow:

HTTPerror.codeMeaningWhat to do
422validation_failedThe body failed validation — a required ship_data/order field is missing or malformed, or license_key was omitted while the account has multiple active licenses.Fix the fields listed in error.details and resend.
403package_limit_reachedYour subscription's shipment quota is exhausted. The carrier was not called.Upgrade the package / contact support; retrying will not help.
424carrier_errorThe carrier rejected the shipment (bad address, carrier-side outage, ...). Nothing was persisted.Inspect error.message, correct the data, and retry — with a new Idempotency-Key if the body changed.
409idempotency_key_conflictThe same Idempotency-Key was reused with a different request body.Use one key per logical shipment; generate a fresh key for a genuinely new shipment.
409duplicate_requestA shipment for this exact request is already being created concurrently.Wait a moment and retry with the same key — you'll get the completed shipment back.

Full catalogue in the Errors guide; idempotency semantics in Idempotency.