Skip to content

משלוחים ​

נקודות הקצה של המשלוחים בגרסה הקודמת 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_keystringכןlicenses.key — חשבון חברת השילוח שהקריאה פועלת תחתיו.
domainstringכןהדומיין של החנות הקוראת. חייב להיות קיים ומטיפוס string; ההשוואה מול הדומיין של הרישיון עצמו מוערת בקוד (commented out) ב-CheckLicense, ולכן כל מחרוזת לא-ריקה מספקת אותה כיום.
tokenstringלאטוקן גישה אישי של 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():

json
{
  "data": {},
  "message": "",
  "success": true,
  "warning": ""
}
שדהסוגתיאור
datamixedה-payload. המבנה משתנה בין נקודות הקצה — אובייקט, מערך, שורת מודל, או null.
messagestringתוצאה קריאה לאדם. בכשלים של חברת השילוח זו ההודעה של חברת השילוח עצמה, מילה במילה (לעיתים בעברית), ולא מחרוזת של ShipOS.
successbooleanהאם הפעולה הצליחה.
warningstringשמור לשימוש עתידי; ריק בכל נקודת קצה בעמוד זה.

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.typeship_data.returnמשמעותshippings.type שנשמרתווית shipping_type שמצורפת
11 או חסרמשלוח רגילregularRegular delivery
12משלוח כפול — מסירה והבאת משהו בחזרה באותו הביקורdoubleDouble delivery
21 או חסראיסוף — איסוף מהנמעןcollectingCollecting delivery
22איסוף שהתבקש עם רגל חזרהdoubleCollecting 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_keystringכןראו אימות. מהווה גם חלק ממפתח האידמפוטנטיות.
domainstringכןראו אימות.
tokenstringלאטוקן Sanctum. כשהוא נשלח, קובע את order.wp_site_id מתוך אתר ה-WooCommerce הרשום עבור domain.
ship_dataobjectכןpayload הפונה לחברת השילוח. ראו בהמשך.
orderobjectכןמעטפת ההזמנה. ראו בהמשך.
order_itemsarrayלאשורות פריטים. ברמה העליונה, לא מקוננות תחת order — זו הצורה ש-v2 שינתה.
order_items[].namestringכןחובה עבור כל איבר כאשר order_items קיים.
order_items[].quantityintegerכןחובה עבור כל איבר. מינימום 1.
order_items[].totalnumericכןחובה עבור כל איבר.
shipment_settingsobjectלאדריסות לכתובת האיסוף: collect_street, collect_street_number, collect_city, collect_company. בשימוש רק כאשר לרישיון עצמו אין הגדרות איסוף — הרישיון תמיד מנצח. איסוף (ship_data.type = 2) דורש שאחד מהשניים יהיה מאוכלס — ראו את האזהרה בסוגי משלוח.
codestringלאמועבר כפי שהוא למתאם חברת השילוח כהגדרת ה-code שלו.

ששת הכללים האלה (ship_data, order, order_items ושלושת הכללים שלה לכל פריט) הם כל מערך הכללים המוצהר. כל מה שבתוך ship_data ו-order אינו מאומת מבחינת טיפוס, למעט חריג אחד: בדיקה חוצת-שדות דורשת שניתן יהיה לחלץ רחוב, עיר ושם איש קשר מתוך ship_data או מתוך order.billing, ודוחה את הבקשה אחרת. "ריק" שם פירושו null או רווחים בלבד — המחרוזת הליטרלית "0" נחשבת רחוב תקין, במכוון (לקוחות אכן מקלידים אותה).

ship_data ​

מועבר למתאם חברת השילוח ונשמר מילה במילה ב-shippings.shipment_data, ולכן אילו מפתחות משפיעים תלוי בחברת השילוח. אלו המפתחות שה-controller של v1 עצמו קורא, ממלא בברירת מחדל, או משכתב:

