Skip to content

תוויות ​

שליפת התווית להדפסה של משלוח. ב-v1 יש שלוש נקודות קצה לתוויות — אחת למשלוח בודד, אחת לכל המשלוחים הפעילים של הזמנה, ואחת לקבוצה שרירותית — וכל השלוש משיבות עם URL במעטפת, ולא עם ה-PDF עצמו.

המקבילה ב-v2: GET /shipments/{shipment}/label ו-POST /shipments/labels.

כל השלוש יושבות בקבוצת התחילית shipping, ולכן הן נושאות את פרטי ההזדהות שלהן בגוף הבקשה (license_key + domain — ראו אימות) ומשיבות במעטפת v1 הסטנדרטית בעלת ארבעת המפתחות.

מחרוזות ה-message באנגלית שמצוטטות בעמוד הזה הן מפתחות תרגום

כל הודעה קריאה לאדם כאן — Invalid License Key, Order not found, No active shippings found for this order, Invalid response from shipping company — היא מפתח תרגום ולא ערך שעובר על החוט. שרת חי רינדר את כולן בעברית. הסתעפו לפי סטטוס ה-HTTP, לפי success, ולפי אילו מפתחות קיימים (data.url, data.labels, error חשוף); לעולם לא לפי הטקסט של message.

באיזו נקודת קצה להשתמש ​

נקודת קצהבחירה לפימבנה dataתווית של חברת השילוח אפשרית
POST /shipping/labelshipping_id יחיד (קוד המעקב של חברת השילוח){ "url": "…" }כן
POST /shipping/order-labelorder_id יחיד, שמתרחב לכל המשלוחים הפעילים שבה{ "labels": [ { "shipping_code": "…", "url": "…" } ] }כן
POST /shipping/bulk-labelעד 100 shipping_codes{ "url": "…" } — URL אחד לכל הקבוצהלא, תמיד הרנדרר של ShipOS

רק order-label מכבדת את is_active

order-label מסננת למשלוחים פעילים ומשיבה 404 / No active shippings found for this order כשלא נשאר אף אחד. label ו-bulk-label אינן מפעילות סינון כזה. לאחר שמשלוח בוטל דרך change_order_status, שתיהן עדיין החזירו עבורו כתובת תווית עובדת — HTTP 200, success: true — והכתובת עדיין רינדרה את התווית. שום דבר בתשובה של אף אחת מהן אינו מעיד שהמשלוח בוטל.

לכן כתובת תווית אינה הוכחה שהמשלוח פעיל. אם זה משנה לכם, בדקו את is_active בעצמכם דרך POST /shipping/order לפני ההדפסה, או השתמשו ב-order-label — היחידה מבין השלוש שתסרב.

התשובה היא URL, לא מסמך

אף נקודת קצה של תוויות ב-v1 אינה מחזירה PDF, בלוק base64 או גוף בינארי. כל אחת מחזירה מחרוזת url שאותה יש לשלוף כדי לקבל את המסמך. כתובות התוויות האלה יושבות על נתיבי ה-web של האפליקציה ואינן מאומתות — כל מי שמחזיק בכתובת כזו יכול להדפיס את התווית, ולכן התייחסו אליהן כאל סוד ואל תשבצו אותן בדפים שנחשפים ללקוחות.

על מה ה-URL מצביע ​

איזו כתובת תקבלו תלוי בשיטת התווית האפקטיבית של המשלוח — הגדרת הספק label_method של הרישיון, עם נפילה חזרה ל-label_method של חשבון הלקוח:

  • shipos — ShipOS מרנדר את התווית בעצמו. הכתובת היא https://app.shipos.co.il/shipping/label/{uuid}, שמפנה לרנדרר ומזרימה PDF מוטמע (שנוצר עם mPDF). לא מתבצעת שום קריאה לחברת השילוח בעת בניית התשובה.
  • כל ערך אחר — נעשה שימוש בתווית של חברת השילוח עצמה. ה-backend קורא ל-API התוויות של חברת השילוח בתוך הבקשה עצמה, והכתובת היא מה שהקריאה הזו מחזירה. אצל רוב חברות השילוח ShipOS מוריד את ה-PDF של חברת השילוח פעם אחת ומחזיר עותק משלו בכתובת https://app.shipos.co.il/storage/document/{uuid}.pdf; כמה חברות שילוח מחזירות במקום זאת קישור שמתארח אצלן. זו קריאה יוצאת סינכרונית לחברת השילוח, ולכן שתי נקודות הקצה האלה איטיות יותר מ-bulk-label ועלולות להיכשל יחד עם חברת השילוח.

