Skip to content

משתמשי אפליקציה ​

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

לא לאינטגרציות של סוחרים

הקבוצה הזו משרתת את אפליקציית הנמענים הפנימית (first-party), לא חנויות. אין לה license_key, אין domain ואין מושג של חשבון אצל חברת שילוח: הגורם הפועל הוא נמען, המזוהה לפי מספר טלפון, והשער הוא סוד משותף שמוחזק בבילד של האפליקציה. רוב המשלבים לעולם לא יקראו לאלה. אם אתם שולחים חבילות, אתם רוצים את משלוחים; אם אתם מציגים מעקב לקונה, אתם רוצים את משטח המעקב, לא את זה.

אימות ​

כל route בקבוצה נמצא מאחורי needs_app_secret, שהוא השוואת כותרת בודדת:

Headerחובהתיאור
X-App-Secretכןחייב להיות זהה לסוד האפליקציה המוגדר בשרת (API_SECRET בסביבת השרת). אין ערך לכל לקוח ושום דבר אינו נגזר מהבקשה — ה-middleware משווה את הכותרת לאותה מחרוזת אחת.

כותרת חסרה או שגויה עוצרת את הבקשה ב-middleware:

json
{
  "errors": [],
  "message": "Invalid Secret Key",
  "success": false
}

סוד שגוי מחזיר HTTP 500

הדחייה משתמשת ב-helper הכישלון הגנרי של האפליקציה הזו, שסטטוס ברירת המחדל שלו הוא 500. זה לא 401 ולא 403. הסתעפו לפי success, ולא לפי מחלקת הסטטוס. ב-GET /shipping/phone_number תראו את התשובה הזו רק אחרי שכבר יש בידיכם bearer token תקף — ראו את הערת הסדר למטה.

GET /shipping/phone_number דורשת בנוסף auth:sanctum — token מסוג Bearer, מעל סוד האפליקציה. ה-token הזה הוא זה ששתי נקודות הקצה האחרות מנפיקות.

בדיקת auth:sanctum רצה לפני בדיקת סוד האפליקציה

הפקודה route:list מדפיסה את ה-middleware בסדר needs_app_secret ואחר כך auth:sanctum, אבל רשימת עדיפות ה-middleware של ברירת המחדל ב-Laravel מכילה את Illuminate\Auth\Middleware\Authenticate, ולכן האימות ממוין לפני NeedsAppSecret שאינו מתועדף — בלי קשר לסדר המודפס. ב-GET /shipping/phone_number זה הופך את סדר האבחון שהייתם מניחים: תגלו שהסוד שגוי רק אחרי שכבר יש בידיכם token תקף:

בקשההתשובה בפועל
סוד שגוי, בלי bearer token401 {"message":"Unauthenticated."} — מבנה Laravel החשוף, בלי מפתח success
סוד שגוי, עם bearer token תקף500 {"errors":[],"message":"Invalid Secret Key","success":false}
סוד נכון, בלי bearer token401 {"message":"Unauthenticated."}

שתי מעטפות שונות

הצלחות משתמשות במעטפת ארבעת המפתחות { data, message, success, warning }. כישלונות בקבוצה הזו משתמשים במבנה שלושת המפתחות של helper הכישלון, { errors, message, success } — אין data ואין מפתח warning בכישלון כאן. תשובת 401 מ-auth:sanctum אינה משתמשת באף אחת מהשתיים: זהו המבנה של Laravel עצמו, {"message":"Unauthenticated."}, בלי מפתח success כלל.

הודעות המסגרת חוזרות בשפת השרת

לקבוצת ה-routes הזו אין middleware של localization, ולכן ל-Accept-Language אין השפעה (נבדק עם Accept-Language: en). כל message וכל רשומת errors שנוצרים על ידי המסגרת מגיעים בשפת השרת — עברית — למשל "שדה phone number הוא חובה." ולא "The phone number field is required.". לעולם אל תתאימו לפי טקסט ההודעה: הסתעפו לפי סטטוס ה-HTTP ולפי מפתחות השדות שבתוך errors.


GET /get_app_user ​

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

