משלוחים בכמויות גדולות עם אצוות (Batches)
מדריך זה מייבא סט גדול של הזמנות כמשלוחים בקריאה אחת, מבצע polling לאצווה עד שהיא מסתיימת, מטפל בפריטים שנכשלו, ומושך CSV של התוצאות. עמודי ייחוס: Batches, Exports, Shipments.
אצוות לעומת POST /shipments סדרתי
POST /shipments סדרתי | POST /batches | |
|---|---|---|
| תגובה | 201 סינכרוני עם המשלוח שנוצר (או שגיאה) לכל בקשה | 202 מיידי עם אצווה ל-polling; המשלוחים נוצרים על ידי queue workers |
| נפח | מתאים לקומץ משלוחים | עד 2000 פריטים לבקשה, מעובדים במקטעים מקביליים |
| בידוד כשלים | בקשה אחת = תוצאה אחת | פריט שגוי הופך לפריט failed; הוא לעולם לא דוחה את שאר האצווה |
| רישיונות | רישיון אחד לבקשה | רישיון ברירת מחדל לאצווה, בתוספת דריסה אופציונלית של license_key לכל פריט |
כל פריט באצווה נוצר דרך אותו נתיב מוגן כמו POST /shipments בודד (אותם כללי ולידציה, אותו מנעול הגנה מפני כפילויות, אותם מתרגמי חברות שילוח), כך שההתנהגות לכל פריט זהה — פשוט מקבלים את התוצאות באופן אסינכרוני.
השתמשו באצווה כשאתם מייבאים CSV, מסנכרנים את הזמנות הלילה, או מבצעים מיגרציה לחנות. השתמשו ביצירות בודדות כשהקורא זקוק לקוד המעקב באותו סבב HTTP.
שלב 1 — יצירת האצווה
POST /batches מקבל כל אחת משתי צורות המעטפת — {"shipments": [ ... ]} או מערך JSON חשוף ברמה העליונה (הוא מנורמל פנימית ל-shipments). כל פריט הוא גוף משלוח מלא — אותו payload של {ship_data, order} כמו ב-POST /shipments — והוא רשאי לשאת license_key משלו כדי לכוון לרישיון אחר שבבעלותכם (הוא חייב להשתייך לחשבון שלכם, אחרת הפריט הזה נכשל עם forbidden; פריטים בלעדיו משתמשים ברישיון ברירת המחדל של האצווה).
curl --location 'https://app.shipos.co.il/api/v2/batches' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"license_key": "{license_key}",
"shipments": [
{
"ship_data": {
"contact_name": "דנה לוי",
"contact_phone": "0521234567",
"street": "הרצל",
"number": "10",
"city": "תל אביב",
"type": "1",
"return": "1",
"packages": 1
},
"order": {
"id": "1042",
"number": "1042",
"shipping": { "first_name": "דנה", "last_name": "לוי", "phone": "0521234567", "city": "תל אביב" }
}
},
{
"license_key": "{other_license_key}",
"ship_data": {
"contact_name": "יוסי כהן",
"contact_phone": "0549876543",
"street": "ויצמן",
"number": "14",
"city": "כפר סבא",
"type": "1",
"return": "1",
"packages": 2
},
"order": {
"id": "1043",
"number": "1043",
"shipping": { "first_name": "יוסי", "last_name": "כהן", "phone": "0549876543", "city": "כפר סבא" }
}
}
]
}'// Node.js 18+ / דפדפנים — ללא תלויות
const res = await fetch('https://app.shipos.co.il/api/v2/batches', {
method: 'POST',
headers: {
'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
license_key: licenseKey,
shipments: shipmentPayloads, // עד 2000 פריטי {ship_data, order}
}),
})
const { data: batch } = await res.json() // res.status === 202
const batchId = batch.id // בצעו polling ל-UUID הזה<?php
// composer require guzzlehttp/guzzle
$client = new \GuzzleHttp\Client(['base_uri' => 'https://app.shipos.co.il/api/v2/']);
$response = $client->post('batches', [
'headers' => [
'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
'Accept' => 'application/json',
],
'json' => [
'license_key' => $licenseKey,
'shipments' => $shipmentPayloads, // עד 2000 פריטי {ship_data, order}
],
]);
$batch = json_decode((string) $response->getBody(), true)['data'];
$batchId = $batch['id']; // בצעו polling ל-UUID הזה<?php
use Illuminate\Support\Facades\Http;
$batch = Http::withHeaders([
'X-Client-Id' => config('services.shipos.client_id'),
'X-Client-Secret' => config('services.shipos.client_secret'),
])
->acceptJson()
->post('https://app.shipos.co.il/api/v2/batches', [
'license_key' => $licenseKey,
'shipments' => $shipmentPayloads, // עד 2000 פריטי {ship_data, order}
])
->throw()
->json('data');
$batchId = $batch['id']; // בצעו polling ל-UUID הזה# pip install httpx
import os
import httpx
response = httpx.post(
"https://app.shipos.co.il/api/v2/batches",
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
},
json={
"license_key": license_key,
"shipments": shipment_payloads, # עד 2000 פריטי {ship_data, order}
},
)
response.raise_for_status()
batch = response.json()["data"] # response.status_code == 202
batch_id = batch["id"] # בצעו polling ל-UUID הזהpackage main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"license_key": os.Getenv("SHIPOS_LICENSE_KEY"),
"shipments": shipmentPayloads, // עד 2000 פריטי {ship_data, order}
})
req, _ := http.NewRequest("POST", "https://app.shipos.co.il/api/v2/batches", bytes.NewReader(body))
req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var payload struct {
Data struct {
ID string `json:"id"`
} `json:"data"`
}
json.NewDecoder(res.Body).Decode(&payload)
fmt.Println("poll batch", payload.Data.ID) // res.StatusCode == 202
}// Java 17+ — java.net.http, ללא תלויות (סריאליזציה של הפריטים עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CreateBatch {
public static void main(String[] args) throws Exception {
String shipments = shipmentPayloadsAsJson(); // עד 2000 פריטי {ship_data, order}
String body = "{\"license_key\":\"" + System.getenv("SHIPOS_LICENSE_KEY")
+ "\",\"shipments\":" + shipments + "}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.shipos.co.il/api/v2/batches"))
.header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
.header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body()); // 202 {"data":{"id":"<uuid>", ...}}
}
}// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;
using var http = new HttpClient
{
BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));
var response = await http.PostAsJsonAsync("batches", new
{
license_key = Environment.GetEnvironmentVariable("SHIPOS_LICENSE_KEY"),
shipments = shipmentPayloads, // עד 2000 פריטי {ship_data, order}
});
response.EnsureSuccessStatusCode(); // 202
var payload = await response.Content.ReadFromJsonAsync<JsonDocument>();
var batchId = payload!.RootElement.GetProperty("data").GetProperty("id").GetString();
Console.WriteLine($"poll batch {batchId}");require "net/http"
require "json"
uri = URI("https://app.shipos.co.il/api/v2/batches")
request = Net::HTTP::Post.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = JSON.dump(
license_key: ENV.fetch("SHIPOS_LICENSE_KEY"),
shipments: shipment_payloads, # עד 2000 פריטי {ship_data, order}
)
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)
batch = JSON.parse(response.body).fetch("data") # 202
batch_id = batch["id"] # בצעו polling ל-UUID הזה// [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 shipment_payloads = json!([/* עד 2000 פריטי {ship_data, order} */]);
let payload: Value = reqwest::Client::new()
.post("https://app.shipos.co.il/api/v2/batches")
.header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
.header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
.header("Accept", "application/json")
.json(&json!({
"license_key": std::env::var("SHIPOS_LICENSE_KEY")?,
"shipments": shipment_payloads,
}))
.send()
.await?
.error_for_status()?
.json()
.await?;
println!("poll batch {}", payload["data"]["id"]); // 202
Ok(())
}תגובת 202 Accepted — עדיין לא נוצר דבר:
{
"data": {
"id": "9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55",
"status": "queued",
"summary": {
"total": 2,
"created": 0,
"failed": 0,
"pending": 2
},
"created_at": "2026-07-29T08:00:00.000000Z",
"updated_at": "2026-07-29T08:00:00.000000Z"
}
}ולידציית פריטים נדחית לשלב העיבוד
קריאת היצירה מאמתת רק את המעטפת (1–2000 פריטים). גוף כל פריט מאומת במהלך העיבוד — פריט לא תקין יופיע מאוחר יותר כפריט failed עם code: "validation_failed", לא כ-422 בקריאת היצירה.
שלב 2 — Polling לאצווה
בצעו polling ל-GET /batches/{uuid} עד ש-status הוא completed. סטטוסים:
| סטטוס | משמעות |
|---|---|
queued | נוצרה; אף פריט טרם הוכרע. |
processing | חלק מהפריטים הוכרעו, אחרים עדיין ממתינים. |
completed | כל פריט הוא created או failed. |
curl --location 'https://app.shipos.co.il/api/v2/batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55?license_key={license_key}' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'// Node.js 18+ / דפדפנים — ללא תלויות
const batchId = '9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55'
const url = `https://app.shipos.co.il/api/v2/batches/${batchId}?license_key=${licenseKey}`
const headers = {
'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
Accept: 'application/json',
}
let batch
do {
await new Promise((resolve) => setTimeout(resolve, 3000))
const res = await fetch(url, { headers })
batch = (await res.json()).data
const { total, created, failed } = batch.summary
console.log(`${created + failed}/${total}`)
} while (batch.status !== 'completed')<?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',
],
]);
$batchId = '9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55';
do {
sleep(3);
$batch = json_decode((string) $client->get("batches/{$batchId}", [
'query' => ['license_key' => $licenseKey],
])->getBody(), true)['data'];
$summary = $batch['summary'];
echo $summary['created'] + $summary['failed'], '/', $summary['total'], PHP_EOL;
} while ($batch['status'] !== 'completed');<?php
use Illuminate\Support\Facades\Http;
$batchId = '9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55';
do {
sleep(3);
$batch = 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/batches/{$batchId}", [
'license_key' => $licenseKey,
])
->throw()
->json('data');
logger()->info($batch['status'], $batch['summary']);
} while ($batch['status'] !== 'completed');# pip install httpx
import os
import time
import httpx
batch_id = "9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55"
client = httpx.Client(
base_url="https://app.shipos.co.il/api/v2",
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
},
)
while True:
time.sleep(3)
response = client.get(f"/batches/{batch_id}", params={"license_key": license_key})
response.raise_for_status()
batch = response.json()["data"]
summary = batch["summary"]
print(f"{summary['created'] + summary['failed']}/{summary['total']}")
if batch["status"] == "completed":
breakpackage main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
type batchResponse struct {
Data struct {
Status string `json:"status"`
Summary map[string]int `json:"summary"`
} `json:"data"`
}
func main() {
url := "https://app.shipos.co.il/api/v2/batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55" +
"?license_key=" + os.Getenv("SHIPOS_LICENSE_KEY")
for {
time.Sleep(3 * time.Second)
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)
}
var batch batchResponse
json.NewDecoder(res.Body).Decode(&batch)
res.Body.Close()
s := batch.Data.Summary
fmt.Printf("%d/%d\n", s["created"]+s["failed"], s["total"])
if batch.Data.Status == "completed" {
return
}
}
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class PollBatch {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.shipos.co.il/api/v2/batches/"
+ "9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55?license_key="
+ System.getenv("SHIPOS_LICENSE_KEY")))
.header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
.header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
.header("Accept", "application/json")
.build();
String body;
do {
Thread.sleep(3_000);
body = client.send(request, HttpResponse.BodyHandlers.ofString()).body();
System.out.println(body); // {"data":{"status":"...","summary":{...}}}
} while (!body.contains("\"status\":\"completed\""));
}
}// .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 licenseKey = Environment.GetEnvironmentVariable("SHIPOS_LICENSE_KEY");
var url = $"batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55?license_key={licenseKey}";
JsonElement batch;
do
{
await Task.Delay(3_000);
var payload = await http.GetFromJsonAsync<JsonDocument>(url);
batch = payload!.RootElement.GetProperty("data");
var summary = batch.GetProperty("summary");
Console.WriteLine($"{summary.GetProperty("created").GetInt32()
+ summary.GetProperty("failed").GetInt32()}/{summary.GetProperty("total").GetInt32()}");
} while (batch.GetProperty("status").GetString() != "completed");require "net/http"
require "json"
uri = URI("https://app.shipos.co.il/api/v2/batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55")
uri.query = URI.encode_www_form(license_key: ENV.fetch("SHIPOS_LICENSE_KEY"))
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"
loop do
sleep 3
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
batch = JSON.parse(response.body).fetch("data")
summary = batch["summary"]
puts "#{summary["created"] + summary["failed"]}/#{summary["total"]}"
break if batch["status"] == "completed"
end// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let url = "https://app.shipos.co.il/api/v2/batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55";
loop {
tokio::time::sleep(Duration::from_secs(3)).await;
let payload: Value = client
.get(url)
.query(&[("license_key", std::env::var("SHIPOS_LICENSE_KEY")?)])
.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 batch = &payload["data"];
println!("{} {}", batch["status"], batch["summary"]);
if batch["status"] == "completed" {
return Ok(());
}
}
}{
"data": {
"id": "9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55",
"status": "completed",
"summary": {
"total": 2,
"created": 1,
"failed": 1,
"pending": 0
},
"created_at": "2026-07-29T08:00:00.000000Z",
"updated_at": "2026-07-29T08:00:06.000000Z"
}
}מוני ה-summary מתעדכנים בזמן אמת, כך שאפשר להציג התקדמות (created + failed מתוך total) בזמן הריצה. לולאת polling פשוטה עם כמה שניות בין בקשות מספיקה; אצוות מעובדות במקביל במקטעים של 50 פריטים על פני queue workers.
שלב 3 — בחינת הפריטים
GET /batches/{uuid}/items מדפדף בתוצאות ברמת הפריט (עימוד cursor, ברירת מחדל של per_page היא 50, מקסימום 100), בסדר שבו שלחתם אותם (position הוא האינדקס מבוסס-האפס). סטטוסי פריט הם pending, created או failed.
curl --location 'https://app.shipos.co.il/api/v2/batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55/items?license_key={license_key}&per_page=100' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'// Node.js 18+ / דפדפנים — עוקב אחר ה-cursor עד העמוד האחרון
const batchId = '9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55'
const headers = {
'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
Accept: 'application/json',
}
let url = `https://app.shipos.co.il/api/v2/batches/${batchId}/items?license_key=${licenseKey}&per_page=100`
const failedItems = []
while (url) {
const res = await fetch(url, { headers })
const { data, links } = await res.json()
failedItems.push(...data.filter((item) => item.status === 'failed'))
url = links.next
}
console.log(`${failedItems.length} failed items`)<?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',
],
]);
$batchId = '9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55';
$query = ['license_key' => $licenseKey, 'per_page' => 100];
$failedItems = [];
do {
$page = json_decode((string) $client->get("batches/{$batchId}/items", [
'query' => $query,
])->getBody(), true);
foreach ($page['data'] as $item) {
if ($item['status'] === 'failed') {
$failedItems[] = $item;
}
}
$query['cursor'] = $page['meta']['next_cursor'];
} while ($query['cursor'] !== null);
echo count($failedItems), ' failed items', PHP_EOL;<?php
use Illuminate\Support\Facades\Http;
$batchId = '9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55';
$query = ['license_key' => $licenseKey, 'per_page' => 100];
$failedItems = [];
do {
$page = 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/batches/{$batchId}/items", $query)
->throw()
->json();
$failedItems = array_merge(
$failedItems,
array_filter($page['data'], fn (array $item): bool => $item['status'] === 'failed'),
);
$query['cursor'] = $page['meta']['next_cursor'];
} while ($query['cursor'] !== null);
logger()->info(count($failedItems).' failed items');# pip install httpx
import os
import httpx
batch_id = "9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55"
client = httpx.Client(
base_url="https://app.shipos.co.il/api/v2",
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
},
)
params = {"license_key": license_key, "per_page": 100}
failed_items = []
while True:
page = client.get(f"/batches/{batch_id}/items", params=params).raise_for_status().json()
failed_items += [item for item in page["data"] if item["status"] == "failed"]
cursor = page["meta"]["next_cursor"]
if cursor is None:
break
params["cursor"] = cursor
print(len(failed_items), "failed items")package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
type itemsPage struct {
Data []struct {
Position int `json:"position"`
Status string `json:"status"`
ShipmentID *string `json:"shipment_id"`
Error json.RawMessage `json:"error"`
} `json:"data"`
Meta struct {
NextCursor *string `json:"next_cursor"`
} `json:"meta"`
}
func main() {
// חזרו עם &cursor=<meta.next_cursor> עד ש-next_cursor הוא null.
url := "https://app.shipos.co.il/api/v2/batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55/items" +
"?license_key=" + os.Getenv("SHIPOS_LICENSE_KEY") + "&per_page=100"
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 page itemsPage
json.NewDecoder(res.Body).Decode(&page)
for _, item := range page.Data {
if item.Status == "failed" {
fmt.Printf("item %d failed: %s\n", item.Position, item.Error)
}
}
}// Java 17+ — java.net.http (פענוח עם Jackson/Gson, עקבו אחר meta.next_cursor)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class BatchItems {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.shipos.co.il/api/v2/batches/"
+ "9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55/items?license_key="
+ System.getenv("SHIPOS_LICENSE_KEY") + "&per_page=100"))
.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());
// {"data":[{"position":0,"status":"created","shipment_id":"..."}, ...],"meta":{...}}
System.out.println(response.body());
}
}// .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 licenseKey = Environment.GetEnvironmentVariable("SHIPOS_LICENSE_KEY");
var url = "batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55/items"
+ $"?license_key={licenseKey}&per_page=100";
// חזרו עם &cursor={meta.next_cursor} עד ש-next_cursor הוא null.
var page = await http.GetFromJsonAsync<JsonDocument>(url)
?? throw new InvalidOperationException("Empty response");
foreach (var item in page.RootElement.GetProperty("data").EnumerateArray())
{
if (item.GetProperty("status").GetString() == "failed")
{
Console.WriteLine($"item {item.GetProperty("position").GetInt32()} failed: "
+ item.GetProperty("error").GetRawText());
}
}require "net/http"
require "json"
base = "https://app.shipos.co.il/api/v2/batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55/items"
query = { license_key: ENV.fetch("SHIPOS_LICENSE_KEY"), per_page: 100 }
failed_items = []
loop do
uri = URI(base)
uri.query = URI.encode_www_form(query)
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) { |http| http.request(request) }
page = JSON.parse(response.body)
failed_items += page["data"].select { |item| item["status"] == "failed" }
break if page.dig("meta", "next_cursor").nil?
query[:cursor] = page.dig("meta", "next_cursor")
end
puts "#{failed_items.size} failed items"// [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 client = reqwest::Client::new();
let url = "https://app.shipos.co.il/api/v2/batches/9b1f7c2e-3a4d-4f18-9c7a-2b6e1d0f8a55/items";
let license_key = std::env::var("SHIPOS_LICENSE_KEY")?;
let mut cursor: Option<String> = None;
loop {
let mut query = vec![("license_key", license_key.clone()), ("per_page", "100".into())];
if let Some(c) = &cursor {
query.push(("cursor", c.clone()));
}
let page: Value = client
.get(url)
.query(&query)
.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?;
for item in page["data"].as_array().unwrap_or(&vec![]) {
if item["status"] == "failed" {
println!("item {} failed: {}", item["position"], item["error"]);
}
}
cursor = page["meta"]["next_cursor"].as_str().map(str::to_string);
if cursor.is_none() {
return Ok(());
}
}
}{
"data": [
{
"id": 4101,
"position": 0,
"status": "created",
"shipment_id": "b7d3a1e0-9f2c-4a8b-8e11-6c5d4b3a2f10",
"payload": { "...": "הגוף המדויק ששלחתם עבור פריט זה" },
"error": null,
"created_at": "2026-07-29T08:00:00.000000Z",
"updated_at": "2026-07-29T08:00:04.000000Z"
},
{
"id": 4102,
"position": 1,
"status": "failed",
"shipment_id": null,
"payload": { "...": "הגוף המדויק ששלחתם עבור פריט זה" },
"error": {
"code": "validation_failed",
"ship_data.city": ["The ship_data.city field is required."]
},
"created_at": "2026-07-29T08:00:00.000000Z",
"updated_at": "2026-07-29T08:00:05.000000Z"
}
],
"links": { "prev": null, "next": null, "first": null, "last": null },
"meta": { "path": "...", "per_page": 100, "next_cursor": null, "prev_cursor": null }
}פריט created נושא את ה-UUID של המשלוח החדש ב-shipment_id — השתמשו בו עם GET /shipments/{shipment}, עם endpoints של תוויות המשלוח, וכן הלאה.
טיפול בפריטים שנכשלו
לכל פריט failed יש אובייקט error עם code יציב בתוספת message ו/או שגיאות ברמת השדה:
error.code | משמעות | מה לעשות |
|---|---|---|
validation_failed | גוף הפריט נכשל בכללי הוולידציה של משלוח; שגיאות השדות מצורפות. | תקנו את השדות המפורטים ושלחו את הפריט מחדש. |
forbidden | ה-license_key של הפריט אינו שייך לחשבון שלכם. | תקנו את המפתח (רשימת הרישיונות שלכם דרך GET /licenses). |
carrier_error | חברת השילוח דחתה את המשלוח. | תקנו את הנתונים שחברת השילוח התלוננה עליהם, או פנו לתמיכה. |
server_error | שגיאה בלתי צפויה בעת עיבוד הפריט. | נסו שוב את הפריט; פנו לתמיכה אם זה נמשך. |
כדי לנסות שוב, אספו את ערכי ה-payload של הפריטים שנכשלו, תקנו אותם, ושלחו אותם כאצווה חדשה (או כקריאות POST /shipments בודדות כשרק מעטים נכשלו). פריטים שנוצרו בהצלחה הם משלוחים אמיתיים — אל תשלחו אותם מחדש.
שלב 4 — ייצוא התוצאות ל-CSV
לאחר שהאצווה הסתיימה אפשר למשוך CSV של המשלוחים שלכם. גם ייצואים הם אסינכרוניים: מכניסים אחד לתור, מבצעים לו polling, ומורידים אותו.
ייצואים חלים על כל הרישיון
ייצוא מכסה את משלוחי הרישיון התואמים לסינונים (נכון לעכשיו filter[active]), לא אצווה ספציפית אחת — אותן שורות שהייתם רואים ברשימת GET /shipments. הריצו אותו אחרי הייבוא שלכם כדי לקבל גיליון שכולל את כל מה שהאצווה יצרה.
הכנסת הייצוא לתור
POST /exports מגיב 202 עם רשומת הייצוא. רק ייצוא אחד לרישיון רשאי להיות בתהליך — בקשה שנייה בזמן שאחד הוא pending/processing מחזירה 409 עם code: "duplicate_request", אז פשוט המשיכו לבצע polling לראשון.
curl --location 'https://app.shipos.co.il/api/v2/exports' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"license_key": "{license_key}",
"filter": { "active": true }
}'// Node.js 18+ / דפדפנים — ללא תלויות
const res = await fetch('https://app.shipos.co.il/api/v2/exports', {
method: 'POST',
headers: {
'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
license_key: licenseKey,
filter: { active: true },
}),
})
const { data: export_ } = await res.json() // res.status === 202
const exportId = export_.id // בצעו polling ל-id המספרי הזה<?php
// composer require guzzlehttp/guzzle
$client = new \GuzzleHttp\Client(['base_uri' => 'https://app.shipos.co.il/api/v2/']);
$response = $client->post('exports', [
'headers' => [
'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
'Accept' => 'application/json',
],
'json' => [
'license_key' => $licenseKey,
'filter' => ['active' => true],
],
]);
$export = json_decode((string) $response->getBody(), true)['data'];
$exportId = $export['id']; // בצעו polling ל-id המספרי הזה<?php
use Illuminate\Support\Facades\Http;
$export = Http::withHeaders([
'X-Client-Id' => config('services.shipos.client_id'),
'X-Client-Secret' => config('services.shipos.client_secret'),
])
->acceptJson()
->post('https://app.shipos.co.il/api/v2/exports', [
'license_key' => $licenseKey,
'filter' => ['active' => true],
])
->throw()
->json('data');
$exportId = $export['id']; // בצעו polling ל-id המספרי הזה# pip install httpx
import os
import httpx
response = httpx.post(
"https://app.shipos.co.il/api/v2/exports",
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
},
json={
"license_key": license_key,
"filter": {"active": True},
},
)
response.raise_for_status()
export = response.json()["data"] # response.status_code == 202
export_id = export["id"] # בצעו polling ל-id המספרי הזהpackage main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"license_key": os.Getenv("SHIPOS_LICENSE_KEY"),
"filter": map[string]bool{"active": true},
})
req, _ := http.NewRequest("POST", "https://app.shipos.co.il/api/v2/exports", bytes.NewReader(body))
req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var payload struct {
Data struct {
ID int `json:"id"`
} `json:"data"`
}
json.NewDecoder(res.Body).Decode(&payload)
fmt.Println("poll export", payload.Data.ID) // res.StatusCode == 202
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class QueueExport {
public static void main(String[] args) throws Exception {
String body = """
{"license_key": "%s", "filter": {"active": true}}
""".formatted(System.getenv("SHIPOS_LICENSE_KEY"));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.shipos.co.il/api/v2/exports"))
.header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
.header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body()); // 202 {"data":{"id":512, ...}}
}
}// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;
using var http = new HttpClient
{
BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));
var response = await http.PostAsJsonAsync("exports", new
{
license_key = Environment.GetEnvironmentVariable("SHIPOS_LICENSE_KEY"),
filter = new { active = true },
});
response.EnsureSuccessStatusCode(); // 202
var payload = await response.Content.ReadFromJsonAsync<JsonDocument>();
var exportId = payload!.RootElement.GetProperty("data").GetProperty("id").GetInt32();
Console.WriteLine($"poll export {exportId}");require "net/http"
require "json"
uri = URI("https://app.shipos.co.il/api/v2/exports")
request = Net::HTTP::Post.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = JSON.dump(
license_key: ENV.fetch("SHIPOS_LICENSE_KEY"),
filter: { active: true },
)
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)
export = JSON.parse(response.body).fetch("data") # 202
export_id = export["id"] # בצעו polling ל-id המספרי הזה// [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: Value = reqwest::Client::new()
.post("https://app.shipos.co.il/api/v2/exports")
.header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
.header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
.header("Accept", "application/json")
.json(&json!({
"license_key": std::env::var("SHIPOS_LICENSE_KEY")?,
"filter": { "active": true },
}))
.send()
.await?
.error_for_status()?
.json()
.await?;
println!("poll export {}", payload["data"]["id"]); // 202
Ok(())
}תגובת 202:
{
"data": {
"id": 512,
"type": "shipments",
"status": "pending",
"platform": null,
"row_count": null,
"filename": null,
"error": null,
"download_url": null,
"created_at": "2026-07-29T08:10:00.000000Z",
"updated_at": "2026-07-29T08:10:00.000000Z"
}
}Polling עד לסיום
GET /exports/{export} — הסטטוס נע pending ← processing ← completed (או failed, עם הסיבה ב-error). בסיום, download_url, filename ו-row_count מאוכלסים:
curl --location 'https://app.shipos.co.il/api/v2/exports/512?license_key={license_key}' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'// Node.js 18+ / דפדפנים — ללא תלויות
const exportId = 512
const url = `https://app.shipos.co.il/api/v2/exports/${exportId}?license_key=${licenseKey}`
const headers = {
'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
Accept: 'application/json',
}
let exportRecord
do {
await new Promise((resolve) => setTimeout(resolve, 3000))
const res = await fetch(url, { headers })
exportRecord = (await res.json()).data
console.log(exportRecord.status)
} while (!['completed', 'failed'].includes(exportRecord.status))
const downloadUrl = exportRecord.download_url // null כאשר הסטטוס הוא failed<?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',
],
]);
$exportId = 512;
do {
sleep(3);
$export = json_decode((string) $client->get("exports/{$exportId}", [
'query' => ['license_key' => $licenseKey],
])->getBody(), true)['data'];
echo $export['status'], PHP_EOL;
} while (! in_array($export['status'], ['completed', 'failed'], true));
$downloadUrl = $export['download_url']; // null כאשר הסטטוס הוא failed<?php
use Illuminate\Support\Facades\Http;
$exportId = 512;
do {
sleep(3);
$export = 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/exports/{$exportId}", [
'license_key' => $licenseKey,
])
->throw()
->json('data');
logger()->info($export['status']);
} while (! in_array($export['status'], ['completed', 'failed'], true));
$downloadUrl = $export['download_url']; // null כאשר הסטטוס הוא failed# pip install httpx
import os
import time
import httpx
export_id = 512
client = httpx.Client(
base_url="https://app.shipos.co.il/api/v2",
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
},
)
while True:
time.sleep(3)
response = client.get(f"/exports/{export_id}", params={"license_key": license_key})
response.raise_for_status()
export = response.json()["data"]
print(export["status"])
if export["status"] in ("completed", "failed"):
break
download_url = export["download_url"] # None כאשר הסטטוס הוא failedpackage main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
type exportResponse struct {
Data struct {
Status string `json:"status"`
DownloadURL string `json:"download_url"`
} `json:"data"`
}
func main() {
url := "https://app.shipos.co.il/api/v2/exports/512" +
"?license_key=" + os.Getenv("SHIPOS_LICENSE_KEY")
for {
time.Sleep(3 * time.Second)
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)
}
var export exportResponse
json.NewDecoder(res.Body).Decode(&export)
res.Body.Close()
fmt.Println(export.Data.Status)
if export.Data.Status == "completed" || export.Data.Status == "failed" {
fmt.Println(export.Data.DownloadURL) // ריק כאשר הסטטוס הוא failed
return
}
}
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class PollExport {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.shipos.co.il/api/v2/exports/512"
+ "?license_key=" + System.getenv("SHIPOS_LICENSE_KEY")))
.header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
.header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
.header("Accept", "application/json")
.build();
String body;
do {
Thread.sleep(3_000);
body = client.send(request, HttpResponse.BodyHandlers.ofString()).body();
System.out.println(body); // {"data":{"status":"...","download_url":...}}
} while (!body.contains("\"status\":\"completed\"")
&& !body.contains("\"status\":\"failed\""));
}
}// .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 licenseKey = Environment.GetEnvironmentVariable("SHIPOS_LICENSE_KEY");
var url = $"exports/512?license_key={licenseKey}";
JsonElement export;
string? status;
do
{
await Task.Delay(3_000);
var payload = await http.GetFromJsonAsync<JsonDocument>(url);
export = payload!.RootElement.GetProperty("data");
status = export.GetProperty("status").GetString();
Console.WriteLine(status);
} while (status is not ("completed" or "failed"));
Console.WriteLine(export.GetProperty("download_url").GetString());require "net/http"
require "json"
uri = URI("https://app.shipos.co.il/api/v2/exports/512")
uri.query = URI.encode_www_form(license_key: ENV.fetch("SHIPOS_LICENSE_KEY"))
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"
loop do
sleep 3
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
export = JSON.parse(response.body).fetch("data")
puts export["status"]
if %w[completed failed].include?(export["status"])
puts export["download_url"] # nil כאשר הסטטוס הוא failed
break
end
end// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let url = "https://app.shipos.co.il/api/v2/exports/512";
loop {
tokio::time::sleep(Duration::from_secs(3)).await;
let payload: Value = client
.get(url)
.query(&[("license_key", std::env::var("SHIPOS_LICENSE_KEY")?)])
.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 export = &payload["data"];
println!("{}", export["status"]);
if export["status"] == "completed" || export["status"] == "failed" {
println!("{}", export["download_url"]); // null כאשר הסטטוס הוא failed
return Ok(());
}
}
}{
"data": {
"id": 512,
"type": "shipments",
"status": "completed",
"platform": null,
"row_count": 1873,
"filename": "shipments-20260729081000.csv",
"error": null,
"download_url": "https://app.shipos.co.il/api/v2/exports/512/download",
"created_at": "2026-07-29T08:10:00.000000Z",
"updated_at": "2026-07-29T08:10:42.000000Z"
}
}הורדת ה-CSV
GET /exports/{export}/download מזרים את הקובץ. לפני שהייצוא הושלם הוא מחזיר 404 עם code: "not_found" וההודעה Export file is not ready. — בצעו polling ל-status תחילה.
curl --location 'https://app.shipos.co.il/api/v2/exports/512/download?license_key={license_key}' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--output shipments.csv// Node.js 18+ — top-level await במודול ESM
import { writeFile } from 'node:fs/promises'
const exportId = 512
const res = await fetch(
`https://app.shipos.co.il/api/v2/exports/${exportId}/download?license_key=${licenseKey}`,
{
headers: {
'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
},
},
)
if (!res.ok) {
const { error } = await res.json()
throw new Error(`${error.code}: ${error.message}`) // not_found עד שהוא מוכן
}
// כתבו את הבייטים הגולמיים — ה-BOM של UTF-8 הוא חלק מהקובץ.
await writeFile('shipments.csv', Buffer.from(await res.arrayBuffer()))<?php
// composer require guzzlehttp/guzzle
$client = new \GuzzleHttp\Client(['base_uri' => 'https://app.shipos.co.il/api/v2/']);
$exportId = 512;
// 'sink' מזרים ישירות לדיסק — הבייטים הגולמיים שומרים על ה-BOM של UTF-8.
$client->get("exports/{$exportId}/download", [
'headers' => [
'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
],
'query' => ['license_key' => $licenseKey],
'sink' => 'shipments.csv',
]);<?php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
$exportId = 512;
$response = Http::withHeaders([
'X-Client-Id' => config('services.shipos.client_id'),
'X-Client-Secret' => config('services.shipos.client_secret'),
])
->get("https://app.shipos.co.il/api/v2/exports/{$exportId}/download", [
'license_key' => $licenseKey,
])
->throw();
// שמרו את הגוף הגולמי — ה-BOM של UTF-8 הוא חלק מהקובץ.
Storage::put('shipments.csv', $response->body());# pip install httpx
import os
import httpx
export_id = 512
with httpx.stream(
"GET",
f"https://app.shipos.co.il/api/v2/exports/{export_id}/download",
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
},
params={"license_key": license_key},
) as response:
response.raise_for_status()
# מצב בינארי — ה-BOM של UTF-8 הוא חלק מהקובץ.
with open("shipments.csv", "wb") as file:
for chunk in response.iter_bytes():
file.write(chunk)package main
import (
"io"
"net/http"
"os"
)
func main() {
url := "https://app.shipos.co.il/api/v2/exports/512/download" +
"?license_key=" + os.Getenv("SHIPOS_LICENSE_KEY")
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"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
file, err := os.Create("shipments.csv")
if err != nil {
panic(err)
}
defer file.Close()
// העתיקו את הבייטים הגולמיים — ה-BOM של UTF-8 הוא חלק מהקובץ.
if _, err := io.Copy(file, res.Body); err != nil {
panic(err)
}
}// Java 17+ — java.net.http, ללא תלויות
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
public class DownloadExport {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.shipos.co.il/api/v2/exports/512/download"
+ "?license_key=" + System.getenv("SHIPOS_LICENSE_KEY")))
.header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
.header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
.build();
// ofFile כותב את הבייטים הגולמיים — ה-BOM של UTF-8 הוא חלק מהקובץ.
HttpResponse<Path> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofFile(Path.of("shipments.csv")));
System.out.println(response.statusCode()); // 404 עד שהייצוא מוכן
}
}// .NET 8+
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 licenseKey = Environment.GetEnvironmentVariable("SHIPOS_LICENSE_KEY");
var response = await http.GetAsync($"exports/512/download?license_key={licenseKey}");
response.EnsureSuccessStatusCode(); // 404 עד שהייצוא מוכן
// זרם בייטים גולמי — ה-BOM של UTF-8 הוא חלק מהקובץ.
await using var file = File.Create("shipments.csv");
await response.Content.CopyToAsync(file);require "net/http"
uri = URI("https://app.shipos.co.il/api/v2/exports/512/download")
uri.query = URI.encode_www_form(license_key: ENV.fetch("SHIPOS_LICENSE_KEY"))
request = Net::HTTP::Get.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request) do |response|
raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
# מצב בינארי — ה-BOM של UTF-8 הוא חלק מהקובץ.
File.open("shipments.csv", "wb") do |file|
response.read_body { |chunk| file.write(chunk) }
end
end
end// [dependencies]
// reqwest = { version = "0.12" }
// tokio = { version = "1", features = ["full"] }
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let bytes = reqwest::Client::new()
.get("https://app.shipos.co.il/api/v2/exports/512/download")
.query(&[("license_key", std::env::var("SHIPOS_LICENSE_KEY")?)])
.header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
.header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
.send()
.await?
.error_for_status()? // 404 עד שהייצוא מוכן
.bytes()
.await?;
// כתבו את הבייטים הגולמיים — ה-BOM של UTF-8 הוא חלק מהקובץ.
tokio::fs::write("shipments.csv", &bytes).await?;
Ok(())
}עמודות ה-CSV הן: shipping_code, short_tracking_code, type, status, is_active, first_name, last_name, phone, city, address_1, created_at. הקובץ מתחיל ב-UTF-8 BOM כדי שעברית תוצג נכון ב-Excel.
מדריך שגיאות מהיר
| סטטוס | קוד | היכן | מתי |
|---|---|---|---|
| 403 | forbidden | הכול | אין רישיון שמיש (לא פעיל/פג תוקף/לא בבעלותכם). |
| 404 | not_found | GET /batches/{uuid}, GET /exports/{export}, הורדה | המשאב אינו שייך לרישיון שלכם, או שקובץ הייצוא עדיין לא מוכן. |
| 409 | duplicate_request | POST /exports | ייצוא לרישיון זה כבר בהכנה — בצעו לו polling במקום. |
| 422 | validation_failed | POST /batches | shipments חסר/ריק או מעל 2000 פריטים; או מספר רישיונות פעילים ללא license_key. |