POST /shipping/bulk-label מעולם אינו מתייעץ עם שיטת התווית: הוא מחזיר תמיד כתובת shipping/labels שמוגשת על ידי הרנדרר של ShipOS.

גודל נייר ופורמט ​

אף אחת משלוש נקודות הקצה אינה מקבלת פרמטר של גודל נייר או פורמט. הרנדרר של ShipOS מחליט לכל משלוח בנפרד לפי סוג התווית האפקטיבי — הגדרת הספק label_type של הרישיון, עם נפילה חזרה לזו של הלקוח:

  • A4 — פרוס בסידור של 8 תוויות לעמוד על דפי A4.
  • כל ערך אחר (ברירת המחדל, תרמית) — עמוד אחד ברוחב 74 מ"מ לכל חבילה, כשגובה העמוד מחושב מהתוכן המרונדר. משלוח עם packages: 3 מפיק שלושה עמודים.

קבוצה מעורבת אינה בעיה: PDF יחיד נושא את שני הפורמטים, כל משלוח בפורמט שלו.

כתובת התווית המרונדרת אכן מקבלת כמה פרמטרי שאילתה, שאותם אפשר להוסיף ל-url שקיבלתם לפני שאתם שולפים אותו. הם חלים רק על הרנדרר של ShipOS (shipping/label/{uuid} ו-shipping/labels), ולא על כתובת שמתארחת אצל חברת השילוח:

שדהסוגחובהתיאור
outputstringלאhtml מרנדר את התוויות כדף HTML במקום כ-PDF. כל ערך אחר (או השמטה) מחזיר את ה-PDF.
positionintegerלאעבור A4, המשבצת בגיליון (מבוססת 1) שממנה להתחיל להדפיס, כדי לאפשר שימוש חוזר בגיליון מדבקות שנוצל חלקית. position - 1 משבצות נשארות ריקות.
enable_sort_by_warehousestringלאtrue ממיין את התוויות לפי מקום המחסן.
sort_by_warehousestringלאכיוון המיון הזה, asc (ברירת מחדל) או desc.

POST /shipping/label ​

החזרת כתובת התווית של משלוח אחד, לפי קוד המעקב של חברת השילוח שלו.

פרמטרים ​

Body ​

שדהסוגחובהתיאור
license_keystringכןה-key של הרישיון. נבדק על ידי middleware הרישיון.
domainstringכןהדומיין של החנות הקוראת. נדרש על ידי ה-middleware.
shipping_idstringכןקוד המעקב של חברת השילוח (shippings.shipping_code). לא ה-uuid.
tokenstringלאאסימון גישה אישי של Sanctum. כאשר הוא נשלח ותקין, החיפוש מתרחב מהרישיון היחיד לכל הרישיונות שבבעלות המשתמש של האסימון.

המשלוח חייב להיות שייך לרישיון שנקבע — בהתאמה על license_id של המשלוח עצמו או על license_id של ההזמנה שלו. is_active אינו חלק מהחיפוש: משלוח שבוטל עדיין נפתר ועדיין מקבל כתובת תווית עובדת.

בקשה לדוגמה ​

bash
curl --location 'https://app.shipos.co.il/api/shipping/label' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
    "license_key": "{license_key}",
    "domain": "your-store.example.com",
    "shipping_id": "66747921"
}'
js
// Node.js 18+ / דפדפנים — בלי תלויות
const response = await fetch('https://app.shipos.co.il/api/shipping/label', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    license_key: process.env.SHIPOS_LICENSE_KEY,
    domain: process.env.SHIPOS_DOMAIN,
    shipping_id: '66747921',
  }),
})

