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/jsonRequests 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.
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'// 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
// 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
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'])]);# 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)")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 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
}
}// .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)");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)"// [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:
{
"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:
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'// 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
// 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
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']);
}# 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"])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 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",...}]}
}
}// .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()}");
}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// [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(())
}{
"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, andpackagesare required;streetandcityare required unless you setship_data.pickup(a pickup-point delivery).order— the order snapshot persisted alongside the shipment, including anorder.shippingrecipient block (required) and optionalorder_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.
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
}
]
}
}'// 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
// 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
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');# 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"])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 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":...}}
}
}// .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()}");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"]// [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:
{
"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:
| Field | Why |
|---|---|
uuid | The shipment's API identifier — needed for every follow-up call (/shipments/{shipment}, /status, /label, /cancel). |
tracking_code | The carrier tracking code. Also the code for the public tracking endpoint, and what your customer sees. |
short_tracking_code | Short code used in pickup-point flows (may be null for home deliveries). |
label_generated | Whether 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:
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'// 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
// 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
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();# 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")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 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":...}}
}
}// .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()}");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"]// [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(())
}{
"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):
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'// 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
// 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
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'),
]);# 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"))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 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":{...}}}
}
}// .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()}");
}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")// [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:
curl --location 'https://app.shipos.co.il/api/v2/tracking/66747921' \
--header 'Accept: application/json'// 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
// 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
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'),
]);# 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"])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 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":{...}}}
}
}// .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()}");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")// [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(())
}{
"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:
| HTTP | error.code | Meaning | What to do |
|---|---|---|---|
422 | validation_failed | The 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. |
403 | package_limit_reached | Your subscription's shipment quota is exhausted. The carrier was not called. | Upgrade the package / contact support; retrying will not help. |
424 | carrier_error | The 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. |
409 | idempotency_key_conflict | The 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. |
409 | duplicate_request | A 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.