token נוצר בכל קריאה מוצלחת, ושמו נגזר מ-device_name. tokens שהונפקו קודם אינם מבוטלים.

פרמטרים ​

שאילתה ​

שדהסוגחובהתיאור
phone_numberstringכןמספר הטלפון של הנמען, מושווה בדיוק מול ה-phone השמור.
device_namestringכןשם ל-personal access token שנוצר. עד 255 תווים.

בקשה לדוגמה ​

bash
curl --location --get 'https://app.shipos.co.il/api/get_app_user' \
--header 'X-App-Secret: {app_secret}' \
--header 'Accept: application/json' \
--data-urlencode 'phone_number=0501234567' \
--data-urlencode 'device_name=Dana iPhone 15'
js
// Node.js 18+ / דפדפנים — בלי תלויות
const query = new URLSearchParams({
  phone_number: '0501234567',
  device_name: 'Dana iPhone 15',
})

const response = await fetch(
  `https://app.shipos.co.il/api/get_app_user?${query}`,
  {
    headers: {
      'X-App-Secret': process.env.SHIPOS_APP_SECRET,
      Accept: 'application/json',
    },
  },
)

const body = await response.json()

if (!body.success) {
  throw new Error(`ShipOS v1: ${body.message}`) // "User not found"
}

const token = body.data.token // השתמשו בו כ-Bearer ב-shipping/phone_number
php
<?php

use Illuminate\Support\Facades\Http;

$body = Http::withHeaders(['X-App-Secret' => config('services.shipos.app_secret')])
    ->acceptJson()
    ->get('https://app.shipos.co.il/api/get_app_user', [
        'phone_number' => '0501234567',
        'device_name' => 'Dana iPhone 15',
    ])
    ->json();

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

$token = $body['data']['token'];

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

json
{
  "data": {
    "id": 4412,
    "name": "ישראל ישראלי",
    "phone": "0501234567",
    "created_at": "2026-03-04T09:12:00.000000Z",
    "updated_at": "2026-03-04T09:12:00.000000Z",
    "token": "412|k7Yt..."
  },
  "message": "",
  "success": true,
  "warning": ""
}

שדות התשובה ​

שדהסוגתיאור
idintegerמזהה הנמען.
namestringשם הנמען.
phonestringמספר הטלפון של הנמען. ייחודי בין הנמענים.
created_atstringחותמת זמן בפורמט ISO 8601.
updated_atstringחותמת זמן בפורמט ISO 8601.
tokenstringSanctum personal access token בטקסט גלוי, מוצג רק בתשובה הזו. שלחו אותו כ-Authorization: Bearer ….

שגיאות ​

סטטוסמתי
422phone_number או device_name חסרים, או ש-device_name ארוך מ-255 תווים. message הוא הודעת הוולידציה הראשונה; errors הוא המפה המלאה לפי שדות.
500אין נמען עם מספר הטלפון הזה (message: "User not found", errors: []), או X-App-Secret שגוי (message: "Invalid Secret Key").

POST /create_app_user ​

רישום נמען והנפקת ה-token הראשון למכשיר הקורא. נקודת הקצה היחידה בקבוצה שמקבלת גוף בקשה.

פרמטרים ​

גוף הבקשה ​

שדהסוגחובהתיאור
namestringכןשם הנמען. עד 255 תווים.
phone_numberstringכןמספר הטלפון של הנמען. חייב להיות ייחודי בין הנמענים — רישום טלפון שכבר קיים הוא כישלון ולידציה, ולא התחברות. עד 255 תווים. נשמר כ-phone.
device_namestringכןשם ל-personal access token שנוצר. עד 255 תווים.

בקשה לדוגמה ​

bash
curl --location 'https://app.shipos.co.il/api/create_app_user' \
--header 'X-App-Secret: {app_secret}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
    "name": "ישראל ישראלי",
    "phone_number": "0501234567",
    "device_name": "Dana iPhone 15"
}'
js
// Node.js 18+ / דפדפנים — בלי תלויות
const response = await fetch('https://app.shipos.co.il/api/create_app_user', {
  method: 'POST',
  headers: {
    'X-App-Secret': process.env.SHIPOS_APP_SECRET,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    name: 'ישראל ישראלי',
    phone_number: '0501234567',
    device_name: 'Dana iPhone 15',
  }),
})