const body = await response.json()

// חברת שילוח שלא החזירה תווית משיבה באובייקט { error } חשוף,
// ולכן בדקו אותו לפני קריאת המעטפת.
if (body.error) {
  throw new Error(`ShipOS v1 label: ${body.error}`)
}

if (!response.ok || !body.success) {
  throw new Error(`ShipOS v1 label: ${body.message}`)
}

console.log(body.data.url) // שלפו את הכתובת הזו כדי לקבל את ה-PDF
php
<?php

use Illuminate\Support\Facades\Http;

$body = Http::acceptJson()
    ->post('https://app.shipos.co.il/api/shipping/label', [
        'license_key' => config('services.shipos.license_key'),
        'domain' => config('services.shipos.domain'),
        'shipping_id' => '66747921',
    ])
    ->json();

if (isset($body['error'])) {
    throw new RuntimeException('ShipOS v1 label: '.$body['error']);
}

if (! ($body['success'] ?? false)) {
    throw new RuntimeException('ShipOS v1 label: '.$body['message']);
}

logger()->info('Label URL', ['url' => $body['data']['url']]);

תשובה לדוגמה ​

תשובה 200, תווית שרונדרה על ידי ShipOS:

json
{
  "data": {
    "url": "https://app.shipos.co.il/shipping/label/9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"
  },
  "message": "",
  "success": true,
  "warning": ""
}

תשובה 200, תווית של חברת השילוח שהורדה ומוגשת מחדש על ידי ShipOS:

json
{
  "data": {
    "url": "https://app.shipos.co.il/storage/document/9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88.pdf"
  },
  "message": "",
  "success": true,
  "warning": ""
}

שני מבני כשל שאינם המעטפת

נקודת קצה זו יכולה להשיב מחוץ למעטפת בעלת ארבעת המפתחות:

  • אין משלוח כזה (או משלוח שאינו בבעלות הרישיון שנקבע) — החיפוש משתמש ב-firstOrFail(), ולכן התשובה היא ה-JSON של Laravel ל"מודל לא נמצא" ב-HTTP 404: {"message": "No query results for model [App\\Models\\Shipping]."}. אין מפתח success.
  • חברת השילוח לא החזירה תווית — הקריאה לחברת השילוח הפיקה URL ריק והתשובה היא HTTP 200 עם {"error": "Invalid response from shipping company"} חשוף. בלי success, בלי data. לקוח שקורא רק את success יתייחס לזה ככשל כברירת מחדל (המפתח נעדר), אבל אסור לו להתייחס לזה כאל URL.

shipping_id אינו עובר ולידציה, ולכן ערך חסר או ריק נופל ל-404 שלמעלה ולא ל-422.


POST /shipping/order-label ​

החזרת כתובת תווית אחת לכל משלוח פעיל בהזמנה — נקודת הקצה שיש לקרוא לה כאשר הזמנה פוצלה לכמה חבילות או משלוחים ואתם רוצים להדפיס את כולם.

פרמטרים ​

Body ​

שדהסוגחובהתיאור
license_keystringכןה-key של הרישיון.
domainstringכןהדומיין של החנות הקוראת.
order_idstringכןמזהה ההזמנה בחנות. מותאם או מול orders.order_id או מול orders.order_number, כך שגם מזהה הפלטפורמה וגם המספר שמוצג למשתמש עובדים. ההתאמה העדכנית ביותר מנצחת.

ההזמנה חייבת להיות שייכת לרישיון שנקבע, או להיות הזמנה בלי רישיון ששייכת ללקוח של הרישיון. בשונה משתי נקודות הקצה האחרות, order-label אינו מכבד שדה token — החיפוש תחום תמיד לרישיון היחיד שנקבע.

רק משלוחים עם is_active = true נכללים. משלוחים שבוטלו מושמטים.

בקשה לדוגמה ​

