משלוחים
נקודות הקצה של המשלוחים בגרסה הקודמת v1: יצירת משלוח מול חברת השילוח, שליפת פרטיו והסטטוס שלו, ביטולו, והצגת רשימת המשלוחים שנוצרו כבר עבור אוסף הזמנות. אלו נקודות הקצה שהתוסף של WooCommerce והאינטגרציות הישירות הישנות קוראים אליהן. הן קפואות — הן מקבלות תיקונים, לא פיצ'רים.
כל נקודת קצה בעמוד זה היא POST תחת https://app.shipos.co.il/api, מקבלת את פרטי האימות שלה בגוף הבקשה ולא בכותרות, ומשיבה במעטפת v1 המתוארת בהמשך. המקבילה בגרסה v2 לעמוד זה היא משלוחים; אינטגרציות חדשות צריכות להשתמש בה.
שני ערכי shipping_id שונים
open_new_order מחזירה data.shipping_id — מזהה השורה המספרי הפנימי שלנו. הפרמטר shipping_id שמקבלות get_order_details, get_shipping_status ו-change_order_status הוא קוד המעקב של חברת השילוח (shippings.shipping_code, מוחזר כ-data.code). העברת המזהה המספרי לשלוש נקודות הקצה האלה מחזירה 404.
אימות
חמש נקודות הקצה בעמוד זה שיושבות תחת הקידומת shipping מריצות את ה-middleware hasLicense, שמאמת את הרישיון, לא משתמש. אין כותרות X-Client-Id / X-Client-Secret ב-v1.
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | licenses.key — חשבון חברת השילוח שהקריאה פועלת תחתיו. |
domain | string | כן | הדומיין של החנות הקוראת. חייב להיות קיים ומטיפוס string; ההשוואה מול הדומיין של הרישיון עצמו מוערת בקוד (commented out) ב-CheckLicense, ולכן כל מחרוזת לא-ריקה מספקת אותה כיום. |
token | string | לא | טוקן גישה אישי של Sanctum. כשהוא קיים, המשתמש של הטוקן משמש לבדיקת החברה הפרימיום, וכן (ב-open_new_order) לזיהוי אתר ה-WooCommerce המחובר שההזמנה שייכת אליו. |
הרישיון חייב להתקיים ולהיות פעיל. אם החברה של הרישיון מסומנת כפרימיום, הקריאה דורשת בנוסף token שהמשתמש שלו מחזיק במנוי פרימיום פעיל.
TIP
license_key ו-domain מאומתים באמצעות המאמת של Laravel עצמו, ולכן חוסר של אחד מהם מפיק את גוף ברירת המחדל 422 של Laravel ({ "message": ..., "errors": {...} }), ולא את מעטפת v1. כל דחייה אחרת של hasLicense היא 403 במעטפת v1 — כולל "license not found", שהבדיקה שמתחת מסמנת כ-404 אך ה-middleware מחזיר תמיד כ-403.
קיים גם מעקף פנימי (first-party): בקשה הנושאת את מפתח האפליקציה כ-secret מדלגת לחלוטין על בדיקות הדומיין והפרימיום. הוא אינו זמין לאינטגרטורים.
מעטפת v1
כל handler בעמוד זה, למעט POST /shipping/order, בונה את הגוף שלו באמצעות ה-helper apiResponse():
{
"data": {},
"message": "",
"success": true,
"warning": ""
}| שדה | סוג | תיאור |
|---|---|---|
data | mixed | ה-payload. המבנה משתנה בין נקודות הקצה — אובייקט, מערך, שורת מודל, או null. |
message | string | תוצאה קריאה לאדם. בכשלים של חברת השילוח זו ההודעה של חברת השילוח עצמה, מילה במילה (לעיתים בעברית), ולא מחרוזת של ShipOS. |
success | boolean | האם הפעולה הצליחה. |
warning | string | שמור לשימוש עתידי; ריק בכל נקודת קצה בעמוד זה. |
success וסטטוס ה-HTTP לא תמיד מסכימים
apiResponse() מחזירה כברירת מחדל HTTP 200, ושני מסלולים מסתמכים על ברירת המחדל הזו: change_order_status מחזירה ביטול שנכשל כ-HTTP 200 עם success: false, ו-get_order_details מחזירה שליפה שנכשלה מול חברת השילוח כ-HTTP 200 עם success: true והכשל מקונן בתוך data. הסתעפו לפי הגוף, לא לפי קוד הסטטוס לבדו.
כל מחרוזת message באנגלית שמצוטטת בעמוד זה היא מפתח תרגום, לא ערך שעובר על החוט
המחרוזות בעמוד הזה הן המפתחות כפי שהם מופיעים בקוד המקור. message מרונדר דרך שכבת התרגום, ולכן מה שמגיע בפועל על החוט נגזר מהלוקאל של השרת. שרת חי החזיר עברית עבור כולן: הודעות האימות, Something went wrong בתור אירעה שגיאה, Shipping status successfully updated בתור סטטוס המשלוח עודכן בהצלחה, והתווית המצורפת shipping_type בתור משלוח רגיל.
לעולם אל תבצעו התאמת מחרוזות על message. הסתעפו לפי סטטוס ה-HTTP, לפי success, ולפי קיומם של מפתחות שדה. השתמשו ב-message להצגה ולרישום בלוג בלבד.
כל הקבוצה מריצה גם את ה-middleware normalize_shipment_response. הוא עושה דבר אחד לגוף: כאשר data הוא מערך שמכיל מפתח code ברמה העליונה, הערך הזה מומר למחרוזת. מפתחות code מקוננים נשארים כפי שהם — code שיושב ב-data.data.code, ושם get_order_details מציבה אותו, אינו מומר וחוזר בדיוק כפי שחברת השילוח שלחה אותו (נצפה בשרת חי: ה-integer 2). חברות השילוח אינן עקביות (LionWheel מחזירה מזהה משימה מטיפוס integer), ולקוחות עם טיפוסים קפדניים נשברו על ה-integer החשוף. כל השאר מועבר בית-בבית כפי שהוא, כולל עברית. ה-middleware גם רושם את הבתים המדויקים שנשלחו בתשובה ב-api_request_logs.normalized_response, קטוע ל-60 000 בתים; השמירה הזו היא best-effort ולעולם לא תוכל להכשיל את הבקשה שלכם.
POST /shipping/{shipment}/status רשומה מחוץ לקבוצה הזו ואינה עוברת נורמליזציה בכלל — ראו את הסעיף שלה.
סוגי משלוח
השירות שהמשלוח מקבל מקודד בשני שדות של ship_data: type ו-return. אף אחד מהם אינו מאומת על-ידי ה-form request; המתאמים של חברות השילוח ו-Shipping::getType() קוראים אותם ישירות.
ship_data.type | ship_data.return | משמעות | shippings.type שנשמר | תווית shipping_type שמצורפת |
|---|---|---|---|---|
1 | 1 או חסר | משלוח רגיל | regular | Regular delivery |
1 | 2 | משלוח כפול — מסירה והבאת משהו בחזרה באותו הביקור | double | Double delivery |
2 | 1 או חסר | איסוף — איסוף מהנמען | collecting | Collecting delivery |
2 | 2 | איסוף שהתבקש עם רגל חזרה | double | Collecting delivery |
שתי ההצגות אינן מסכימות בשורה האחרונה
shippings.type מחושב על-ידי getType(), שבו return == 2 מנצח את כל השאר; התווית shipping_type מחושבת בנפרד, ושם type == 2 מנצח. כך ש-type: 2 עם return: 2 נשמר כ-double בעודו מתויג Collecting delivery. שלחו return: 1 (או השמיטו אותו) עבור איסוף רגיל כדי להישאר מחוץ לפינה הזו.
איסוף עם type: 2 דורש שתתקיים כתובת איסוף
איסוף נאסף מהנמען ונמסר לסוחר, ולכן חברת השילוח צריכה את כתובת האיסוף של הסוחר. הכתובת הזו מגיעה מהרישיון (collect_street, collect_street_number, collect_city, collect_company), ומ-shipment_settings שבבקשה רק כאשר לרישיון אין אחת.
כששניהם ריקים, בקשה עם ship_data.type = 2 נדחתה על-ידי חברת השילוח ב-HTTP 500 ובהודעה The params.to address.city field is required. — נוסח שמצביע על כתובת היעד ואינו נותן שום רמז שהחלק החסר הוא כתובת האיסוף. לכן אם איסוף נכשל בהודעת אימות על עיר או כתובת בזמן שכתובת הנמען ששלחתם שלמה, בדקו קודם את הגדרות האיסוף של הרישיון, או שלחו shipment_settings.
POST /shipping/open_new_order
יצירת משלוח מול חברת השילוח. שומרת את ההזמנה, את תמונת-מצב הנמען שלה ואת שורות הפריטים שלה, קוראת לחברת השילוח, ומחזירה את קוד המעקב. אימות: license_key + domain. רישיון: חובה.
זו נקודת הקצה היחידה ב-v1 שיש לה הגנה מפני כפילויות — קראו הגנה מפני כפילויות לפני שאתם בונים מעליה ניסיונות חוזרים.
פרמטרים
גוף הבקשה
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. מהווה גם חלק ממפתח האידמפוטנטיות. |
domain | string | כן | ראו אימות. |
token | string | לא | טוקן Sanctum. כשהוא נשלח, קובע את order.wp_site_id מתוך אתר ה-WooCommerce הרשום עבור domain. |
ship_data | object | כן | payload הפונה לחברת השילוח. ראו בהמשך. |
order | object | כן | מעטפת ההזמנה. ראו בהמשך. |
order_items | array | לא | שורות פריטים. ברמה העליונה, לא מקוננות תחת order — זו הצורה ש-v2 שינתה. |
order_items[].name | string | כן | חובה עבור כל איבר כאשר order_items קיים. |
order_items[].quantity | integer | כן | חובה עבור כל איבר. מינימום 1. |
order_items[].total | numeric | כן | חובה עבור כל איבר. |
shipment_settings | object | לא | דריסות לכתובת האיסוף: collect_street, collect_street_number, collect_city, collect_company. בשימוש רק כאשר לרישיון עצמו אין הגדרות איסוף — הרישיון תמיד מנצח. איסוף (ship_data.type = 2) דורש שאחד מהשניים יהיה מאוכלס — ראו את האזהרה בסוגי משלוח. |
code | string | לא | מועבר כפי שהוא למתאם חברת השילוח כהגדרת ה-code שלו. |
ששת הכללים האלה (ship_data, order, order_items ושלושת הכללים שלה לכל פריט) הם כל מערך הכללים המוצהר. כל מה שבתוך ship_data ו-order אינו מאומת מבחינת טיפוס, למעט חריג אחד: בדיקה חוצת-שדות דורשת שניתן יהיה לחלץ רחוב, עיר ושם איש קשר מתוך ship_data או מתוך order.billing, ודוחה את הבקשה אחרת. "ריק" שם פירושו null או רווחים בלבד — המחרוזת הליטרלית "0" נחשבת רחוב תקין, במכוון (לקוחות אכן מקלידים אותה).
ship_data
מועבר למתאם חברת השילוח ונשמר מילה במילה ב-shippings.shipment_data, ולכן אילו מפתחות משפיעים תלוי בחברת השילוח. אלו המפתחות שה-controller של v1 עצמו קורא, ממלא בברירת מחדל, או משכתב:
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
contact_name | string | מותנה | שם הנמען. נופל חזרה ל-order.billing.first_name + order.billing.last_name. נדחה רק אם כל שלושתם ריקים. |
contact_phone | string | לא | נופל חזרה ל-order.billing.phone. בדיקת הקיום מוערת בקוד, ולכן משלוח בלי טלפון בכלל מתקבל אצלנו ומושאר לחברת השילוח. |
contact_mail | string | לא | נופל חזרה ל-order.billing.email. |
street | string | מותנה | נופל חזרה ל-order.billing.address_1. חובה (לאחר הנפילה חזרה הזו). |
number | string | לא | מספר בית. כאשר סופק street, נופל חזרה ל-order.billing.address_2. |
city | string | מותנה | נופל חזרה ל-order.billing.city. חובה (לאחר הנפילה חזרה הזו). |
company | string | לא | נופל חזרה ל-order.billing.company. |
type | integer | כן | כיוון השירות. ראו סוגי משלוח. אינו מאומת, אך גם חברות השילוח וגם קביעת הסוג שלנו דורשות אותו. |
return | integer | כן | רגל החזרה. ראו סוגי משלוח. |
packages | integer | לא | מספר חבילות. |
pickup | string | לא | מזהה נקודת איסוף / נקודת חלוקה, עבור משלוחים לנקודת איסוף. |
pickup_address | string | לא | כתובת נקודת האיסוף בפורמט קריא. |
floor | string | לא | קומה. |
apartment | string | לא | דירה. |
entrance | string | לא | כניסה. |
house_no | string | לא | מספר בית, עבור חברות השילוח שקוראות את המפתח הזה במקום number. |
note | string | לא | הערת משלוח. עובר שינוי בשרת — ראו את ההערה בהמשך. |
extra_note | string | לא | הערה נוספת, עבור חברות השילוח שתומכות באחת. |
exaction_date | date (Y-m-d) | לא | תאריך איסוף מבוקש. ברירת המחדל היא היום כשהוא ריק או חסר. |
woo_id | string | לא | מזהה ההזמנה שלכם כפי שהוא מוצג לחברת השילוח. מקבל את ה-order_prefix של הרישיון כקידומת כאשר מוגדר כזה. |
urgent | boolean | לא | טיפול דחוף. |
motor | integer | לא | שליח אופנוע. |
collect | boolean | לא | דגל איסוף, במקומות שבהם חברת השילוח מבדילה אותו מ-type. |
is_cod / cod_amount | mixed | לא | גבייה במסירה (COD), עבור חברות השילוח שתומכות בה. |
IsManual | boolean | לא | מסמן את המשלוח כהוזן ידנית. |
ship_data.note אינו נשלח כפי ששלחתם אותו
לפני הקריאה לחברת השילוח, ההערה עשויה להתארך בסמן הערת-מתנה (כאשר order.meta_data מכיל רשומת letter עם ערך) ובסיכום מוצרים קומפקטי (כאשר הרישיון מפעיל את print_products_on_label_notes). אם send_note_on_order כבוי ברישיון, ההערה נקבעת ל-null ושום דבר ממה ששלחתם אינו מועבר. גם ship_data.shipping_id ו-ship_data.license_id נדרסים בצד השרת.
order
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
order.id | string | כן | מזהה ההזמנה שלכם. נשמר כ-orders.order_id, מותאם על-ידי POST /shipping/order, ומהווה חלק ממפתח המטמון של האידמפוטנטיות — ראו את האזהרה בהגנה מפני כפילויות. |
order.number | string | לא | מספר הזמנה קריא. ברירת המחדל היא order.id. מקבל את ה-order_prefix של הרישיון כקידומת כאשר מוגדר כזה. |
order.status | string | לא | סטטוס ההזמנה שלכם. נבדק מול רשימת הסטטוסים החסומים של הרישיון; סטטוס חסום נדחה עם 400. |
order.source | string | לא | תווית פלטפורמה, ברירת מחדל wordpress. חלק מזהות ההזמנה לצורך upsert. שליחתו גם מדלגת על דחיפת סטטוס ההזמנה ל-WooCommerce לאחר יצירה מוצלחת. |
order.shipping | object | לא | תמונת-מצב הנמען. כשהוא חסר, נעשה שימוש ב-order.billing במקומו. |
order.billing | object | לא | בלוק החיוב. גם מקור הנפילה חזרה עבור ship_data.street, number, city, company, contact_name, contact_phone ו-contact_mail. |
order.order_key | string | לא | מפתח ההזמנה בפלטפורמה. חלק מזהות ההזמנה לצורך upsert. |
order.shopify_id | mixed | לא | מזהה חנות Shopify. חלק מזהות ההזמנה לצורך upsert. |
order.wix_id | mixed | לא | מזהה אתר Wix. חלק מזהות ההזמנה לצורך upsert. |
order.wp_site_id | mixed | לא | מזהה אתר WooCommerce מחובר. חלק מזהות ההזמנה לצורך upsert; נדרס כאשר token מזהה אתר עבור domain. |
order.currency | string | לא | מטבע ISO. |
order.total | numeric | לא | סך ההזמנה. |
order.total_tax | numeric | לא | סך המס. |
order.discount_total | numeric | לא | סך ההנחה. |
order.discount_tax | numeric | לא | מס ההנחה. |
order.shipping_total | numeric | לא | סך דמי המשלוח. |
order.shipping_tax | numeric | לא | מס דמי המשלוח. |
order.shipping_lines | array | לא | שורות משלוח של הפלטפורמה. |
order.cart_tax | numeric | לא | מס העגלה. |
order.prices_include_tax | boolean | לא | האם המחירים כוללים מס. |
order.payment_method | string | לא | אמצעי תשלום. |
order.meta_data | array | לא | זוגות { key, value }. מפתח letter עם ערך שאינו null מוסיף סמן הערת-מתנה להערה שנשלחת לחברת השילוח. |
כל מפתח שאתם שולחים בתוך order ואינו מופיע ברשימה שלמעלה נשמר כמטא-נתון של ההזמנה ולא נזרק.
order.shipping ו-order.billing מקבלים שניהם first_name, last_name, company, address_1, address_2, city, state, postcode, country ו-phone; order.billing.email נקרא בנוסף כנפילה חזרה לדוא"ל איש הקשר. כל מפתח אחר בתוך order.shipping נשמר כמטא-נתון של המשלוח. מספר הבית מנותח מתוך address_1 (או address_2) ונשמר בנפרד.
order_items[] מקבל גם id, variation_id, variation_name, sku, price, image, is_virtual, gallery_images ו-meta_data מעבר לשלושת השדות המאומתים.
הגנה מפני כפילויות
היצירה מוגנת על-ידי שחזור מהמטמון בתוספת מנעול, כששניהם ממופתחים לפי הרישיון וההזמנה:
| ערך | |
|---|---|
| מפתח מטמון השחזור | shipment_response_{license_key}_{order.id} |
| TTL של המטמון | שעה אחת |
| שם המנעול | wp_create_shipping_{license_key}_{order.id} |
| TTL של המנעול | 120 שנ' |
| חלון ההמתנה (block) | 120 שנ' |
מה שהצד הקורא רואה:
- ניסיון חוזר בתוך אותה שעה משחזר את התשובה הראשונה מילה במילה — אותו גוף, אותו סטטוס HTTP — בתוספת
data.idempotent: true. לא מתבצעת קריאה לחברת השילוח, לא נוצר משלוח שני, ואף אחת מתופעות הלוואי שלאחר היצירה (SMS על מסירה, עדכוני הזמנה בפלטפורמה, מאזיניShippingCreated) אינה נורית שוב. הדגל הזה הוא האות שלכם לדלג גם על תופעות הלוואי שלכם. - רק יצירות מוצלחות נשמרות במטמון. ניסיון שנכשל אינו משאיר דבר אחריו, ולכן הוא נשאר בר-ניסיון-חוזר.
- ניסיון חוזר מקבילי ממתין במקום להיכשל. הוא חונה על המנעול עד 120 שנ', וכשהמקורי מסתיים הוא לוקח את מסלול השחזור. שתי הקלקות במרווח של שנייה מפיקות משלוח אחד ושתי תשובות זהות.
- אם חלון ההמתנה פג, תקבלו
409עםAnother shipment request for this order is in progress. Please try again.— הבקשה אינה מנוסה. נסו שוב לאחר הפסקה. - לאחר השעה, ניתן לשלוח את אותה הזמנה שוב. במכוון אין בדיקת כפילויות ברמת בסיס הנתונים: סוחרים יוצרים באופן לגיטימי משלוח שני להזמנה אחת ימים או שבועות אחר כך. האידמפוטנטיות ב-v1 היא חלון של שעה, לא חלון קבוע.
המפתח אינו כולל את סוג המשלוח
shipment_response_{license_key}_{order.id} אינו נושא שום סוג שירות. צרו משלוח רגיל להזמנה 1042 ואז, בתוך אותה שעה, בקשו איסוף להזמנה 1042: הקריאה השנייה תשחזר את תשובת המשלוח ולא ייווצר שום איסוף. זו פינה חדה מוכרת. אם אתם צריכים שני סוגי שירות להזמנה אחת, המתינו עד שה-TTL יפוג או שנו את ה-order.id שאתם שולחים.
order.id חסר מכווץ את המפתח
order.id נקרא כ-order.id ?? '', ולכן בקשה בלעדיו משתמשת במפתח shipment_response_{license_key}_. כל בקשה חסרת-מזהה עבור אותו רישיון חולקת את המפתח היחיד הזה ותשחזר את התשובות של האחרות. שלחו תמיד order.id.
דוגמת בקשה
curl --location 'https://app.shipos.co.il/api/shipping/open_new_order' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"license_key": "{license_key}",
"domain": "your-store.example.com",
"ship_data": {
"contact_name": "ישראל ישראלי",
"contact_phone": "0521234567",
"contact_mail": "dana@example.com",
"street": "הרצל",
"number": "10",
"city": "תל אביב",
"floor": "3",
"apartment": "12",
"type": 1,
"return": 1,
"packages": 1,
"note": "להשאיר אצל השוער",
"woo_id": "1042"
},
"order": {
"id": "1042",
"number": "1042",
"status": "processing",
"source": "custom",
"currency": "ILS",
"total": 199.90,
"billing": {
"first_name": "Dana",
"last_name": "Levi",
"phone": "0521234567",
"email": "dana@example.com",
"address_1": "הרצל 10",
"city": "תל אביב",
"postcode": "6100000",
"country": "IL"
},
"shipping": {
"first_name": "Dana",
"last_name": "Levi",
"phone": "0521234567",
"address_1": "הרצל 10",
"city": "תל אביב",
"postcode": "6100000",
"country": "IL"
}
},
"order_items": [
{ "sku": "SKU-1", "name": "ספל כחול", "quantity": 2, "total": 99.90 }
]
}'// Node.js 18+ / דפדפנים — בלי תלויות
const response = await fetch('https://app.shipos.co.il/api/shipping/open_new_order', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: 'your-store.example.com',
ship_data: {
contact_name: 'ישראל ישראלי',
contact_phone: '0521234567',
contact_mail: 'dana@example.com',
street: 'הרצל',
number: '10',
city: 'תל אביב',
floor: '3',
apartment: '12',
type: 1,
return: 1,
packages: 1,
note: 'להשאיר אצל השוער',
woo_id: '1042',
},
order: {
id: '1042',
number: '1042',
status: 'processing',
source: 'custom',
currency: 'ILS',
total: 199.9,
billing: {
first_name: 'Dana',
last_name: 'Levi',
phone: '0521234567',
email: 'dana@example.com',
address_1: 'הרצל 10',
city: 'תל אביב',
postcode: '6100000',
country: 'IL',
},
shipping: {
first_name: 'Dana',
last_name: 'Levi',
phone: '0521234567',
address_1: 'הרצל 10',
city: 'תל אביב',
postcode: '6100000',
country: 'IL',
},
},
order_items: [{ sku: 'SKU-1', name: 'ספל כחול', quantity: 2, total: 99.9 }],
}),
})
const payload = await response.json()
// v1 מסמנת כשל בגוף התשובה; דחייה של חברת השילוח היא 500 עם
// ההודעה של חברת השילוח עצמה, ושחזור הוא 200 עם data.idempotent.
if (!payload.success) {
throw new Error(payload.message)
}
if (payload.data.idempotent) {
console.log('replayed existing shipment', payload.data.code)
} else {
console.log('created', payload.data.code)
}<?php
use Illuminate\Support\Facades\Http;
$payload = Http::acceptJson()
->post('https://app.shipos.co.il/api/shipping/open_new_order', [
'license_key' => config('services.shipos.license_key'),
'domain' => 'your-store.example.com',
'ship_data' => [
'contact_name' => 'ישראל ישראלי',
'contact_phone' => '0521234567',
'contact_mail' => 'dana@example.com',
'street' => 'הרצל',
'number' => '10',
'city' => 'תל אביב',
'floor' => '3',
'apartment' => '12',
'type' => 1,
'return' => 1,
'packages' => 1,
'note' => 'להשאיר אצל השוער',
'woo_id' => '1042',
],
'order' => [
'id' => '1042',
'number' => '1042',
'status' => 'processing',
'source' => 'custom',
'currency' => 'ILS',
'total' => 199.90,
'billing' => [
'first_name' => 'Dana',
'last_name' => 'Levi',
'phone' => '0521234567',
'email' => 'dana@example.com',
'address_1' => 'הרצל 10',
'city' => 'תל אביב',
'postcode' => '6100000',
'country' => 'IL',
],
'shipping' => [
'first_name' => 'Dana',
'last_name' => 'Levi',
'phone' => '0521234567',
'address_1' => 'הרצל 10',
'city' => 'תל אביב',
'postcode' => '6100000',
'country' => 'IL',
],
],
'order_items' => [
['sku' => 'SKU-1', 'name' => 'ספל כחול', 'quantity' => 2, 'total' => 99.90],
],
])
->json();
if (! $payload['success']) {
throw new RuntimeException($payload['message']);
}
logger()->info('shipment '.$payload['data']['code'], [
'replay' => $payload['data']['idempotent'] ?? false,
]);תשובה 200
{
"data": {
"status": 1,
"code": "66747921",
"response_status_code": 200,
"shipping_id": 918342,
"order_id": 771204,
"shipping_uuid": "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88",
"effective_label_method": "shipos",
"effective_label_type": "default",
"shipping_line": "12",
"shipping_line_desc": "תל אביב south",
"line_branch": null,
"tracking_url": "https://track.example-carrier.co.il/66747921",
"price": null,
"company": {
"name": "HFD",
"logo": "https://app.shipos.co.il/storage/companies/hfd.png"
}
},
"message": "Shipping successfully created, shipping number: 66747921",
"success": true,
"warning": ""
}| שדה | סוג | תיאור |
|---|---|---|
data.status | integer | הסטטוס של מתאם חברת השילוח. 1 פירושו נוצר; ממנו נגזר success. |
data.code | string | קוד המעקב של חברת השילוח. תמיד מחרוזת — הנורמליזציה ממירה אותו. השתמשו בו כפרמטר shipping_id של נקודות הקצה האחרות. |
data.response_status_code | mixed | null | שדה הסטטוס של חברת השילוח עצמה, כשהיא החזירה אחד. |
data.shipping_id | integer | ה-shippings.id הפנימי שלנו. מתקבל על-ידי POST /shipping/{shipment}/status, ולא על-ידי נקודות הקצה שמקבלות פרמטר shipping_id. |
data.order_id | integer | ה-orders.id הפנימי שלנו. |
data.shipping_uuid | string | ה-uuid של המשלוח, שבשימוש כתובת התווית של ShipOS. |
data.effective_label_method | string | מאיפה מגיעה התווית: shipos (אנחנו מרנדרים אותה) או shipping_company (חברת השילוח מרנדרת אותה). |
data.effective_label_type | string | נייר התווית: default (תרמי) או A4. |
data.shipping_line | string | null | קו/מסלול של חברת השילוח, כאשר היא מדווחת עליו. |
data.shipping_line_desc | string | null | תיאור הקו; null עבור חברות שילוח שאינן מדווחות עליו. |
data.line_branch | string | null | הסניף של הקו; null עבור חברות שילוח שאינן מדווחות עליו. |
data.tracking_url | string | null | כתובת המעקב של חברת השילוח הפונה לשולח, כשהיא מוחזרת. |
data.price | mixed | null | המחיר שחברת השילוח נקבה, כשהוא מוחזר. |
data.company | object | { name, logo } של חברת השילוח שמאחורי הרישיון. |
data.idempotent | boolean | קיים רק בשחזור, תמיד true. חסר ביצירה חדשה. |
ה-message של המעטפת אינו ריק בהצלחה. הוא עוקב אחר התבנית Shipping successfully created, shipping number: {code} — נצפה בשרת חי כ-Shipping successfully created, shipping number: 68834253. זו מחרוזת מתורגמת, ולכן קראו את הקוד מ-data.code ולעולם אל תחלצו אותו מתוך ההודעה.
תשובה משוחזרת היא הגוף שלמעלה בתוספת "idempotent": true בתוך data, ועם סטטוס ה-HTTP המקורי — כולל אותו message מקורי שאינו ריק:
{
"data": {
"status": 1,
"code": "66747921",
"shipping_id": 918342,
"idempotent": true
},
"message": "Shipping successfully created, shipping number: 66747921",
"success": true,
"warning": ""
}דחייה של חברת השילוח מחזירה את אותו מערך שדות עם success: false, HTTP 500, וההודעה של חברת השילוח:
{
"data": {
"status": 0,
"code": "0",
"response_status_code": 200,
"shipping_id": 918343,
"order_id": 771204,
"shipping_uuid": "1c4d8e2a-5b6f-4a71-b0c3-9d8e7f6a5b41",
"effective_label_method": "",
"effective_label_type": "",
"shipping_line": "",
"company": { "name": "HFD", "logo": "https://app.shipos.co.il/storage/companies/hfd.png" }
},
"message": "The params.to address.city field is required.",
"success": false,
"warning": ""
}ארבעה פרטים במבנה הזה מפילים צדדים קוראים. data.code הוא המחרוזת "0" ולא "" — ולכן code שאינו ריק אינו סימן להצלחה. data.response_status_code הוא 200, מפני שקריאת ה-HTTP לחברת השילוח עצמה הצליחה והדחייה נמצאת בתוך הגוף של חברת השילוח; הוא אינו משקף את ה-500 שאתם מקבלים. ו-effective_label_method, effective_label_type ו-shipping_line כולם קיימים כמחרוזות ריקות ולא נעדרים, ולכן בדיקת קיום מפתחות לא תבדיל בין דחייה ליצירה. הסתעפו לפי success ולפי data.status.
TIP
שורת המשלוח שורדת דחייה של חברת השילוח רק בתשובה — הטרנזקציה מגולגלת חזרה, ולכן data.shipping_id ו-data.order_id מצביעים על מזהים שאינם קיימים יותר. התייחסו ליצירה עם success: false כאל "לא קרה כלום", ונסו שוב.
שגיאות
| סטטוס | message | מתי |
|---|---|---|
| 400 | You have reached the utilization of the package | מכסת המשלוחים החודשית של הלקוח עבור החבילה שלו מוצתה. |
| 400 | Shipment creation is blocked for orders with status: :status | הרישיון מפעיל את מסנן סטטוס ההזמנות ו-order.status נמצא ברשימת החסומים שלו. |
| 403 | Invalid License Key | אין רישיון עם ה-license_key הזה. (מוחזר כ-403, לא כ-404.) |
| 403 | License key has been deactivated | הרישיון אינו פעיל. |
| 403 | Premium companies are not supported in this version of plugin | החברה של חברת השילוח היא פרימיום ואף token לא זיהה משתמש. |
| 403 | You must be a premium member to use this company | חברה פרימיום, משתמש בלי מנוי פרימיום פעיל. |
| 409 | Another shipment request for this order is in progress. Please try again. | חלון ההמתנה של המנעול פג — יצירה מקבילית עבור אותו רישיון + הזמנה עדיין רצה. לא נוצר דבר. |
| 422 | הודעת האימות הראשונה, כשכולן תחת data.errors | ship_data / order חסרים, איבר order_items חסר שדות, או שלא ניתן לחלץ רחוב, עיר או שם איש קשר. משתמשת במעטפת v1. |
| 422 | גוף ברירת המחדל של Laravel | license_key או domain חסרים. משתמשת ב-{ "message": ..., "errors": {...} }, ולא במעטפת v1. |
| 500 | הודעת חברת השילוח, מילה במילה | חברת השילוח דחתה את המשלוח. success: false. |
| 500 | An unexpected error occurred while creating the shipment. Please try again or contact support. | חריגה בלתי מטופלת בתוך מסלול היצירה. |
POST /shipping/get_order_details
שליפת הפרטים העכשוויים של משלוח מצד חברת השילוח. שולפת מחברת השילוח בזמן אמת בכל קריאה ומחזירה את התוצאה הגולמית של המתאם — היא אינה מעדכנת דבר בצד שלנו. אימות: license_key + domain. רישיון: חובה.
פרמטרים
גוף הבקשה
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. |
domain | string | כן | ראו אימות. |
shipping_id | string | כן | קוד המעקב של חברת השילוח (data.code מ-open_new_order). מותאם מול shippings.shipping_code, מוגבל להזמנות השייכות לרישיון הזה. |
דוגמת בקשה
curl --location 'https://app.shipos.co.il/api/shipping/get_order_details' \
--header 'Accept: application/json' \
--header 'Content-Type: 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/get_order_details', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: 'your-store.example.com',
shipping_id: '66747921',
}),
})
const payload = await response.json()
// המעטפת כאן היא תמיד success:true — תוצאת חברת השילוח נמצאת בתוך data.
if (payload.data?.success !== true) {
throw new Error(payload.data?.message ?? 'carrier lookup failed')
}
console.log(payload.data.data.code, payload.data.data.text)<?php
use Illuminate\Support\Facades\Http;
$payload = Http::acceptJson()
->post('https://app.shipos.co.il/api/shipping/get_order_details', [
'license_key' => config('services.shipos.license_key'),
'domain' => 'your-store.example.com',
'shipping_id' => '66747921',
])
->json();
abort_unless(($payload['data']['success'] ?? false) === true, 502, 'carrier lookup failed');
logger()->info('status', $payload['data']['data']);תשובה 200
data הוא אובייקט התוצאה של מתאם חברת השילוח, לא עטוף — ולכן הסטטוס יושב ב-data.data. המפתחות שבתוכו תלויים בחברת השילוח; code ו-text הם השניים שכל מתאם ממלא.
{
"data": {
"data": {
"code": 2,
"text": "Delivered",
"receiver": "ישראל ישראלי"
},
"error": false,
"success": true
},
"message": "",
"success": true,
"warning": ""
}code אינו עובר נורמליזציה למחרוזת בנקודת הקצה הזו
normalize_shipment_response ממיר רק data.code ברמה העליונה, וכאן קוד חברת השילוח יושב רמה אחת עמוק יותר, ב-data.data.code. לכן הוא מגיע בדיוק כפי שחברת השילוח שלחה אותו ויכול להיות integer — נצפה בשרת חי כ-integer 2, ולא כמחרוזת שהוא במקומות אחרים. זהו המקום היחיד בעמוד הזה שבו ההתחייבות ש"code הוא תמיד מחרוזת", שניתנה עבור open_new_order ועבור get_shipping_status, אינה מתקיימת. קראו את data.data.code בסובלנות: המירו אותו לפני השוואה, או קבלו את שני הטיפוסים.
שליפה שנכשלה מול חברת השילוח היא עדיין HTTP 200 עם success: true
כאשר הקריאה לחברת השילוח נכשלת, המתאם מחזיר { "error": true, "message": ..., "success": false } והאובייקט הזה הופך להיות data ללא שינוי — המעטפת סביבו עדיין אומרת success: true והסטטוס עדיין 200. בדקו את data.success / data.error, לא את המעטפת:
{
"data": {
"error": true,
"message": "Error retrieving tracking info",
"success": false
},
"message": "",
"success": true,
"warning": ""
}שגיאות
| סטטוס | message | מתי |
|---|---|---|
| 403 | הודעות רישיון | ראו אימות. |
| 404 | גוף ברירת המחדל של Laravel | אין משלוח עם קוד המעקב הזה תחת הרישיון הזה. הגוף הוא { "message": "No query results for model [App\\Models\\Shipping]." }, ולא מעטפת v1. |
| 422 | גוף ברירת המחדל של Laravel | shipping_id, license_key או domain חסרים. |
POST /shipping/get_shipping_status
רענון סטטוס המשלוח מחברת השילוח והחזרתו. בשונה מ-get_order_details, זו נקודת קצה שכותבת: היא שומרת את הסטטוס החדש על המשלוח (ועבור חלק מחברות השילוח, משביתה משלוח שבוטל) לפני שהיא משיבה. אימות: license_key + domain. רישיון: חובה.
פרמטרים
גוף הבקשה
זהה לget_order_details — אותו form request עומד מאחורי שתיהן.
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. |
domain | string | כן | ראו אימות. |
shipping_id | string | כן | קוד המעקב של חברת השילוח. מותאם מול shippings.shipping_code, מוגבל להזמנות השייכות לרישיון הזה. |
דוגמת בקשה
curl --location 'https://app.shipos.co.il/api/shipping/get_shipping_status' \
--header 'Accept: application/json' \
--header 'Content-Type: 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/get_shipping_status', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: 'your-store.example.com',
shipping_id: '66747921',
}),
})
const { data } = await response.json()
// data הוא null כשהשליפה מחברת השילוח נכשלה — לא "אין סטטוס עדיין".
if (data === null) {
throw new Error('status unavailable')
}
console.log(data.code, data.text, data.is_delivered)<?php
use Illuminate\Support\Facades\Http;
$status = Http::acceptJson()
->post('https://app.shipos.co.il/api/shipping/get_shipping_status', [
'license_key' => config('services.shipos.license_key'),
'domain' => 'your-store.example.com',
'shipping_id' => '66747921',
])
->json('data');
abort_if($status === null, 502, 'status unavailable');
logger()->info('delivered: '.var_export($status['is_delivered'], true));תשובה 200
{
"data": {
"code": "99",
"text": "Delivered",
"receiver": "ישראל ישראלי",
"is_delivered": true
},
"message": "",
"success": true,
"warning": ""
}| שדה | סוג | תיאור |
|---|---|---|
data.code | string | קוד הסטטוס של חברת השילוח. תמיד מחרוזת — הנורמליזציה ממירה אותו. הקודים ספציפיים לכל חברת שילוח. |
data.text | string | null | תיאור הסטטוס של חברת השילוח עצמה. |
data.is_delivered | boolean | נוסף על-ידינו: true כאשר code נמצא ברשימת סטטוסי המסירה של חברת השילוח. נכפה ל-false כאשר חברת השילוח מדווחת isCancelled: "y". |
כל מפתח אחר הוא מה שמתאם חברת השילוח שמר (receiver, isCancelled, וכן הלאה) והוא משתנה בין חברות השילוח. data.is_delivered נוסף רק כאשר לסטטוס השמור יש code.
data: null פירושו שהשליפה נכשלה
כמה ממתאמי חברות השילוח מסמנים כשל בכך שהם מחזירים מערך שגיאה במקום את המשלוח. ה-handler הזה אינו מסתעף על כך, ולכן התשובה היא HTTP 200, success: true, ו-data: null. payload בערך null הוא קריאה שנכשלה מול חברת השילוח, ולא "הסטטוס עוד לא ידוע" — נסו שוב במקום להתייחס למשלוח כחסר-סטטוס.
שגיאות
| סטטוס | message | מתי |
|---|---|---|
| 403 | הודעות רישיון | ראו אימות. |
| 404 | גוף ברירת המחדל של Laravel | אין משלוח עם קוד המעקב הזה תחת הרישיון הזה. |
| 422 | גוף ברירת המחדל של Laravel | shipping_id, license_key או domain חסרים. |
POST /shipping/change_order_status
ביטול משלוח מול חברת השילוח. בהצלחה, המשלוח מסומן גם כלא-פעיל בצד שלנו (is_active = false). אימות: license_key + domain. רישיון: חובה.
פרמטרים
גוף הבקשה
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. |
domain | string | כן | ראו אימות. |
shipping_id | string | כן | קוד המעקב של חברת השילוח. מותאם מול shippings.shipping_code. השליפה מקבלת את המשלוח דרך הרישיון של ההזמנה שלו או דרך ה-license_id של המשלוח עצמו, ולכן גם משלוחים שנוצרו מחוץ להקשר של הזמנה ניתנים לביטול. |
דוגמת בקשה
curl --location 'https://app.shipos.co.il/api/shipping/change_order_status' \
--header 'Accept: application/json' \
--header 'Content-Type: 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/change_order_status', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: 'your-store.example.com',
shipping_id: '66747921',
}),
})
// ביטול שנדחה מוחזר גם הוא כ-HTTP 200 — success הוא האות היחיד.
const payload = await response.json()
if (!payload.success) {
throw new Error(payload.message)
}
console.log(payload.message) // טקסט האישור של חברת השילוח<?php
use Illuminate\Support\Facades\Http;
$payload = Http::acceptJson()
->post('https://app.shipos.co.il/api/shipping/change_order_status', [
'license_key' => config('services.shipos.license_key'),
'domain' => 'your-store.example.com',
'shipping_id' => '66747921',
])
->json();
// HTTP 200 מכסה את שתי התוצאות; ->throw() לא יתפוס דחייה.
if (! $payload['success']) {
throw new RuntimeException($payload['message']);
}
logger()->info($payload['message']);תשובה 200
בוטל:
{
"data": [],
"message": "Successfully cancelled shipment",
"success": true,
"warning": ""
}נדחה על-ידי חברת השילוח — גם כן HTTP 200:
{
"data": {
"error": "Shipment already collected"
},
"message": "Cancellation not permitted at this stage",
"success": false,
"warning": ""
}לא נמצא תחת הרישיון הזה — שוב HTTP 200:
{
"data": [],
"message": "אירעה שגיאה",
"success": false,
"warning": ""
}ההודעה הזו היא מפתח התרגום Something went wrong; השרת החי רינדר אותה כ-אירעה שגיאה, וזה מה שצד קורא מקבל בפועל.
נקודת הקצה הזו לעולם אינה מחזירה קוד סטטוס של כשל
כל שלושת הגופים שלמעלה הם HTTP 200; success הוא האות היחיד שאפשר לסמוך עליו, ו-data.error נושא את הסיבה של חברת השילוח כשיש כזו. שימו לב גם שמקרה "לא נמצא" מדווח בהודעה הגנרית Something went wrong ולא ב-404, ולכן קוד מעקב שגוי ודחייה של חברת השילוח נבדלים זה מזה רק לפי message ו-data — וה-message מתורגם, ולכן אינו דבר שאפשר להתאים עליו מחרוזות.
ביטול פעמיים אינו נבדל מביטול פעם אחת
משלוח שכבר בוטל מתקבל שוב: HTTP 200, success: true, ואותה הודעת Successfully cancelled shipment. אין שום אות של "כבר בוטל" בגוף התשובה — אין הודעה נפרדת, אין דגל, ו-data נשאר []. לכן צד קורא אינו יכול להבדיל בין ביטול אמיתי לבין פעולה חסרת השפעה, וניסיון חוזר לאחר timeout ייראה כאילו הצליח בין אם הניסיון הראשון עבר ובין אם לא. אם אתם צריכים את המצב הנוכחי, קראו את is_active מ-POST /shipping/order במקום להסיק אותו מהתשובה הזו.
שגיאות
| סטטוס | message | מתי |
|---|---|---|
| 200 | Something went wrong (מרונדר אירעה שגיאה) | אין משלוח עם קוד המעקב הזה תחת הרישיון הזה. success: false. |
| 200 | הודעת חברת השילוח | חברת השילוח דחתה את הביטול. success: false, הסיבה ב-data.error. |
| 403 | הודעות רישיון | ראו אימות. |
| 422 | גוף ברירת המחדל של Laravel | shipping_id, license_key או domain חסרים. |
POST /shipping/order
הצגת רשימת המשלוחים שנוצרו כבר עבור אוסף של מזהי הזמנות שלכם. קריאה בלבד; לא מתבצעת קריאה לחברת השילוח עבור שורות המשלוח עצמן. אימות: license_key + domain. רישיון: חובה.
פרמטרים
גוף הבקשה
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
license_key | string | כן | ראו אימות. בלי token, זה מה שמצמצם את החיפוש: כל רישיון שחולק את המפתח הזה. |
domain | string | כן | ראו אימות. |
ids | array | כן | מזהי ההזמנות שלכם, מותאמים מול orders.order_id. איברים ריקים-למעשה נזרקים; אם לא נשאר דבר, הקריאה נכשלת. |
token | string | לא | טוקן Sanctum. כשהוא נשלח, החיפוש מצומצם לכל הרישיונות שבבעלות המשתמש של הטוקן במקום ל-license_key בלבד. |
דוגמת בקשה
curl --location 'https://app.shipos.co.il/api/shipping/order' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"license_key": "{license_key}",
"domain": "your-store.example.com",
"ids": ["1042", "1043"]
}'// Node.js 18+ / דפדפנים — בלי תלויות
const response = await fetch('https://app.shipos.co.il/api/shipping/order', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
license_key: process.env.SHIPOS_LICENSE_KEY,
domain: 'your-store.example.com',
ids: ['1042', '1043'],
}),
})
// נקודת הקצה הזו משיבה במערך חשוף — ומדווחת על כשלים כ-HTTP 500.
if (!response.ok) {
const { message } = await response.json()
throw new Error(message)
}
const shipments = await response.json()
console.log(`${shipments.length} shipments`)<?php
use Illuminate\Support\Facades\Http;
$shipments = Http::acceptJson()
->post('https://app.shipos.co.il/api/shipping/order', [
'license_key' => config('services.shipos.license_key'),
'domain' => 'your-store.example.com',
'ids' => ['1042', '1043'],
])
->throw()
->json();
logger()->info(count($shipments).' shipments');תשובה 200
אין מעטפת כאן
זו נקודת הקצה היחידה בעמוד זה שאינה משתמשת ב-apiResponse(). היא מחזירה מערך JSON חשוף של שורות משלוח, שטוח על פני ההזמנות שהותאמו — בלי data, message, success או warning. תוצאה ריקה היא [], גם כאשר אף אחד מהמזהים לא התאים לדבר.
[
{
"shipping_id": 918342,
"delivery_number": "66747921",
"is_active": true,
"created_at": "2026-07-29T09:14:00.000000Z",
"updated_at": "2026-07-29T09:14:00.000000Z",
"delivery_time": null,
"type": "1",
"return": "1",
"company": {
"id": 3,
"name": "HFD",
"email": "ops@example-carrier.example",
"description": null,
"phone": "036000000",
"license_quota": 50,
"service_url": "https://api.example-carrier.example/v1/",
"service_code": null,
"service_username": null,
"service_password": null,
"provider_id": 4,
"logo": "companies/hfd.png",
"is_premium": false,
"display_company_logo_on_label": false,
"created_at": "2023-02-16T07:14:18.000000Z",
"updated_at": "2026-07-29T09:14:00.000000Z",
"deleted_at": null,
"available_licenses_count": -49,
"provider": {
"id": 4,
"name": "hfd",
"endpoints": { "create": "…", "status": "…", "label": "…" },
"created_at": "2023-02-16T07:14:18.000000Z",
"updated_at": "2023-02-16T07:14:18.000000Z"
},
"provider_settings": [
{
"id": 87,
"provider_id": 4,
"company_id": 3,
"settings": { "…": "…" },
"created_at": "2023-02-20T09:29:32.000000Z",
"updated_at": "2026-07-01T06:11:40.000000Z"
}
],
"current_provider_settings": {
"id": 87,
"provider_id": 4,
"company_id": 3,
"settings": { "…": "…" }
}
},
"license_key": "{license_key}",
"code": "66747921",
"shipping_line": "12",
"line_branch": ""
}
]| שדה | סוג | תיאור |
|---|---|---|
shipping_id | integer | ה-shippings.id הפנימי שלנו. |
delivery_number | string | null | קוד המעקב של חברת השילוח (shippings.shipping_code). |
is_active | boolean | false לאחר שהמשלוח בוטל. |
created_at | datetime | מתי נוצרה שורת המשלוח. |
updated_at | datetime | העדכון האחרון. |
delivery_time | string | null | נקרא מתוך payload המשלוח השמור; null עבור משלוחים שנוצרו בלעדיו. |
type | string | null | ship_data.type כפי שנשמר. ראו סוגי משלוח. |
return | string | null | ship_data.return כפי שנשמר. |
company | object | null | כל שורת ה-companies שמאחורי הרישיון של המשלוח, בתוספת שלושה קשרים שנטענים מראש — provider, provider_settings[] ו-current_provider_settings. אינו מבנה מנוהל בקפידה; ראו את האזהרה בהמשך. |
license_key | string | הרישיון שהמשלוח שייך אליו — המבדיל כאשר token הרחיב את החיפוש על פני כמה רישיונות. |
code | string | שדה ה-code של תשובת חברת השילוח השמורה; "" כאשר חברת השילוח לא החזירה אחד. |
shipping_line | string | קו/מסלול של חברת השילוח, נגזר מחדש מתשובת חברת השילוח השמורה. |
line_branch | string | null | הסניף של אותו קו; null או "" עבור חברות שילוח שאינן מדווחות על אחד. |
company נושא נתונים פנימיים של חברת השילוח ושל המכסות
Company מגדיר $guarded = [] ואינו מגדיר $hidden, ולכן השורה מסודרת בשלמותה וכל עמודה של companies מופיעה כאן — כולל service_url (כתובת ה-API הבסיסית של חברת השילוח), העמודות service_code / service_username / service_password, license_quota, is_premium, display_company_logo_on_label ו-deleted_at. המאפיין המצורף available_licenses_count הוא מונה חי של יתרת המכסה ויכול להיות שלילי (נצפה: -49) כאשר חברה חרגה מה-license_quota שלה. באופן דומה provider.endpoints נושא את מפת נקודות הקצה של חברת השילוח, ו-provider_settings[] הוא שורה אחת לכל ספק שהוגדר.
שום דבר מזה אינו מיועד לחנות. בחרו את שני-שלושת השדות שאתם באמת צריכים מתוך company לפני שאתם מעבירים את המערך הזה למקום שלקוח יכול לראות, ואל תרשמו את התשובה בלוג בשלמותה.
שגיאות
כשלים הם HTTP 500
ה-handler הזה מדווח על כל כשל דרך ה-helper failResponse(), שסטטוס ברירת המחדל שלו הוא 500. בקשה שגויה ותקלת שרת אינן נבדלות זו מזו לפי קוד הסטטוס; השתמשו ב-message. הגוף הוא { "errors": ..., "message": ..., "success": false } — צורה שלישית, שונה גם ממעטפת v1 וגם ממערך ההצלחה.
| סטטוס | message | מתי |
|---|---|---|
| 403 | הודעות רישיון | ראו אימות. |
| 500 | Validation Failed | license_key, domain או ids חסרים או מטיפוס שגוי. פרטים תחת errors. |
| 500 | Invalid license key | לא נקבע מזהה רישיון מהטוקן או מהמפתח. |
| 500 | Order Id not found | ids היה קיים אך כל האיברים היו ריקים. |
POST /shipping/{shipment}/status
רענון סטטוס חברת השילוח של משלוח בודד לפי המזהה הפנימי שלו. זו נקודת הקצה שאליה קורא פקד "רענון סטטוס" של האדמין של ShipOS עצמו. אימות: אין. רישיון: נקבע מתוך המשלוח.
בלי שום אימות, והתשובה חושפת פרטי הזדהות
הנתיב הזה רשום בנפרד, לפני קבוצת הקידומת shipping, ולכן הוא מריץ אף לא אחד מרכיבי ה-middleware של אותה קבוצה: אין hasLicense, אין רישום בקשות, אין normalize_shipment_response. הוא אינו דורש שום אימות שהוא. נבדק ללא שום פרטי הזדהות — בלי license_key, בלי domain, בלי token, בלי שום כותרת אימות — והקריאה עדיין משיבה HTTP 200. הנתיב קושר את ה-integer הסידורי shippings.id, ולכן המזהים ניתנים למעבר סדרתי בקלות.
ה-data שבתשובה טוען מראש (eager-load) את license, order ו-provider, ולכן חושף:
data.license.key— מפתח הרישיון, בטקסט גלוי (32 תווים).data.order.license.keyהוא אותו מפתח פעם שנייה.data.order.customer— כל שורת המשתמש הבעלים, 47 מפתחות.User::$hiddenמכסה רק אתpassword,remember_tokenואת עמודות האימות הדו-שלבי, ולכן שדות ההזדהות אינם מוסתרים: ה-emailשל הבעלים, פרטי ההזדהות שלו ל-API של v2 (client_id,client_secret) והחומר של Google OAuth (google_token,google_refresh_token,google_api_key) מופיעים כולם ב-JSON.
כל מי שיכול להגיע לנתיב הזה יכול לעבור על מרחב המזהים ולאסוף מפתחות רישיון ופרטי הזדהות ל-API. התייחסו לכל גוף התשובה כרגיש: אל תעבירו אותו לדפדפן, אל תרשמו אותו בלוג בצד הלקוח, אל תדביקו אותו בדיווח שגיאה או במערכת מעקב תקלות, וצמצמו אותו לקומץ השדות שאתם צריכים ברגע שהוא מגיע.
לרענון יש גם תופעות לוואי — הוא כותב את הסטטוס החדש למשלוח, יכול לשלוח לנמען את ה-SMS על הגעת המשלוח, ויכול לכתוב תגיות סטטוס על הזמנת ה-Shopify של הסוחר — כך שגם קורא שעובר סדרתית על המזהים אינו קורא-בלבד.
התייחסו לנקודת הקצה הזו כאל פנימית. לאינטגרציות השתמשו במקומה ב-get_shipping_status: היא מבצעת את אותו רענון תחת אימות רישיון ומחזירה רק את הסטטוס.
פרמטרים
נתיב
| שדה | סוג | חובה | תיאור |
|---|---|---|---|
shipment | integer | כן | ה-shippings.id הפנימי — ה-data.shipping_id מ-open_new_order. לא ה-uuid ולא קוד המעקב. מזהה לא מוכר מחזיר 404. |
פורמט התשובה נקבע על-ידי content negotiation ולא על-ידי פרמטר: שלחו Accept: application/json במפורש. לא מספיק להימנע מלסתור אותו — Accept: */*, שהוא ברירת המחדל של curl כשאינכם שולחים כותרת Accept בכלל, גם הוא לוקח את מסלול הדפדפן ומשיב 302. כל Accept שאינו מציין בפועל application/json מקבל את ההפניה (redirect) בתוספת הודעת flash במקום JSON.
דוגמת בקשה
curl --location --request POST 'https://app.shipos.co.il/api/shipping/918342/status' \
--header 'Accept: application/json'// Node.js 18+ / דפדפנים — בלי תלויות
// Accept: application/json נדרש, אחרת התשובה תהיה הפניה.
const response = await fetch('https://app.shipos.co.il/api/shipping/918342/status', {
method: 'POST',
headers: { Accept: 'application/json' },
})
const payload = await response.json()
if (!payload.success) {
throw new Error(payload.message)
}
console.log(payload.data.status.code, payload.data.shipping_type)<?php
use Illuminate\Support\Facades\Http;
$payload = Http::acceptJson()
->post('https://app.shipos.co.il/api/shipping/918342/status')
->json();
if (! ($payload['success'] ?? false)) {
throw new RuntimeException($payload['message']);
}
logger()->info('status', $payload['data']['status']);תשובה 200
data הוא כל שורת ה-shippings כפי ש-Eloquent מסדרת אותה — כל עמודה, בתוספת המאפיינים המצורפים של המודל ויומן הסטטוסים שלו. זה אינו חוזה מנוהל בקפידה; עמודות שנוספות לטבלה מופיעות כאן.
הדוגמה שלהלן היא מקוצרת. הגוף האמיתי נושא גם את האובייקטים license, order (עם ה-license וה-customer המקוננים שלו) ו-provider שנטענים מראש, ושם נמצאת חשיפת פרטי ההזדהות שתוארה בתיבה שלמעלה; הם הושמטו כאן במכוון.
{
"data": {
"id": 918342,
"uuid": "9b2c7f1e-3a4d-4c88-9f21-2f0d5a6b7c88",
"shipping_code": "66747921",
"type": "regular",
"is_active": true,
"first_name": "Dana",
"last_name": "Levi",
"city": "תל אביב",
"address_1": "הרצל",
"street_number": "10",
"phone": "0521234567",
"status": {
"code": "99",
"text": "Delivered"
},
"shipment_data": { "type": 1, "return": 1, "packages": 1 },
"response": {
"response": { "code": "66747921", "status": 1 },
"error": "",
"status": 200
},
"created_at": "2026-07-29T09:14:00.000000Z",
"updated_at": "2026-07-31T11:02:14.000000Z",
"shipping_type": "Regular delivery",
"short_code": "bs6oM=",
"short_tracking_code": "e6Pns4k",
"effective_label_method": "shipos",
"effective_label_type": "default",
"shipping_log": [
{
"id": 55120,
"shipping_id": 918342,
"status_code": "99",
"status": "Delivered",
"created_at": "2026-07-31T11:02:14.000000Z"
}
]
},
"message": "Shipping status successfully updated",
"success": true,
"warning": ""
}| שדה | סוג | תיאור |
|---|---|---|
data.status | object | null | סטטוס חברת השילוח שנשמר כרגע — code ו-text, בתוספת כל מה שאותה חברת שילוח מדווחת. אינו עובר נורמליזציה כאן: code יכול להיות integer, מפני שהנתיב הזה אינו מריץ normalize_shipment_response. |
data.type | string | regular, double או collecting. ראו סוגי משלוח. |
data.shipping_type | string | תווית מצורפת ומתורגמת לאותו דבר. |
data.short_code | string | מצורף, ואינו צורה מקוצרת של קוד המעקב — הוא כלל אינו נגזר ממנו. Shipping::getShortCodeAttribute() מחזיר קידוד אטום של כ-6 תווים של מזהה השורה של המשלוח (shippings.id); עבור קוד המעקב 68834253 הוא נצפה כ-bs6oM=. התייחסו אליו כאל אסימון אטום: הוא יכול להכיל = ותווים אחרים שאינם אלפאנומריים. |
data.short_tracking_code | string | null | עמודה נפרדת ששמורה בטבלת shippings, וערך שונה בשלישית — לא short_code ולא תת-מחרוזת של קוד המעקב (נצפה: e6Pns4k). אטום גם הוא. |
data.effective_label_method | string | shipos או shipping_company. |
data.effective_label_type | string | default (תרמי) או A4. |
data.shipment_data | object | ה-ship_data השמור ממוזג עם הגדרות חברת השילוח שנקבעו. |
data.response | object | מעטפת, ולא ה-payload של חברת השילוח עצמו: { "response": {…payload של חברת השילוח…}, "error": "", "status": 200 }. הגוף של חברת השילוח יושב רמה אחת עמוק יותר, ב-data.response.response; data.response.status הוא סטטוס ה-HTTP של חברת השילוח כפי שתפסנו אותו, ו-data.response.error הוא "" כשלא הייתה שגיאה. |
data.shipping_log | array | שורות יומן הסטטוסים של המשלוח, מהחדשה לישנה. כל שורה נושאת status_code ו-status לצד המזהים וחתימות הזמן. שורה נוספת רק כאשר הסטטוס משתנה — ראו את ההערה בהמשך. |
shipping_log מתעד שינויים, לא קריאות
זה אינו יומן קריאות. חמישה רענוני סטטוס עוקבים של אותו משלוח הפיקו בדיוק שורת יומן אחת: שורה נוספת כאשר חברת השילוח מדווחת על סטטוס שונה מזה השמור, ורענון שמחזיר את אותו סטטוס אינו מוסיף דבר. לכן מספר השורות מספר כמה פעמים הסטטוס זז, ולא כמה פעמים מישהו שאל, וה-created_at של השורה החדשה ביותר הוא זמן השינוי האחרון ולא זמן הקריאה שלכם.
רק שתים-עשרה חברות שילוח מחוברות ל-handler הזה: Baldar, Tapuz, Run Software, HFD, Done, Cargo, LionWheel, GetPackage, Orian, Zig Zag, Ship UPS ו-Cactus. משלוח אצל כל ספק אחר נדחה עם Not yet supported for: <provider>.
שגיאות
כשלים כאן משתמשים ב-helper failResponse() — הגוף { "errors": [], "message": ..., "success": false }, סטטוס 500.
| סטטוס | message | מתי |
|---|---|---|
| 404 | גוף ברירת המחדל של Laravel | אין משלוח עם המזהה הזה. |
| 500 | No order exists for this shipment | למשלוח אין הזמנה מקושרת. |
| 500 | No license exists for this shipment | לא ההזמנה ולא המשלוח מאפשרים לקבוע רישיון. |
| 500 | No valid license settings for <provider> | לרישיון אין שורת הגדרות עבור הספק של המשלוח. |
| 500 | Not yet supported for: <provider> | חברת השילוח של המשלוח אינה אחת משתים-עשרה החברות שמחוברות למעלה. |
| 500 | שגיאת חברת השילוח | שליפת הסטטוס מחברת השילוח נכשלה. |
בלי כותרת Accept שמציינת application/json אף אחת מאלה אינה מוחזרת כ-JSON: ה-handler משיב 302 עם ההודעה ב-flash של ה-session במקום. זה כולל את ה-*/* ש-curl שולח כברירת מחדל.