שדהסוגחובהתיאור
contact_namestringמותנהשם הנמען. נופל חזרה ל-order.billing.first_name + order.billing.last_name. נדחה רק אם כל שלושתם ריקים.
contact_phonestringלאנופל חזרה ל-order.billing.phone. בדיקת הקיום מוערת בקוד, ולכן משלוח בלי טלפון בכלל מתקבל אצלנו ומושאר לחברת השילוח.
contact_mailstringלאנופל חזרה ל-order.billing.email.
streetstringמותנהנופל חזרה ל-order.billing.address_1. חובה (לאחר הנפילה חזרה הזו).
numberstringלאמספר בית. כאשר סופק street, נופל חזרה ל-order.billing.address_2.
citystringמותנהנופל חזרה ל-order.billing.city. חובה (לאחר הנפילה חזרה הזו).
companystringלאנופל חזרה ל-order.billing.company.
typeintegerכןכיוון השירות. ראו סוגי משלוח. אינו מאומת, אך גם חברות השילוח וגם קביעת הסוג שלנו דורשות אותו.
returnintegerכןרגל החזרה. ראו סוגי משלוח.
packagesintegerלאמספר חבילות.
pickupstringלאמזהה נקודת איסוף / נקודת חלוקה, עבור משלוחים לנקודת איסוף.
pickup_addressstringלאכתובת נקודת האיסוף בפורמט קריא.
floorstringלאקומה.
apartmentstringלאדירה.
entrancestringלאכניסה.
house_nostringלאמספר בית, עבור חברות השילוח שקוראות את המפתח הזה במקום number.
notestringלאהערת משלוח. עובר שינוי בשרת — ראו את ההערה בהמשך.
extra_notestringלאהערה נוספת, עבור חברות השילוח שתומכות באחת.
exaction_datedate (Y-m-d)לאתאריך איסוף מבוקש. ברירת המחדל היא היום כשהוא ריק או חסר.
woo_idstringלאמזהה ההזמנה שלכם כפי שהוא מוצג לחברת השילוח. מקבל את ה-order_prefix של הרישיון כקידומת כאשר מוגדר כזה.
urgentbooleanלאטיפול דחוף.
motorintegerלאשליח אופנוע.
collectbooleanלאדגל איסוף, במקומות שבהם חברת השילוח מבדילה אותו מ-type.
is_cod / cod_amountmixedלאגבייה במסירה (COD), עבור חברות השילוח שתומכות בה.
IsManualbooleanלאמסמן את המשלוח כהוזן ידנית.

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.idstringכןמזהה ההזמנה שלכם. נשמר כ-orders.order_id, מותאם על-ידי POST /shipping/order, ומהווה חלק ממפתח המטמון של האידמפוטנטיות — ראו את האזהרה בהגנה מפני כפילויות.
order.numberstringלאמספר הזמנה קריא. ברירת המחדל היא order.id. מקבל את ה-order_prefix של הרישיון כקידומת כאשר מוגדר כזה.
order.statusstringלאסטטוס ההזמנה שלכם. נבדק מול רשימת הסטטוסים החסומים של הרישיון; סטטוס חסום נדחה עם 400.
order.sourcestringלאתווית פלטפורמה, ברירת מחדל wordpress. חלק מזהות ההזמנה לצורך upsert. שליחתו גם מדלגת על דחיפת סטטוס ההזמנה ל-WooCommerce לאחר יצירה מוצלחת.
order.shippingobjectלאתמונת-מצב הנמען. כשהוא חסר, נעשה שימוש ב-order.billing במקומו.
order.billingobjectלאבלוק החיוב. גם מקור הנפילה חזרה עבור ship_data.street, number, city, company, contact_name, contact_phone ו-contact_mail.
order.order_keystringלאמפתח ההזמנה בפלטפורמה. חלק מזהות ההזמנה לצורך upsert.
order.shopify_idmixedלאמזהה חנות Shopify. חלק מזהות ההזמנה לצורך upsert.
order.wix_idmixedלאמזהה אתר Wix. חלק מזהות ההזמנה לצורך upsert.
order.wp_site_idmixedלאמזהה אתר WooCommerce מחובר. חלק מזהות ההזמנה לצורך upsert; נדרס כאשר token מזהה אתר עבור domain.
order.currencystringלאמטבע ISO.
order.totalnumericלאסך ההזמנה.
order.total_taxnumericלאסך המס.
order.discount_totalnumericלאסך ההנחה.
order.discount_taxnumericלאמס ההנחה.
order.shipping_totalnumericלאסך דמי המשלוח.
order.shipping_taxnumericלאמס דמי המשלוח.
order.shipping_linesarrayלאשורות משלוח של הפלטפורמה.
order.cart_taxnumericלאמס העגלה.
order.prices_include_taxbooleanלאהאם המחירים כוללים מס.
order.payment_methodstringלאאמצעי תשלום.
order.meta_dataarrayלאזוגות { 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 שנ'

מה שהצד הקורא רואה:

  1. ניסיון חוזר בתוך אותה שעה משחזר את התשובה הראשונה מילה במילה — אותו גוף, אותו סטטוס HTTP — בתוספת data.idempotent: true. לא מתבצעת קריאה לחברת השילוח, לא נוצר משלוח שני, ואף אחת מתופעות הלוואי שלאחר היצירה (SMS על מסירה, עדכוני הזמנה בפלטפורמה, מאזיני ShippingCreated) אינה נורית שוב. הדגל הזה הוא האות שלכם לדלג גם על תופעות הלוואי שלכם.
  2. רק יצירות מוצלחות נשמרות במטמון. ניסיון שנכשל אינו משאיר דבר אחריו, ולכן הוא נשאר בר-ניסיון-חוזר.
  3. ניסיון חוזר מקבילי ממתין במקום להיכשל. הוא חונה על המנעול עד 120 שנ', וכשהמקורי מסתיים הוא לוקח את מסלול השחזור. שתי הקלקות במרווח של שנייה מפיקות משלוח אחד ושתי תשובות זהות.
  4. אם חלון ההמתנה פג, תקבלו 409 עם Another shipment request for this order is in progress. Please try again. — הבקשה אינה מנוסה. נסו שוב לאחר הפסקה.
  5. לאחר השעה, ניתן לשלוח את אותה הזמנה שוב. במכוון אין בדיקת כפילויות ברמת בסיס הנתונים: סוחרים יוצרים באופן לגיטימי משלוח שני להזמנה אחת ימים או שבועות אחר כך. האידמפוטנטיות ב-v1 היא חלון של שעה, לא חלון קבוע.

המפתח אינו כולל את סוג המשלוח

shipment_response_{license_key}_{order.id} אינו נושא שום סוג שירות. צרו משלוח רגיל להזמנה 1042 ואז, בתוך אותה שעה, בקשו איסוף להזמנה 1042: הקריאה השנייה תשחזר את תשובת המשלוח ולא ייווצר שום איסוף. זו פינה חדה מוכרת. אם אתם צריכים שני סוגי שירות להזמנה אחת, המתינו עד שה-TTL יפוג או שנו את ה-order.id שאתם שולחים.

order.id חסר מכווץ את המפתח

order.id נקרא כ-order.id ?? '', ולכן בקשה בלעדיו משתמשת במפתח shipment_response_{license_key}_. כל בקשה חסרת-מזהה עבור אותו רישיון חולקת את המפתח היחיד הזה ותשחזר את התשובות של האחרות. שלחו תמיד order.id.

דוגמת בקשה ​

bash
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 }
  ]
}'
js
// 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
<?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 ​