bash
curl --location 'https://app.shipos.co.il/api/shipping/order-label' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
    "license_key": "{license_key}",
    "domain": "your-store.example.com",
    "order_id": "19602"
}'
js
// Node.js 18+ / דפדפנים — בלי תלויות
const response = await fetch('https://app.shipos.co.il/api/shipping/order-label', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    license_key: process.env.SHIPOS_LICENSE_KEY,
    domain: process.env.SHIPOS_DOMAIN,
    order_id: '19602',
  }),
})

const body = await response.json()

if (!response.ok || !body.success) {
  // 404 "Order not found" / "No active shippings found for this order"
  throw new Error(`ShipOS v1 order labels: ${body.message}`)
}

for (const label of body.data.labels) {
  console.log(label.shipping_code, label.url)
}
php
<?php

use Illuminate\Support\Facades\Http;

$body = Http::acceptJson()
    ->post('https://app.shipos.co.il/api/shipping/order-label', [
        'license_key' => config('services.shipos.license_key'),
        'domain' => config('services.shipos.domain'),
        'order_id' => '19602',
    ])
    ->json();

if (! ($body['success'] ?? false)) {
    throw new RuntimeException('ShipOS v1 order labels: '.$body['message']);
}

foreach ($body['data']['labels'] as $label) {
    logger()->info($label['shipping_code'].' → '.$label['url']);
}

תשובה לדוגמה ​

תשובה 200:

json
{
  "data": {
    "labels": [
      {
        "shipping_code": "66747921",
        "url": "https://app.shipos.co.il/shipping/label/9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"
      },
      {
        "shipping_code": "66747922",
        "url": "https://app.shipos.co.il/storage/document/1d5e8a2c-9f0b-4e11-8c33-6a7b8c9d0e12.pdf"
      }
    ]
  },
  "message": "",
  "success": true,
  "warning": ""
}

כל רשומה נפתרת באופן עצמאי, ולכן רשימה מעורבת היא דבר נורמלי: משלוח אחד יכול לשאת כתובת שרונדרה על ידי ShipOS והבא אחריו כתובת של חברת השילוח, בהתאם לרישיון של כל משלוח.

תוצאות חלקיות הן שקטות

משלוח שפתירת התווית שלו הפיקה URL ריק מושמט מהמערך בלי אזהרה — warning נשאר ריק ו-success נשאר true. השוו את labels.length למספר המשלוחים שאתם מצפים לו, במקום להניח שהרשימה שלמה. רק כאשר כל המשלוחים נכשלים נקודת הקצה מדווחת על כשל.

שגיאות ​

סטטוסmessageמתי
403Invalid License Key / License key has been deactivatedבדיקת הרישיון נכשלה ב-middleware.
404Order not foundאין הזמנה שמתאימה ל-order_id (או ל-order_number) תחת הרישיון שנקבע.
404No active shippings found for this orderההזמנה קיימת אבל אין לה משלוח עם is_active = true.
500Invalid response from shipping companyפתירת התווית של כל אחד מהמשלוחים החזירה URL ריק.

order_id חסר הוא כשל ולידציה של Laravel: HTTP 422 עם {"message": …, "errors": {"order_id": […]}}, ולא מעטפת v1.


POST /shipping/bulk-label ​

החזרת URL יחיד שמדפיס משלוחים רבים כמסמך אחד. זו הזולה מבין השלוש: היא אינה מבצעת שום קריאה לחברת שילוח, אלא רק פותרת את הקודים ששלחתם ל-uuids ובונה את כתובת הרנדרר.

פרמטרים ​

Body ​

שדהסוגחובהתיאור
license_keystringכןה-key של הרישיון.
domainstringכןהדומיין של החנות הקוראת.
shipping_codesarrayכןקודי מעקב של חברת השילוח. מינימום 1, מקסימום 100.
shipping_codes.*stringכןערך shippings.shipping_code.
tokenstringלאאסימון גישה אישי של Sanctum. כאשר הוא נשלח ותקין, החיפוש מתרחב לכל הרישיונות שבבעלות המשתמש של האסימון.

קודים לא מזוהים מושמטים בשקט