const body = await response.json()

if (!body.success) {
  // 422 כאשר מספר הטלפון כבר רשום
  throw new Error(`ShipOS v1: ${body.message}`)
}

const token = body.data.token
php
<?php

use Illuminate\Support\Facades\Http;

$body = Http::withHeaders(['X-App-Secret' => config('services.shipos.app_secret')])
    ->acceptJson()
    ->post('https://app.shipos.co.il/api/create_app_user', [
        'name' => 'ישראל ישראלי',
        'phone_number' => '0501234567',
        'device_name' => 'Dana iPhone 15',
    ])
    ->json();

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

$token = $body['data']['token'];

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

זהה במבנה ל-get_app_user.

json
{
  "data": {
    "id": 4413,
    "name": "ישראל ישראלי",
    "phone": "0501234567",
    "created_at": "2026-08-31T08:40:11.000000Z",
    "updated_at": "2026-08-31T08:40:11.000000Z",
    "token": "413|9pQx..."
  },
  "message": "",
  "success": true,
  "warning": ""
}

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

json
{
  "errors": {
    "phone_number": ["שדה phone number הוא חובה."]
  },
  "message": "שדה phone number הוא חובה.",
  "success": false
}

שגיאות ​

סטטוסמתי
422שדה חסר, מעל 255 תווים, או phone_number שכבר קיים.
500X-App-Secret שגוי או חסר (message: "Invalid Secret Key").

GET /shipping/phone_number ​

רשימה מעומדת של המשלוחים הממוענים למספר הטלפון של הנמען המאומת. על אף הנתיב, אין פרמטר טלפון — הטלפון מגיע מה-token.

דורשת גם X-App-Secret וגם Authorization: Bearer {token}, כאשר ה-token הוא כזה שהונפק על ידי get_app_user או create_app_user. המשלוחים מותאמים לפי עמודת ה-phone של המשלוח עצמו מול ה-phone של בעל ה-token.

פרמטרים ​

שאילתה ​

שדהסוגחובהתיאור
per_pageintegerלאגודל העמוד. ברירת המחדל היא 100.
pageintegerלאמספר העמוד, פרמטר ה-paginator הסטנדרטי של Laravel.
searchstringלאהתאמת תת-מחרוזת על השם הפרטי/שם המשפחה של הנמען, שורות הכתובת, העיר וקוד המשלוח, ובנוסף שם הלקוח של ההזמנה, מזהה ההזמנה ומספר ההזמנה.
order_idstringלאהתאמת תת-מחרוזת על ה-order_id או ה-order_number של ההזמנה.
namestringלאהתאמת תת-מחרוזת על השם הפרטי או שם המשפחה שבמשלוח.
addressstringלאהתאמת תת-מחרוזת על שורת כתובת 1, שורת כתובת 2 או עיר.
phonestringלאהתאמת תת-מחרוזת על עמודת ה-phone של המשלוח עצמו או על ה-JSON של billing שבהזמנה. מצמצם בתוך המשלוחים של הנמען עצמו; אינו יכול להרחיב את הקבוצה.
shipping_codestringלאהתאמת תת-מחרוזת על קוד המעקב של חברת השילוח.
search_byobjectלאצורה מקוננת של חמישה מהמסננים שלמעלה, שמכובדת במקביל אליהם: search_by[order_id], search_by[name], search_by[address], search_by[phone], search_by[shipping_code]. כל אחד מתנהג כמו המקבילה השטוחה שלו, וערך ריק או falsy מדולג.
customerintegerלאהגבלה למשלוחים שההזמנה שלהם שייכת למזהה הלקוח הזה.
companyintegerלאהגבלה למשלוחים שהרישיון של ההזמנה שלהם שייך למזהה החברה הזה.
licenseintegerלאהגבלה למשלוחים על מזהה הרישיון הזה — מותאם מול ה-license_id של המשלוח עצמו או מול זה של ההזמנה שלו.
flagintegerלאהגבלה למשלוחים הנושאים את מזהה הדגל הזה.
typestringלאcod מגביל למשלוחים בגוביינא (shipment_data.is_cod אמיתי); independent מגביל למשלוחים על ספק Independent; כל ערך אחר מותאם מול עמודת ה-type של המשלוח ובנוסף מוציא משלוחים בגוביינא.
sort_by_warehousestringלאasc או desc, כשברירת המחדל היא desc. ממיין את התוצאות לפי מיקום במחסן, ומכובד רק כאשר לחשבון המאומת מופעל מיון לפי מחסן.
statusstringלאהתאמת תת-מחרוזת על טקסט הסטטוס השמור של המשלוח.
fromstringלארק משלוחים שנוצרו בתאריך הזה או אחריו.
tostringלארק משלוחים שנוצרו בתאריך הזה או לפניו.
active_onlybooleanלאכאשר true, מגביל למשלוחים שבהם is_active מוגדר — כלומר כאלה שלא בוטלו.
label_generatedbooleanלאסינון לפי האם נוצרה תווית.
sort_bystringלאהעמודה שלפיה למיין, מוסמכת מול טבלת ה-shippings. חייבת להישלח יחד עם sort_direction.
sort_directionstringלאasc או desc. חייב להישלח יחד עם sort_by.

