רשימות, סינונים ועימוד
כל רשימה מעומדת ב-v2 משתמשת בעימוד cursor: במקום מספרי עמודים עוקבים אחרי אסימון cursor אטום מעמוד לעמוד. עמוד זה מתעד את פרמטרי העימוד והסינון של כל endpoint רשימה, ואז עובר על לולאת עימוד מלאה.
איך עימוד cursor עובד
פרמטרי הבקשה (query string):
| פרמטר | תיאור |
|---|---|
per_page | פריטים לעמוד. לכל endpoint יש ברירת מחדל משלו; כולם מוגבלים ל-100. |
cursor | אסימון אטום שמצביע על העמוד הבא (או הקודם). השמיטו אותו בעמוד הראשון; קחו אותו מ-meta.next_cursor של התגובה הקודמת (או פשוט עקבו אחרי links.next, שכבר נושא אותו יחד עם שאר פרמטרי השאילתה שלכם). |
תגובות מעומדות חולקות את המעטפת הזו:
{
"data": [ { "...": "resources" } ],
"links": {
"first": null,
"last": null,
"prev": null,
"next": "https://app.shipos.co.il/api/v2/orders?cursor=eyJpZCI6OTg3NiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ&per_page=25"
},
"meta": {
"path": "https://app.shipos.co.il/api/v2/orders",
"per_page": 25,
"next_cursor": "eyJpZCI6OTg3NiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ",
"prev_cursor": null
}
}meta.next_cursor— אסימון לעמוד הבא;nullבעמוד האחרון. ה-null הזה הוא תנאי העצירה של הלולאה שלכם.meta.prev_cursor— אסימון לעמוד הקודם;nullבעמוד הראשון.links.next/links.prev— כתובות URL מוכנות הנושאות את ה-cursor ואת פרמטרי השאילתה שלכם;links.first/links.lastהם תמידnull(ל-cursors אין "קפיצה לעמוד האחרון").- אין ספירה כוללת — עימוד cursor לא סופר את סט התוצאות המלא (זה מה שהופך אותו למהיר על טבלאות גדולות). אם אתם צריכים חילוץ מלא, רוצו בלולאה עד הסוף או השתמשו בייצוא.
התייחסו ל-cursor כמחרוזת אטומה: העבירו אותו בחזרה בדיוק כפי שהתקבל, מקודד ל-URL (כתובת ה-links.next כבר מקודדת).
Endpoint אחרי endpoint
GET /shipments — ייחוס
מעומד ב-cursor. ברירת המחדל של per_page היא 25, מקסימום 100.
| פרמטר | סוג | תיאור |
|---|---|---|
filter[active] | boolean | true ← רק משלוחים פעילים, false ← רק לא פעילים (מבוטלים). השמיטו לקבלת שניהם. |
sort | string | מפתח מיון מרשימה מאושרת: created_at או id. הוסיפו קידומת - לסדר יורד (sort=-created_at). מפתחות לא מוכרים נופלים חזרה לברירת המחדל. ברירת מחדל: החדש ביותר קודם (id יורד). שובר-שוויון יציב של id מוחל תמיד. |
per_page, cursor | כמתואר לעיל. |
curl --location 'https://app.shipos.co.il/api/v2/shipments?filter[active]=true&sort=-created_at&per_page=50' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'// Node.js 18+ / דפדפנים — ללא תלויות
const params = new URLSearchParams({
'filter[active]': 'true',
sort: '-created_at',
per_page: '50',
})
const response = await fetch(`https://app.shipos.co.il/api/v2/shipments?${params}`, {
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 page = await response.json()
console.log(page.data.length, 'shipments; next cursor:', page.meta.next_cursor)<?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',
],
]);
$page = json_decode($client->get('shipments', [
'query' => [
'filter[active]' => 'true',
'sort' => '-created_at',
'per_page' => 50,
],
])->getBody()->getContents(), true);
printf("%d shipments\n", count($page['data']));
echo 'next cursor: ', $page['meta']['next_cursor'] ?? 'null', PHP_EOL;<?php
use Illuminate\Support\Facades\Http;
$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/shipments', [
'filter[active]' => 'true',
'sort' => '-created_at',
'per_page' => 50,
])
->throw()
->json();
logger()->info(count($page['data']).' shipments', [
'next_cursor' => $page['meta']['next_cursor'],
]);# pip install httpx
import os
import httpx
response = httpx.get(
"https://app.shipos.co.il/api/v2/shipments",
params={"filter[active]": "true", "sort": "-created_at", "per_page": 50},
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
},
)
response.raise_for_status()
page = response.json()
print(len(page["data"]), "shipments; next cursor:", page["meta"]["next_cursor"])package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
)
type shipmentPage struct {
Data []json.RawMessage `json:"data"`
Meta struct {
NextCursor *string `json:"next_cursor"`
} `json:"meta"`
}
func main() {
query := url.Values{
"filter[active]": {"true"},
"sort": {"-created_at"},
"per_page": {"50"},
}
req, _ := http.NewRequest("GET",
"https://app.shipos.co.il/api/v2/shipments?"+query.Encode(), nil)
req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var page shipmentPage
if err := json.NewDecoder(res.Body).Decode(&page); err != nil {
panic(err)
}
fmt.Println(len(page.Data), "shipments")
}// 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 ShipOsShipments {
public static void main(String[] args) throws Exception {
String url = "https://app.shipos.co.il/api/v2/shipments"
+ "?filter%5Bactive%5D=true&sort=-created_at&per_page=50";
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
.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());
}
// {"data":[...],"links":{...},"meta":{...}} — מפו עם Jackson/Gson
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 page = await http.GetFromJsonAsync<JsonDocument>(
"shipments?filter%5Bactive%5D=true&sort=-created_at&per_page=50")
?? throw new InvalidOperationException("Empty response");
Console.WriteLine($"{page.RootElement.GetProperty("data").GetArrayLength()} shipments");
Console.WriteLine(
page.RootElement.GetProperty("meta").GetProperty("next_cursor").GetString() ?? "null");require "net/http"
require "json"
uri = URI("https://app.shipos.co.il/api/v2/shipments")
uri.query = URI.encode_www_form(
"filter[active]" => "true", "sort" => "-created_at", "per_page" => 50
)
request = Net::HTTP::Get.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
page = JSON.parse(response.body)
puts "#{page["data"].size} shipments; next cursor: #{page.dig("meta", "next_cursor")}"// [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 page: Value = reqwest::Client::new()
.get("https://app.shipos.co.il/api/v2/shipments")
.query(&[
("filter[active]", "true"),
("sort", "-created_at"),
("per_page", "50"),
])
.header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
.header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
.header("Accept", "application/json")
.send()
.await?
.error_for_status()?
.json()
.await?;
println!("{} shipments", page["data"].as_array().map_or(0, Vec::len));
println!("next cursor: {}", page["meta"]["next_cursor"]);
Ok(())
}GET /orders — ייחוס
מעומד ב-cursor, החדש ביותר קודם. ברירת המחדל של per_page היא 25, מקסימום 100. ללא סינונים.
| פרמטר | סוג | תיאור |
|---|---|---|
per_page, cursor | כמתואר לעיל. |
GET /orders/{order}/shipments (המשלוחים של הזמנה אחת) אינו מעומד — הוא מחזיר את כל משלוחי ההזמנה בתגובה אחת.
החזרות — GET /shipments?filter[type]=2
אין endpoint נפרד להחזרות. החזרה היא משלוח עם סוג שירות איסוף, ולכן מציגים אותן באמצעות הפרמטר filter[type] המתועד תחת GET /shipments למעלה.
GET /batches — ייחוס
מעומד ב-cursor, החדש ביותר קודם, כל אצווה עם ה-items שלה טעונים מראש (eager-loaded). ברירת המחדל של per_page היא 25, מקסימום 100. ללא סינונים.
GET /batches/{uuid}/items — ייחוס
מעומד ב-cursor לפי סדר השליחה (position, ואז id). ברירת המחדל של per_page היא 50, מקסימום 100. ללא סינונים.
GET /notifications/sms-logs — ייחוס
מעומד ב-cursor, החדש ביותר קודם. ברירת המחדל של per_page היא 25, מקסימום 100.
| פרמטר | סוג | תיאור |
|---|---|---|
filter[shipping_id] | integer | רק לוגים של משלוח (shipping) בודד. |
filter[success] | boolean | true ← רק שליחות שהצליחו, false ← רק שנכשלו. השמיטו לקבלת שניהם. |
per_page, cursor | כמתואר לעיל. |
curl --location 'https://app.shipos.co.il/api/v2/notifications/sms-logs?filter[success]=false&per_page=50' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'// Node.js 18+ / דפדפנים — ללא תלויות
const params = new URLSearchParams({
'filter[success]': 'false',
per_page: '50',
})
const response = await fetch(
`https://app.shipos.co.il/api/v2/notifications/sms-logs?${params}`,
{
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 page = await response.json()
console.log(page.data.length, 'failed sends; next cursor:', page.meta.next_cursor)<?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',
],
]);
$page = json_decode($client->get('notifications/sms-logs', [
'query' => [
'filter[success]' => 'false',
'per_page' => 50,
],
])->getBody()->getContents(), true);
printf("%d failed sends\n", count($page['data']));
echo 'next cursor: ', $page['meta']['next_cursor'] ?? 'null', PHP_EOL;<?php
use Illuminate\Support\Facades\Http;
$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/notifications/sms-logs', [
'filter[success]' => 'false',
'per_page' => 50,
])
->throw()
->json();
logger()->info(count($page['data']).' failed sends', [
'next_cursor' => $page['meta']['next_cursor'],
]);# pip install httpx
import os
import httpx
response = httpx.get(
"https://app.shipos.co.il/api/v2/notifications/sms-logs",
params={"filter[success]": "false", "per_page": 50},
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
},
)
response.raise_for_status()
page = response.json()
print(len(page["data"]), "failed sends; next cursor:", page["meta"]["next_cursor"])package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
)
type smsLogPage struct {
Data []json.RawMessage `json:"data"`
Meta struct {
NextCursor *string `json:"next_cursor"`
} `json:"meta"`
}
func main() {
query := url.Values{
"filter[success]": {"false"},
"per_page": {"50"},
}
req, _ := http.NewRequest("GET",
"https://app.shipos.co.il/api/v2/notifications/sms-logs?"+query.Encode(), nil)
req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var page smsLogPage
if err := json.NewDecoder(res.Body).Decode(&page); err != nil {
panic(err)
}
fmt.Println(len(page.Data), "failed sends")
}// 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 ShipOsSmsLogs {
public static void main(String[] args) throws Exception {
String url = "https://app.shipos.co.il/api/v2/notifications/sms-logs"
+ "?filter%5Bsuccess%5D=false&per_page=50";
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
.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());
}
// {"data":[...],"links":{...},"meta":{...}} — מפו עם Jackson/Gson
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 page = await http.GetFromJsonAsync<JsonDocument>(
"notifications/sms-logs?filter%5Bsuccess%5D=false&per_page=50")
?? throw new InvalidOperationException("Empty response");
Console.WriteLine($"{page.RootElement.GetProperty("data").GetArrayLength()} failed sends");
Console.WriteLine(
page.RootElement.GetProperty("meta").GetProperty("next_cursor").GetString() ?? "null");require "net/http"
require "json"
uri = URI("https://app.shipos.co.il/api/v2/notifications/sms-logs")
uri.query = URI.encode_www_form("filter[success]" => "false", "per_page" => 50)
request = Net::HTTP::Get.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
page = JSON.parse(response.body)
puts "#{page["data"].size} failed sends; next cursor: #{page.dig("meta", "next_cursor")}"// [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 page: Value = reqwest::Client::new()
.get("https://app.shipos.co.il/api/v2/notifications/sms-logs")
.query(&[("filter[success]", "false"), ("per_page", "50")])
.header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
.header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
.header("Accept", "application/json")
.send()
.await?
.error_for_status()?
.json()
.await?;
println!("{} failed sends", page["data"].as_array().map_or(0, Vec::len));
println!("next cursor: {}", page["meta"]["next_cursor"]);
Ok(())
}GET /webhooks/{uuid}/deliveries — ייחוס
מעומד ב-cursor, החדש ביותר קודם. ברירת המחדל של per_page היא 25, מקסימום 100. ללא סינונים.
Endpoints ללא עימוד
אלה מחזירים את סט התוצאות המלא במערך data יחיד — אין cursor, per_page, links או meta:
| Endpoint | הערות |
|---|---|
GET /pickup-points — ייחוס | מחזיר את רשימת הנקודות המלאה של חברת השילוח. city (סינון תת-מחרוזת) ו-limit (תקרה, מקסימום 500) מצמצמים את התוצאה, ו-lat/lng/address מדרגים אותה מהקרוב ביותר — אבל אין עמודים — limit הוא תקרה, לא גודל עמוד. |
GET /licenses — ייחוס | כל הרישיונות של החשבון. |
GET /webhooks — ייחוס | כל מינויי ה-webhook של הרישיון. |
GET /orders/{order}/shipments — ייחוס | כל המשלוחים של הזמנה אחת. |
GET /notifications/sms-templates, GET /reference/* | רשימות מלאות. |
טיפ — ערכי סינון בוליאניים
סינונים בוליאניים (filter[active], filter[success]) מקבלים את הצורות המקובלות של אמת/שקר: 1/0, true/false.
דוגמה מלאה — מעבר על כל ההזמנות
משכו כל הזמנה, עמוד אחרי עמוד, ועצרו כאשר meta.next_cursor הוא null.
#!/usr/bin/env bash
# דורש jq. עובר על /orders עמוד אחרי עמוד.
BASE='https://app.shipos.co.il/api/v2'
CURSOR=''
while :; do
URL="$BASE/orders?per_page=100"
[ -n "$CURSOR" ] && URL="$URL&cursor=$CURSOR"
PAGE=$(curl --silent --location "$URL" \
--header "X-Client-Id: $CLIENT_ID" \
--header "X-Client-Secret: $CLIENT_SECRET" \
--header 'Accept: application/json')
echo "$PAGE" | jq -r '.data[] | "\(.order_id)\t\(.number)\t\(.total) \(.currency)"'
CURSOR=$(echo "$PAGE" | jq -r '.meta.next_cursor // empty')
[ -z "$CURSOR" ] && break
done// Node.js 18+ / דפדפנים — ללא תלויות
const headers = {
'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
'Accept': 'application/json',
}
const orders = []
let cursor = null
do {
const params = new URLSearchParams({ per_page: '100' })
if (cursor) params.set('cursor', cursor)
const res = await fetch(`https://app.shipos.co.il/api/v2/orders?${params}`, { headers })
const page = await res.json()
orders.push(...page.data)
cursor = page.meta.next_cursor // null בעמוד האחרון
} while (cursor)
console.log(`Fetched ${orders.length} orders`)<?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',
];
$cursor = null;
$orders = [];
do {
$response = $client->get('orders', [
'headers' => $headers,
'query' => array_filter([
'per_page' => 100,
'cursor' => $cursor,
]),
]);
$page = json_decode((string) $response->getBody(), true);
foreach ($page['data'] as $order) {
$orders[] = $order;
}
$cursor = $page['meta']['next_cursor'] ?? null;
} while ($cursor !== null);
printf("Fetched %d orders\n", count($orders));<?php
use Illuminate\Support\Facades\Http;
$client = Http::withHeaders([
'X-Client-Id' => config('services.shipos.client_id'),
'X-Client-Secret' => config('services.shipos.client_secret'),
])->acceptJson()->baseUrl('https://app.shipos.co.il/api/v2');
$cursor = null;
$orders = [];
do {
$page = $client->get('orders', array_filter([
'per_page' => 100,
'cursor' => $cursor,
]))->throw()->json();
$orders = array_merge($orders, $page['data']);
$cursor = $page['meta']['next_cursor'] ?? null;
} while ($cursor !== null);
logger()->info('Fetched '.count($orders).' orders');# pip install httpx
import os
import httpx
headers = {
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
}
orders = []
cursor = None
with httpx.Client(base_url="https://app.shipos.co.il/api/v2", headers=headers) as client:
while True:
params = {"per_page": 100}
if cursor:
params["cursor"] = cursor
page = client.get("/orders", params=params).raise_for_status().json()
orders.extend(page["data"])
cursor = page["meta"]["next_cursor"] # None בעמוד האחרון
if not cursor:
break
print(f"Fetched {len(orders)} orders")package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
)
type ordersPage struct {
Data []json.RawMessage `json:"data"`
Meta struct {
NextCursor *string `json:"next_cursor"`
} `json:"meta"`
}
func main() {
cursor, total := "", 0
for {
query := url.Values{"per_page": {"100"}}
if cursor != "" {
query.Set("cursor", cursor)
}
req, _ := http.NewRequest("GET",
"https://app.shipos.co.il/api/v2/orders?"+query.Encode(), nil)
req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
var page ordersPage
err = json.NewDecoder(res.Body).Decode(&page)
res.Body.Close()
if err != nil {
panic(err)
}
total += len(page.Data)
if page.Meta.NextCursor == nil { // null בעמוד האחרון
break
}
cursor = *page.Meta.NextCursor
}
fmt.Printf("Fetched %d orders\n", total)
}// Java 17+ — java.net.http (קריאת ה-cursor עם Jackson)
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class ShipOsWalkOrders {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
ObjectMapper mapper = new ObjectMapper();
String cursor = null;
int total = 0;
do {
String url = "https://app.shipos.co.il/api/v2/orders?per_page=100"
+ (cursor == null ? ""
: "&cursor=" + URLEncoder.encode(cursor, StandardCharsets.UTF_8));
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
.header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
.header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
.header("Accept", "application/json")
.build();
JsonNode page = mapper.readTree(
client.send(request, HttpResponse.BodyHandlers.ofString()).body());
total += page.get("data").size();
JsonNode next = page.at("/meta/next_cursor"); // null בעמוד האחרון
cursor = next.isNull() || next.isMissingNode() ? null : next.asText();
} while (cursor != null);
System.out.println("Fetched " + total + " orders");
}
}// .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"));
string? cursor = null;
var total = 0;
do
{
var url = "orders?per_page=100"
+ (cursor is null ? "" : $"&cursor={Uri.EscapeDataString(cursor)}");
var page = await http.GetFromJsonAsync<JsonDocument>(url)
?? throw new InvalidOperationException("Empty response");
total += page.RootElement.GetProperty("data").GetArrayLength();
var next = page.RootElement.GetProperty("meta").GetProperty("next_cursor");
cursor = next.ValueKind == JsonValueKind.Null ? null : next.GetString();
} while (cursor is not null);
Console.WriteLine($"Fetched {total} orders");require "net/http"
require "json"
cursor = nil
total = 0
loop do
uri = URI("https://app.shipos.co.il/api/v2/orders")
params = { "per_page" => 100 }
params["cursor"] = cursor if cursor
uri.query = URI.encode_www_form(params)
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) { |h| h.request(request) }
raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
page = JSON.parse(response.body)
total += page["data"].size
cursor = page.dig("meta", "next_cursor") # nil בעמוד האחרון
break unless cursor
end
puts "Fetched #{total} orders"// [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 mut cursor: Option<String> = None;
let mut total = 0usize;
loop {
let mut query = vec![("per_page", "100".to_string())];
if let Some(c) = &cursor {
query.push(("cursor", c.clone()));
}
let page: Value = client
.get("https://app.shipos.co.il/api/v2/orders")
.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?;
total += page["data"].as_array().map_or(0, Vec::len);
cursor = page["meta"]["next_cursor"].as_str().map(str::to_owned);
if cursor.is_none() {
break;
}
}
println!("Fetched {total} orders");
Ok(())
}אותה לולאה עובדת עבור /shipments, /orders, /batches, פריטי אצווה, לוגים של SMS ומשלוחי webhook — רק ה-endpoint, הסינונים וברירת המחדל של per_page משתנים.
אזהרה — Cursors לא שורדים שינויי שאילתה
cursor מקודד את המיקום בתוך סידור ספציפי אחד. שמרו על per_page, sort והסינונים זהים לאורך הלולאה — שינוי שלהם באמצע המעבר מבטל את תוקף המיקום. התחילו מעבר טרי (ללא cursor) בכל פעם שהשאילתה משתנה.