תוויות
שליפת התווית להדפסה של משלוח. ב-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/label | shipping_id יחיד (קוד המעקב של חברת השילוח) | { "url": "…" } | כן |
POST /shipping/order-label | order_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), ולא על כתובת שמתארחת אצל חברת השילוח:
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
output | string | לא | html מרנדר את התוויות כדף HTML במקום כ-PDF. כל ערך אחר (או השמטה) מחזיר את ה-PDF. |
position | integer | לא | עבור A4, המשבצת בגיליון (מבוססת 1) שממנה להתחיל להדפיס, כדי לאפשר שימוש חוזר בגיליון מדבקות שנוצל חלקית. position - 1 משבצות נשארות ריקות. |
enable_sort_by_warehouse | string | לא | true ממיין את התוויות לפי מקום המחסן. |
sort_by_warehouse | string | לא | כיוון המיון הזה, asc (ברירת מחדל) או desc. |
POST /shipping/label
החזרת כתובת התווית של משלוח אחד, לפי קוד המעקב של חברת השילוח שלו.
פרמטרים
Body
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ה-key של הרישיון. נבדק על ידי middleware הרישיון. |
domain | string | כן | הדומיין של החנות הקוראת. נדרש על ידי ה-middleware. |
shipping_id | string | כן | קוד המעקב של חברת השילוח (shippings.shipping_code). לא ה-uuid. |
token | string | לא | אסימון גישה אישי של Sanctum. כאשר הוא נשלח ותקין, החיפוש מתרחב מהרישיון היחיד לכל הרישיונות שבבעלות המשתמש של האסימון. |
המשלוח חייב להיות שייך לרישיון שנקבע — בהתאמה על license_id של המשלוח עצמו או על license_id של ההזמנה שלו. is_active אינו חלק מהחיפוש: משלוח שבוטל עדיין נפתר ועדיין מקבל כתובת תווית עובדת.
בקשה לדוגמה
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"
}'// 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
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:
{
"data": {
"url": "https://app.shipos.co.il/shipping/label/9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88"
},
"message": "",
"success": true,
"warning": ""
}תשובה 200, תווית של חברת השילוח שהורדה ומוגשת מחדש על ידי ShipOS:
{
"data": {
"url": "https://app.shipos.co.il/storage/document/9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88.pdf"
},
"message": "",
"success": true,
"warning": ""
}שני מבני כשל שאינם המעטפת
נקודת קצה זו יכולה להשיב מחוץ למעטפת בעלת ארבעת המפתחות:
- אין משלוח כזה (או משלוח שאינו בבעלות הרישיון שנקבע) — החיפוש משתמש ב-
firstOrFail(), ולכן התשובה היא ה-JSON של Laravel ל"מודל לא נמצא" ב-HTTP404:{"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_key | string | כן | ה-key של הרישיון. |
domain | string | כן | הדומיין של החנות הקוראת. |
order_id | string | כן | מזהה ההזמנה בחנות. מותאם או מול orders.order_id או מול orders.order_number, כך שגם מזהה הפלטפורמה וגם המספר שמוצג למשתמש עובדים. ההתאמה העדכנית ביותר מנצחת. |
ההזמנה חייבת להיות שייכת לרישיון שנקבע, או להיות הזמנה בלי רישיון ששייכת ללקוח של הרישיון. בשונה משתי נקודות הקצה האחרות, order-label אינו מכבד שדה token — החיפוש תחום תמיד לרישיון היחיד שנקבע.
רק משלוחים עם is_active = true נכללים. משלוחים שבוטלו מושמטים.
בקשה לדוגמה
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"
}'// 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
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:
{
"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 | מתי |
|---|---|---|
| 403 | Invalid License Key / License key has been deactivated | בדיקת הרישיון נכשלה ב-middleware. |
| 404 | Order not found | אין הזמנה שמתאימה ל-order_id (או ל-order_number) תחת הרישיון שנקבע. |
| 404 | No active shippings found for this order | ההזמנה קיימת אבל אין לה משלוח עם is_active = true. |
| 500 | Invalid response from shipping company | פתירת התווית של כל אחד מהמשלוחים החזירה URL ריק. |
order_id חסר הוא כשל ולידציה של Laravel: HTTP 422 עם {"message": …, "errors": {"order_id": […]}}, ולא מעטפת v1.
POST /shipping/bulk-label
החזרת URL יחיד שמדפיס משלוחים רבים כמסמך אחד. זו הזולה מבין השלוש: היא אינה מבצעת שום קריאה לחברת שילוח, אלא רק פותרת את הקודים ששלחתם ל-uuids ובונה את כתובת הרנדרר.
פרמטרים
Body
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ה-key של הרישיון. |
domain | string | כן | הדומיין של החנות הקוראת. |
shipping_codes | array | כן | קודי מעקב של חברת השילוח. מינימום 1, מקסימום 100. |
shipping_codes.* | string | כן | ערך shippings.shipping_code. |
token | string | לא | אסימון גישה אישי של 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 — אם אתם צריכים לדעת שכל מה שביקשתם נכלל.
בקשה לדוגמה
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"]
}'// 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
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:
{
"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. |