בלי sort_by / sort_direction, התוצאות חוזרות מהחדשות לישנות (shippings.created_at בסדר יורד).

בקשה לדוגמה ​

bash
curl --location --get 'https://app.shipos.co.il/api/shipping/phone_number' \
--header 'X-App-Secret: {app_secret}' \
--header 'Authorization: Bearer {token}' \
--header 'Accept: application/json' \
--data-urlencode 'per_page=20'
js
// Node.js 18+ / דפדפנים — בלי תלויות
const query = new URLSearchParams({ per_page: '20' })

const response = await fetch(
  `https://app.shipos.co.il/api/shipping/phone_number?${query}`,
  {
    headers: {
      'X-App-Secret': process.env.SHIPOS_APP_SECRET,
      Authorization: `Bearer ${token}`,
      Accept: 'application/json',
    },
  },
)

const body = await response.json()

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

console.log(body.data.client, body.data.meta.total)

for (const shipment of body.data.shippings) {
  console.log(shipment.shipping_id, shipment.shipping_status)
}
php
<?php

use Illuminate\Support\Facades\Http;

$body = Http::withHeaders(['X-App-Secret' => config('services.shipos.app_secret')])
    ->withToken($token)
    ->acceptJson()
    ->get('https://app.shipos.co.il/api/shipping/phone_number', [
        'per_page' => 20,
    ])
    ->json();

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

foreach ($body['data']['shippings'] as $shipment) {
    logger()->info($shipment['shipping_id'].' '.$shipment['shipping_status']);
}

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

json
{
  "data": {
    "client": "ישראל ישראלי",
    "shippings": [
      {
        "orderId": "M85157304",
        "shipping_id": "171258",
        "shipping_status": "פעיל",
        "shipping_date": "2026-07-07T13:44:04.000000Z",
        "shipping_company": "Lionwheel test",
        "shipping_from": null,
        "shipping_from_logo": "https://app.shipos.co.il/storage/logos/lionwheel-test.png",
        "items": []
      }
    ],
    "meta": {
      "current_page": 1,
      "per_page": 20,
      "total": 3,
      "last_page": 1,
      "from": 1,
      "to": 3,
      "path": "https://app.shipos.co.il/api/shipping/phone_number",
      "first_page_url": "...",
      "last_page_url": "...",
      "next_page_url": null,
      "prev_page_url": null,
      "links": [ { "...": "..." } ]
    }
  },
  "message": "",
  "success": true,
  "warning": ""
}

שדות התשובה ​

שדהסוגתיאור
clientstringה-name של הנמען המאומת.
shippingsarrayעמוד המשלוחים הנוכחי, כל אחד במבנה של ShippingResource — בדיוק שמונה מפתחות, המפורטים למטה.
metaobjectה-payload של ה-paginator של Laravel פחות מפתח ה-data שלו — current_page, per_page, total, last_page, from, to, path, first_page_url, last_page_url, next_page_url, prev_page_url, links.

