Skip to content

רשימות, סינונים ועימוד

כל רשימה מעומדת ב-v2 משתמשת בעימוד cursor: במקום מספרי עמודים עוקבים אחרי אסימון cursor אטום מעמוד לעמוד. עמוד זה מתעד את פרמטרי העימוד והסינון של כל endpoint רשימה, ואז עובר על לולאת עימוד מלאה.

איך עימוד cursor עובד

פרמטרי הבקשה (query string):

פרמטרתיאור
per_pageפריטים לעמוד. לכל endpoint יש ברירת מחדל משלו; כולם מוגבלים ל-100.
cursorאסימון אטום שמצביע על העמוד הבא (או הקודם). השמיטו אותו בעמוד הראשון; קחו אותו מ-meta.next_cursor של התגובה הקודמת (או פשוט עקבו אחרי links.next, שכבר נושא אותו יחד עם שאר פרמטרי השאילתה שלכם).

תגובות מעומדות חולקות את המעטפת הזו:

json
{
  "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]booleantrue ← רק משלוחים פעילים, false ← רק לא פעילים (מבוטלים). השמיטו לקבלת שניהם.
sortstringמפתח מיון מרשימה מאושרת: created_at או id. הוסיפו קידומת - לסדר יורד (sort=-created_at). מפתחות לא מוכרים נופלים חזרה לברירת המחדל. ברירת מחדל: החדש ביותר קודם (id יורד). שובר-שוויון יציב של id מוחל תמיד.
per_page, cursorכמתואר לעיל.
bash
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'
js
// 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
<?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
<?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'],
]);
python
# 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"])
go
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
// 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());
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

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

var 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");
ruby
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")}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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]booleantrue ← רק שליחות שהצליחו, false ← רק שנכשלו. השמיטו לקבלת שניהם.
per_page, cursorכמתואר לעיל.
bash
curl --location 'https://app.shipos.co.il/api/v2/notifications/sms-logs?filter[success]=false&per_page=50' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / דפדפנים — ללא תלויות
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
<?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
<?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'],
]);
python
# pip install httpx
import os

import httpx

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

print(len(page["data"]), "failed sends; next cursor:", page["meta"]["next_cursor"])
go
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
// 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());
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

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

var 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");
ruby
require "net/http"
require "json"

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

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

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

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

page = JSON.parse(response.body)
puts "#{page["data"].size} failed sends; next cursor: #{page.dig("meta", "next_cursor")}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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.

bash
#!/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
js
// 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
<?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
<?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');
python
# 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")
go
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
// 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");
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

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

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");
ruby
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"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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) בכל פעם שהשאילתה משתנה.