json
{
  "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.statusintegerהסטטוס של מתאם חברת השילוח. 1 פירושו נוצר; ממנו נגזר success.
data.codestringקוד המעקב של חברת השילוח. תמיד מחרוזת — הנורמליזציה ממירה אותו. השתמשו בו כפרמטר shipping_id של נקודות הקצה האחרות.
data.response_status_codemixed | nullשדה הסטטוס של חברת השילוח עצמה, כשהיא החזירה אחד.
data.shipping_idintegerה-shippings.id הפנימי שלנו. מתקבל על-ידי POST /shipping/{shipment}/status, ולא על-ידי נקודות הקצה שמקבלות פרמטר shipping_id.
data.order_idintegerה-orders.id הפנימי שלנו.
data.shipping_uuidstringה-uuid של המשלוח, שבשימוש כתובת התווית של ShipOS.
data.effective_label_methodstringמאיפה מגיעה התווית: shipos (אנחנו מרנדרים אותה) או shipping_company (חברת השילוח מרנדרת אותה).
data.effective_label_typestringנייר התווית: default (תרמי) או A4.
data.shipping_linestring | nullקו/מסלול של חברת השילוח, כאשר היא מדווחת עליו.
data.shipping_line_descstring | nullתיאור הקו; null עבור חברות שילוח שאינן מדווחות עליו.
data.line_branchstring | nullהסניף של הקו; null עבור חברות שילוח שאינן מדווחות עליו.
data.tracking_urlstring | nullכתובת המעקב של חברת השילוח הפונה לשולח, כשהיא מוחזרת.
data.pricemixed | nullהמחיר שחברת השילוח נקבה, כשהוא מוחזר.
data.companyobject{ name, logo } של חברת השילוח שמאחורי הרישיון.
data.idempotentbooleanקיים רק בשחזור, תמיד true. חסר ביצירה חדשה.

ה-message של המעטפת אינו ריק בהצלחה. הוא עוקב אחר התבנית Shipping successfully created, shipping number: {code} — נצפה בשרת חי כ-Shipping successfully created, shipping number: 68834253. זו מחרוזת מתורגמת, ולכן קראו את הקוד מ-data.code ולעולם אל תחלצו אותו מתוך ההודעה.

תשובה משוחזרת היא הגוף שלמעלה בתוספת "idempotent": true בתוך data, ועם סטטוס ה-HTTP המקורי — כולל אותו message מקורי שאינו ריק:

json
{
  "data": {
    "status": 1,
    "code": "66747921",
    "shipping_id": 918342,
    "idempotent": true
  },
  "message": "Shipping successfully created, shipping number: 66747921",
  "success": true,
  "warning": ""
}

דחייה של חברת השילוח מחזירה את אותו מערך שדות עם success: false, HTTP 500, וההודעה של חברת השילוח:

json
{
  "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מתי
400You have reached the utilization of the packageמכסת המשלוחים החודשית של הלקוח עבור החבילה שלו מוצתה.
400Shipment creation is blocked for orders with status: :statusהרישיון מפעיל את מסנן סטטוס ההזמנות ו-order.status נמצא ברשימת החסומים שלו.
403Invalid License Keyאין רישיון עם ה-license_key הזה. (מוחזר כ-403, לא כ-404.)
403License key has been deactivatedהרישיון אינו פעיל.
403Premium companies are not supported in this version of pluginהחברה של חברת השילוח היא פרימיום ואף token לא זיהה משתמש.
403You must be a premium member to use this companyחברה פרימיום, משתמש בלי מנוי פרימיום פעיל.
409Another shipment request for this order is in progress. Please try again.חלון ההמתנה של המנעול פג — יצירה מקבילית עבור אותו רישיון + הזמנה עדיין רצה. לא נוצר דבר.
422הודעת האימות הראשונה, כשכולן תחת data.errorsship_data / order חסרים, איבר order_items חסר שדות, או שלא ניתן לחלץ רחוב, עיר או שם איש קשר. משתמשת במעטפת v1.
422גוף ברירת המחדל של Laravellicense_key או domain חסרים. משתמשת ב-{ "message": ..., "errors": {...} }, ולא במעטפת v1.
500הודעת חברת השילוח, מילה במילהחברת השילוח דחתה את המשלוח. success: false.
500An unexpected error occurred while creating the shipment. Please try again or contact support.חריגה בלתי מטופלת בתוך מסלול היצירה.

POST /shipping/get_order_details ​

שליפת הפרטים העכשוויים של משלוח מצד חברת השילוח. שולפת מחברת השילוח בזמן אמת בכל קריאה ומחזירה את התוצאה הגולמית של המתאם — היא אינה מעדכנת דבר בצד שלנו. אימות: license_key + domain. רישיון: חובה.

פרמטרים ​

גוף הבקשה ​

שדהסוגחובהתיאור
license_keystringכןראו אימות.
domainstringכןראו אימות.
shipping_idstringכןקוד המעקב של חברת השילוח (data.code מ-open_new_order). מותאם מול shippings.shipping_code, מוגבל להזמנות השייכות לרישיון הזה.

דוגמת בקשה ​

bash
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"
}'
js
// 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
<?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 הם השניים שכל מתאם ממלא.

json
{
  "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, לא את המעטפת:

json
{
  "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גוף ברירת המחדל של Laravelshipping_id, license_key או domain חסרים.

POST /shipping/get_shipping_status ​

רענון סטטוס המשלוח מחברת השילוח והחזרתו. בשונה מ-get_order_details, זו נקודת קצה שכותבת: היא שומרת את הסטטוס החדש על המשלוח (ועבור חלק מחברות השילוח, משביתה משלוח שבוטל) לפני שהיא משיבה. אימות: license_key + domain. רישיון: חובה.

פרמטרים ​

גוף הבקשה ​

זהה לget_order_details — אותו form request עומד מאחורי שתיהן.

שדהסוגחובהתיאור
license_keystringכןראו אימות.
domainstringכןראו אימות.
shipping_idstringכןקוד המעקב של חברת השילוח. מותאם מול shippings.shipping_code, מוגבל להזמנות השייכות לרישיון הזה.

דוגמת בקשה ​

bash
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"
}'
js
// 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
<?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 ​

json
{
  "data": {
    "code": "99",
    "text": "Delivered",
    "receiver": "ישראל ישראלי",
    "is_delivered": true
  },
  "message": "",
  "success": true,
  "warning": ""
}
שדהסוגתיאור
data.codestringקוד הסטטוס של חברת השילוח. תמיד מחרוזת — הנורמליזציה ממירה אותו. הקודים ספציפיים לכל חברת שילוח.
data.textstring | nullתיאור הסטטוס של חברת השילוח עצמה.
data.is_deliveredbooleanנוסף על-ידינו: 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גוף ברירת המחדל של Laravelshipping_id, license_key או domain חסרים.

POST /shipping/change_order_status ​

ביטול משלוח מול חברת השילוח. בהצלחה, המשלוח מסומן גם כלא-פעיל בצד שלנו (is_active = false). אימות: license_key + domain. רישיון: חובה.

פרמטרים ​

גוף הבקשה ​

שדהסוגחובהתיאור
license_keystringכןראו אימות.
domainstringכןראו אימות.
shipping_idstringכןקוד המעקב של חברת השילוח. מותאם מול shippings.shipping_code. השליפה מקבלת את המשלוח דרך הרישיון של ההזמנה שלו או דרך ה-license_id של המשלוח עצמו, ולכן גם משלוחים שנוצרו מחוץ להקשר של הזמנה ניתנים לביטול.

דוגמת בקשה ​

bash
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"
}'
js
// 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
<?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 ​

בוטל:

json
{
  "data": [],
  "message": "Successfully cancelled shipment",
  "success": true,
  "warning": ""
}

נדחה על-ידי חברת השילוח — גם כן HTTP 200:

json
{
  "data": {
    "error": "Shipment already collected"
  },
  "message": "Cancellation not permitted at this stage",
  "success": false,
  "warning": ""
}

לא נמצא תחת הרישיון הזה — שוב HTTP 200:

json
{
  "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מתי
200Something went wrong (מרונדר אירעה שגיאה)אין משלוח עם קוד המעקב הזה תחת הרישיון הזה. success: false.
200הודעת חברת השילוחחברת השילוח דחתה את הביטול. success: false, הסיבה ב-data.error.
403הודעות רישיוןראו אימות.
422גוף ברירת המחדל של Laravelshipping_id, license_key או domain חסרים.

POST /shipping/order ​

הצגת רשימת המשלוחים שנוצרו כבר עבור אוסף של מזהי הזמנות שלכם. קריאה בלבד; לא מתבצעת קריאה לחברת השילוח עבור שורות המשלוח עצמן. אימות: license_key + domain. רישיון: חובה.

פרמטרים ​

גוף הבקשה ​

שדהסוגחובהתיאור
license_keystringכןראו אימות. בלי token, זה מה שמצמצם את החיפוש: כל רישיון שחולק את המפתח הזה.
domainstringכןראו אימות.
idsarrayכןמזהי ההזמנות שלכם, מותאמים מול orders.order_id. איברים ריקים-למעשה נזרקים; אם לא נשאר דבר, הקריאה נכשלת.
tokenstringלאטוקן Sanctum. כשהוא נשלח, החיפוש מצומצם לכל הרישיונות שבבעלות המשתמש של הטוקן במקום ל-license_key בלבד.

דוגמת בקשה ​

bash
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"]
}'
js
// 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
<?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. תוצאה ריקה היא [], גם כאשר אף אחד מהמזהים לא התאים לדבר.

json
[
  {
    "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_idintegerה-shippings.id הפנימי שלנו.
delivery_numberstring | nullקוד המעקב של חברת השילוח (shippings.shipping_code).
is_activebooleanfalse לאחר שהמשלוח בוטל.
created_atdatetimeמתי נוצרה שורת המשלוח.
updated_atdatetimeהעדכון האחרון.
delivery_timestring | nullנקרא מתוך payload המשלוח השמור; null עבור משלוחים שנוצרו בלעדיו.
typestring | nullship_data.type כפי שנשמר. ראו סוגי משלוח.
returnstring | nullship_data.return כפי שנשמר.
companyobject | nullכל שורת ה-companies שמאחורי הרישיון של המשלוח, בתוספת שלושה קשרים שנטענים מראש — provider, provider_settings[] ו-current_provider_settings. אינו מבנה מנוהל בקפידה; ראו את האזהרה בהמשך.
license_keystringהרישיון שהמשלוח שייך אליו — המבדיל כאשר token הרחיב את החיפוש על פני כמה רישיונות.
codestringשדה ה-code של תשובת חברת השילוח השמורה; "" כאשר חברת השילוח לא החזירה אחד.
shipping_linestringקו/מסלול של חברת השילוח, נגזר מחדש מתשובת חברת השילוח השמורה.
line_branchstring | 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הודעות רישיוןראו אימות.
500Validation Failedlicense_key, domain או ids חסרים או מטיפוס שגוי. פרטים תחת errors.
500Invalid license keyלא נקבע מזהה רישיון מהטוקן או מהמפתח.
500Order Id not foundids היה קיים אך כל האיברים היו ריקים.

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: היא מבצעת את אותו רענון תחת אימות רישיון ומחזירה רק את הסטטוס.

פרמטרים ​

נתיב ​

שדהסוגחובהתיאור
shipmentintegerכןה-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.

דוגמת בקשה ​

bash
curl --location --request POST 'https://app.shipos.co.il/api/shipping/918342/status' \
--header 'Accept: application/json'
js
// 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
<?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 שנטענים מראש, ושם נמצאת חשיפת פרטי ההזדהות שתוארה בתיבה שלמעלה; הם הושמטו כאן במכוון.

json
{
  "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.statusobject | nullסטטוס חברת השילוח שנשמר כרגע — code ו-text, בתוספת כל מה שאותה חברת שילוח מדווחת. אינו עובר נורמליזציה כאן: code יכול להיות integer, מפני שהנתיב הזה אינו מריץ normalize_shipment_response.
data.typestringregular, double או collecting. ראו סוגי משלוח.
data.shipping_typestringתווית מצורפת ומתורגמת לאותו דבר.
data.short_codestringמצורף, ואינו צורה מקוצרת של קוד המעקב — הוא כלל אינו נגזר ממנו. Shipping::getShortCodeAttribute() מחזיר קידוד אטום של כ-6 תווים של מזהה השורה של המשלוח (shippings.id); עבור קוד המעקב 68834253 הוא נצפה כ-bs6oM=. התייחסו אליו כאל אסימון אטום: הוא יכול להכיל = ותווים אחרים שאינם אלפאנומריים.
data.short_tracking_codestring | nullעמודה נפרדת ששמורה בטבלת shippings, וערך שונה בשלישית — לא short_code ולא תת-מחרוזת של קוד המעקב (נצפה: e6Pns4k). אטום גם הוא.
data.effective_label_methodstringshipos או shipping_company.
data.effective_label_typestringdefault (תרמי) או A4.
data.shipment_dataobjectה-ship_data השמור ממוזג עם הגדרות חברת השילוח שנקבעו.
data.responseobjectמעטפת, ולא ה-payload של חברת השילוח עצמו: { "response": {…payload של חברת השילוח…}, "error": "", "status": 200 }. הגוף של חברת השילוח יושב רמה אחת עמוק יותר, ב-data.response.response; data.response.status הוא סטטוס ה-HTTP של חברת השילוח כפי שתפסנו אותו, ו-data.response.error הוא "" כשלא הייתה שגיאה.
data.shipping_logarrayשורות יומן הסטטוסים של המשלוח, מהחדשה לישנה. כל שורה נושאת 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אין משלוח עם המזהה הזה.
500No order exists for this shipmentלמשלוח אין הזמנה מקושרת.
500No license exists for this shipmentלא ההזמנה ולא המשלוח מאפשרים לקבוע רישיון.
500No valid license settings for <provider>לרישיון אין שורת הגדרות עבור הספק של המשלוח.
500Not yet supported for: <provider>חברת השילוח של המשלוח אינה אחת משתים-עשרה החברות שמחוברות למעלה.
500שגיאת חברת השילוחשליפת הסטטוס מחברת השילוח נכשלה.

בלי כותרת Accept שמציינת application/json אף אחת מאלה אינה מוחזרת כ-JSON: ה-handler משיב 302 עם ההודעה ב-flash של ה-session במקום. זה כולל את ה-*/* ש-curl שולח כברירת מחדל.