כל רשומה ב-shippings:

שדהסוגתיאור
orderIdstringהאסמכתא של הסוחר להזמנה — ה-order_id של ההזמנה, למשל "M85157304".
shipping_idstringמזהה המשלוח, כמחרוזת. זהו מזהה השורה ב-ShipOS, ולא קוד המעקב של חברת השילוח.
shipping_statusstringטקסט הסטטוס, בשפת השרת — למשל "פעיל".
shipping_datestringחותמת זמן היצירה בפורמט ISO 8601.
shipping_companystringשם התצוגה של חברת השילוח שאיתה יצא המשלוח.
shipping_fromstring | nullתווית השולח / נקודת האיסוף. null במשלוחים שאין להם כזו.
shipping_from_logostringכתובת URL מלאה ל-storage של ShipOS עבור הלוגו.
itemsarrayפריטי השורה של המשלוח. [] כאשר אין למשלוח מוצרים.

כל רשומה ב-items נושאת את item_name, item_price, item_price_formatted, item_quantity, item_total, item_total_formatted, item_image ו-item_currency.

shippings הוא המבנה של ShippingResource

ה-handler בונה אוסף ShippingResource בשורה אחת ומחזיר את $shippings->items() בשורה שאחריה. השורה הראשונה נראית כמו קוד מת, אבל היא נושאת משקל: ShippingResource::collection($shippings) משנה את ה-paginator במקום — ה-collectResource() של Laravel קורא ל-setCollection() — ולכן items() מחזיר את מופעי ה-resource, ולא את שורות המודל. לכן לכל רשומה יש בדיוק שמונת המפתחות שלמעלה, זהים בכל 137 השורות שנבדקו בשרת החי. אין בתשובה id, uuid, first_name, city, order או products בשום מקום.

שגיאות ​

סטטוסמתי
401token מסוג Authorization: Bearer חסר או שגוי. מכיוון ש-auth:sanctum ממוין לפני בדיקת הסוד, זו גם התשובה על X-App-Secret שגוי כל עוד ה-token חסר. מבנה Laravel החשוף — {"message":"Unauthenticated."}, בלי מפתח success.
422per_page אינו integer.
500X-App-Secret שגוי או חסר (message: "Invalid Secret Key") — נגיש רק אחרי שיש bearer token תקף.
500Attempt to read property "currency" on null — פגם בצד השרת ב-ShippingResource, שנדרך על ידי משלוח בעמוד המבוקש שיש לו מוצרים אבל הזמנה null. ראו למטה.

ברירת המחדל המתועדת עלולה להחזיר HTTP 500

per_page אכן מקבל כברירת מחדל את 100, אבל קריאה בלי שום פרמטר — ברירת המחדל המתועדת — החזירה 500 בשרת החי: "Attempt to read property \"currency\" on null", שנזרק מ-app/Http/Resources/Api/ShippingResource.php. שלוש שורות במיפוי ה-items קוראות את $this->order->currency בלי הגנה, בעוד השדות השכנים משתמשים נכון ב-?->, ולכן משלוח בודד שיש לו מוצרים אבל הזמנה מנותקת (null) מפיל את כל העמוד. זהו פגם ידוע בצד השרת, ולא שגיאה של הקורא: האם תיתקלו בו תלוי בנתונים של הנמען, ולא בבקשה שלכם. בחיפוש בינארי בשרת החי על נמען אחד, per_page=7 החזיר 200 ו-per_page=8 החזיר 500 — כלומר per_page קטן יכול לעקוף שורה בעייתית. הפגם לא תוקן.


המקבילה ב-v2 ​

אין. API v2 מאמת סוחר באמצעות client credentials ותוחם את הכול לרישיונות של אותו סוחר; אין לו זהות של נמען, אין התחברות לפי מספר טלפון ואין שער של סוד אפליקציה. אין ב-v2 שום דבר להעביר אליו את שלוש נקודות הקצה האלה.