קודים שאינם נפתרים למשלוח תחת הרישיון שנקבע פשוט נשארים מחוץ לכתובת, והתשובה עדיין 200 עם success: true. משלוחים שבוטלו אינם מושמטים — bulk-label אינה מפעילה סינון is_active.

אם אף אחד מהם אינו נפתר תקבלו כתובת חשופה https://app.shipos.co.il/shipping/labels בלי query string בכלל. הכתובת הזו אינה נכשלת כשפותחים אותה: כששולפים אותה היא מפנה לדף ההתחברות ומחזירה HTTP 200 עם גוף HTML. צד קורא שבודק רק את קוד הסטטוס יראה 200 ובלי PDF, וצד קורא שכותב את הגוף לקובץ .pdf יכתוב לתוכו דף התחברות. ספרו את ה-ids בכתובת שהוחזרה — או ודאו שה-Content-Type של המסמך שנשלף הוא באמת application/pdf — אם אתם צריכים לדעת שכל מה שביקשתם נכלל.

בקשה לדוגמה ​

bash
curl --location 'https://app.shipos.co.il/api/shipping/bulk-label' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
    "license_key": "{license_key}",
    "domain": "your-store.example.com",
    "shipping_codes": ["66747921", "66747922"]
}'
js
// Node.js 18+ / דפדפנים — בלי תלויות
const shippingCodes = ['66747921', '66747922']

const response = await fetch('https://app.shipos.co.il/api/shipping/bulk-label', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    license_key: process.env.SHIPOS_LICENSE_KEY,
    domain: process.env.SHIPOS_DOMAIN,
    shipping_codes: shippingCodes,
  }),
})

const body = await response.json()

if (!response.ok || !body.success) {
  throw new Error(`ShipOS v1 bulk label: ${body.message}`)
}

// קודים שלא נפתרו נעדרים מהכתובת, ולכן ודאו את המספר.
const included = new URL(body.data.url).searchParams
const found = [...included.keys()].filter((key) => key.startsWith('ids[')).length

if (found !== shippingCodes.length) {
  console.warn(`Only ${found} of ${shippingCodes.length} shipments were included`)
}

console.log(body.data.url)
php
<?php

use Illuminate\Support\Facades\Http;

$shippingCodes = ['66747921', '66747922'];

$body = Http::acceptJson()
    ->post('https://app.shipos.co.il/api/shipping/bulk-label', [
        'license_key' => config('services.shipos.license_key'),
        'domain' => config('services.shipos.domain'),
        'shipping_codes' => $shippingCodes,
    ])
    ->json();

if (! ($body['success'] ?? false)) {
    throw new RuntimeException('ShipOS v1 bulk label: '.$body['message']);
}

parse_str((string) parse_url($body['data']['url'], PHP_URL_QUERY), $query);

if (count($query['ids'] ?? []) !== count($shippingCodes)) {
    logger()->warning('Not every shipment was included in the bulk label URL');
}

logger()->info('Bulk label URL', ['url' => $body['data']['url']]);

תשובה לדוגמה ​

תשובה 200:

json
{
  "data": {
    "url": "https://app.shipos.co.il/shipping/labels?ids%5B0%5D=9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88&ids%5B1%5D=1d5e8a2c-9f0b-4e11-8c33-6a7b8c9d0e12"
  },
  "message": "",
  "success": true,
  "warning": ""
}

ה-ids שבכתובת הם uuids של משלוחים, ולא ה-shipping_codes ששלחתם — התרגום הזה הוא כל תפקידה של נקודת הקצה. שליפת הכתובת מזרימה PDF מוטמע אחד שמכיל את כל התוויות, בסדר שמתחיל מהמשלוח הוותיק ביותר.

שגיאות ​

סטטוסגוףמתי
403מעטפת v1, Invalid License Key / License key has been deactivatedבדיקת הרישיון נכשלה ב-middleware.
422{"message": …, "errors": {"shipping_codes": […]}}shipping_codes חסר, אינו מערך, ריק, או ארוך מ-100. זה מבנה הולידציה של Laravel, ולא מעטפת v1.