GET /vehicles/{plate}, אין מפתח API, אין מסמך OpenAPI ואין SLA. מה שיש זה משהו אחר, ולמעשה טוב לא פחות: משרד התחבורה מפרסם את מרשם הרכב כמאגרי נתונים פתוחים בפורטל data.gov.il, שרץ על CKAN. ל-CKAN יש ממשק HTTP לשאילתות על טבלאות — datastore_search — וממנו אפשר לשלוף רשומת רכב לפי מספר רישוי. המדריך הזה מסביר איך לעשות את זה נכון, ומה נשבר בדרך.
ההבדל הזה אינו סמנטי, והוא ישפיע על כל החלטת ארכיטקטורה שתקבל. ב-API מוצר יש חוזה: סכמה מתועדת, גרסאות, קודי שגיאה, מדיניות תאימות לאחור. במאגר נתונים פתוח יש קובץ — או מדויק יותר, טבלה שנטענה למחסן נתונים — ומעליה שכבת שאילתות גנרית.
המשמעויות המעשיות:
mispar_rechev, tozeret_nm, shnat_yitzur — בתעתיק לטיני של עברית, ולעיתים באיות לא עקבי בין מאגרים. אף אחד לא התחייב לשמור על השמות האלה, ובפועל הם משתנים מדי פעם./v1/ שיגן עליך משינוי סכמה.resource_id) יכול להתחלף. כשמפרסמים גרסה חדשה של מאגר, לפעמים נוצר משאב חדש עם מזהה חדש. קוד שמקודד את המזהה קשיח יישבר בשקט.כל השליפות עוברות דרך כתובת אחת:
https://data.gov.il/api/3/action/datastore_search
זו קריאת GET רגילה עם פרמטרים ב-query string, ללא אימות וללא מפתח. הפרמטרים שבאמת חשובים:
| פרמטר | סוג | למה הוא משמש |
|---|---|---|
resource_id | UUID | חובה. מזהה הטבלה שממנה שולפים. מתקבל מעמוד המאגר בפורטל. |
q | מחרוזת או JSON | חיפוש חופשי בכל העמודות, או אובייקט JSON לחיפוש בעמודה מסוימת. |
filters | JSON | סינון לפי התאמה מדויקת של עמודה לערך. זה מה שכדאי להשתמש בו למספר רישוי. |
limit | מספר | מספר הרשומות המוחזרות. ברירת המחדל נמוכה, אז כדאי לציין במפורש. |
offset | מספר | דילוג על רשומות — הבסיס לעימוד. |
fields | מחרוזת | רשימת עמודות מופרדת בפסיקים, כדי לצמצם את גודל התשובה. |
sort | מחרוזת | מיון, למשל shnat_yitzur desc. |
ההבדל בין q ל-filters חשוב. q הוא חיפוש טקסטואלי — נוח, אבל עלול להחזיר התאמות חלקיות ורשומות לא רלוונטיות. filters הוא השוואה מדויקת, ולכן הוא הבחירה הנכונה כשמחפשים מספר רישוי ספציפי. בפועל, מימוש עמיד מנסה filters קודם ונופל ל-q רק אם לא נמצאה רשומה, כי שם העמודה שונה בין מאגרים.
לא נכתוב כאן מזהי UUID. מזהים מומצאים או מיושנים הם הדרך המהירה ביותר להישבר בייצור, והמזהים האמיתיים מתחלפים. קח אותם מהמקור:
https://data.gov.il/api/3/action/package_list ואז package_show?id=... לקבלת המשאבים של כל מאגר.
חיפוש מספר רישוי עם התאמה מדויקת, באמצעות filters. שים לב לקידוד ה-JSON ב-URL — עם curl נוח להשתמש ב---data-urlencode יחד עם -G, כדי לא לקודד ידנית:
curl -sG "https://data.gov.il/api/3/action/datastore_search" \
--data-urlencode 'resource_id=RESOURCE_ID' \
--data-urlencode 'filters={"mispar_rechev":"1234567"}' \
--data-urlencode 'limit=5' \
-H 'Accept: application/json'
וגרסת החיפוש החופשי, שימושית כשאינך בטוח בשם העמודה במאגר הספציפי:
curl -sG "https://data.gov.il/api/3/action/datastore_search" \
--data-urlencode 'resource_id=RESOURCE_ID' \
--data-urlencode 'q=1234567' \
--data-urlencode 'limit=5'
אם אתה מעדיף לבנות את ה-URL בעצמך, זכור שסימני {, } ו-" חייבים להיות מקודדים. שאילתת filters שנשלחת בלי קידוד תקין תחזור עם שגיאת ולידציה, ולא עם תוצאה ריקה — מה שדווקא נוח לדיבוג.
לגלות אילו עמודות קיימות במשאב לפני שמסננים לפיהן — פשוט שלוף רשומה אחת וקרא את המפתחות:
curl -sG "https://data.gov.il/api/3/action/datastore_search" \
--data-urlencode 'resource_id=RESOURCE_ID' \
--data-urlencode 'limit=1' | python3 -m json.tool | head -60
אותה שליפה מצד לקוח או מ-Node. שים לב ל-URLSearchParams, שמטפל בקידוד עבורך, ולטיפול בשגיאות שמפריד בין כשל HTTP לכשל לוגי:
const BASE = 'https://data.gov.il/api/3/action/datastore_search';
async function lookupVehicle(resourceId, plate, { column = null, limit = 5 } = {}) {
const params = new URLSearchParams({
resource_id: resourceId,
limit: String(limit),
});
if (column) {
params.set('filters', JSON.stringify({ [column]: plate }));
} else {
params.set('q', plate);
}
const res = await fetch(BASE + '?' + params.toString(), {
headers: { Accept: 'application/json' },
});
if (!res.ok) {
throw new Error('datastore_search HTTP ' + res.status);
}
const json = await res.json();
// CKAN מחזיר success:false עם קוד 200 בחלק מהמקרים
if (!json.success) {
throw new Error('datastore_search failed: ' + JSON.stringify(json.error));
}
return {
records: json.result.records,
total: json.result.total,
fields: json.result.fields,
};
}
// שימוש
const { records, total } = await lookupVehicle('RESOURCE_ID', '1234567', {
column: 'mispar_rechev',
});
if (total === 0) {
console.log('לא נמצאה רשומה');
} else {
console.log(records[0]);
}
HTTP 200 עם success: false בגוף התשובה. בדיקה של res.ok לבדה אינה מספיקה — חייבים לבדוק גם את json.success, אחרת תקבל חריגה מוזרה בשלב שבו אתה נוגע ב-result.records.
התשובה עוטפת את התוצאה באובייקט קבוע. המפתחות שאתה צריך:
success — בוליאני. תמיד לבדוק.result.records — מערך של אובייקטים, רשומה לשורה. כאן נמצא המידע.result.total — סך כל הרשומות שתאמו את השאילתה, ולא מספר הרשומות שהוחזרו. זה הנתון שמאפשר לבנות עימוד ולהחליט אם יש עוד עמודים.result.fields — תיאור העמודות והטיפוסים שלהן. שימושי מאוד לגילוי סכמה בזמן ריצה, במקום להניח שמות שדות.result.limit / result.offset — הדהוד של מה שביקשת.result._links — קישורי next/prev יחסיים, שאפשר להתעלם מהם ולנהל את העימוד לבד.
כל הערכים ברשומות מוחזרים לרוב כמחרוזות, גם כשמדובר בשנה או בנפח מנוע. אל תסמוך על טיפוסים: המר במפורש, וטפל בערכים חסרים, שיכולים להיות null, מחרוזת ריקה או מקף.
לשליפה של רשומת רכב בודדת אין צורך בעימוד — limit=1 או limit=5 מספיק. עימוד נדרש כשמורידים חתיכות גדולות, למשל בניית קאש מקומי או ניתוח סטטיסטי.
async function* iterateAll(resourceId, pageSize = 1000) {
let offset = 0;
let total = Infinity;
while (offset < total) {
const params = new URLSearchParams({
resource_id: resourceId,
limit: String(pageSize),
offset: String(offset),
});
const res = await fetch(BASE + '?' + params.toString());
const json = await res.json();
if (!json.success) throw new Error('page failed at offset ' + offset);
total = json.result.total;
const batch = json.result.records;
if (batch.length === 0) break;
yield batch;
offset += batch.length;
// נשימה בין עמודים — לא להציף שירות ציבורי
await new Promise((r) => setTimeout(r, 250));
}
}
כמה כללי אצבע: השרת עשוי לכפות תקרה על limit, ולכן קדם על בסיס batch.length בפועל ולא על בסיס pageSize שביקשת. הוסף השהיה קצרה בין עמודים. תמיד קבע תנאי עצירה על מערך ריק, כי הסתמכות על total לבדה תיכשל אם המאגר מתעדכן בזמן הסריקה. ואם אתה באמת צריך את כל המאגר — עדיף להוריד את קובץ ה-CSV המלא מעמוד המאגר במקום לעמד מאות אלפי רשומות דרך ה-API.
זו הנקודה שמפילה את רוב הפרויקטים בשלב הראשון. קריאה ישירה מדפדפן לכתובת של data.gov.il תלויה בכותרות ה-CORS שהפורטל מחזיר, והן אינן מובטחות ואינן יציבות לאורך זמן. גם כשזה עובד היום, אין התחייבות שזה יעבוד מחר, ואין למי לפנות. בנוסף, קריאה מהדפדפן חושפת את כל הלוגיקה שלך, לא מאפשרת קאשינג משותף בין משתמשים, ומעבירה כל שגיאה של השירות הציבורי ישירות לפני המשתמש.
הפתרון הנכון הוא שכבת ביניים משלך: פונקציית edge, serverless function או שרת קטן שמבצע את הקריאה מצד השרת ומחזיר JSON נקי ללקוח. היתרונות מצטברים במהירות:
אם אתה עובד עם Netlify Functions, Cloudflare Workers, Vercel או Supabase Edge Functions — כל אחת מהן מספיקה בהחלט לתפקיד. שים לב שהמדריך הזה מוגש מאתר עם מדיניות CSP קפדנית, ולכן כל דומיין חיצוני שאתה קורא אליו צריך להיות מוצהר במפורש ב-connect-src.
אין מסמך רשמי שמגדיר מדיניות הגבלת קצב, וזו בדיוק הסיבה להתנהג בזהירות — הכתובת אינה מגבילה אותך בצורה צפויה, אבל היא כן מאטה ומחזירה שגיאות תחת עומס. עשה זאת:
מספרי רישוי בישראל מוצגים לאדם עם מקפים, ונשמרים במאגר כספרות בלבד. בין הפורמט שהמשתמש מקליד לפורמט שהמאגר מכיל יש פער שחייב טיפול, אחרת תקבל "לא נמצא" על רכב שקיים.
לא הכול נמצא בטבלה אחת, וזו נקודה עיקרית בתכנון. בדיקה שנראית למשתמש כמו פעולה אחת מורכבת מכמה שליפות ממאגרים שונים, שלכל אחד סכמה משלו:
| מאגר | מה יש בו | הערה למפתח |
|---|---|---|
| רכבים פרטיים ומסחריים | הליבה: יצרן, דגם, שנה, נפח מנוע, סוג בעלות, תוקף רישיון, ק״מ בטסט | המאגר הגדול והמרכזי. נקודת ההתחלה כמעט תמיד. |
| דו-גלגלי | אופנועים וקטנועים: נפח מנוע, סוג, דרגת רישיון נדרשת | סכמה שונה מהמאגר הפרטי. אל תשתמש באותו מיפוי שדות. |
| צמ״ה / טרקטורים | ציוד מכני הנדסי ורכב חקלאי: מחפרון, מלגזה, דחפור, טרקטור | מספרי רישוי בפורמט שונה. דורש נרמול נפרד. |
| רכב כבד | משאיות ואוטובוסים: משקל כולל, סוג מרכב | שדות משקל ומידות שאינם קיימים ברכב פרטי. |
| ריקולים | קריאות בטיחות שדווחו, כולל אלה שטרם בוצעו | ההצטלבות למספר רישוי לא תמיד ישירה. קרא היטב את השדות. |
| ביטול סופי / ירידה מהכביש | רכבים שירדו מהכביש לצמיתות | מאגר שהתשובה החשובה בו היא בינארית: נמצא או לא נמצא. |
| יבוא אישי | רכבים שיובאו ביבוא אישי | שימושי כאיתות ולא כפרט טכני. |
מה שבמפורש אינו קיים באף אחד מהמאגרים האלה: תאונות ותביעות ביטוח, שעבודים ועיקולים (אלה אצל רשם המשכונות, במערכת נפרדת לחלוטין), היסטוריית טיפולים במוסך, ופרטי הבעלים. אם מוצר שלך מרמז שהוא "בודק אם הרכב היה בתאונה" על בסיס המאגרים האלה — זו הצהרה שאינה נכונה.
המאגרים מתעדכנים במנות, לא בזמן אמת. תדירות העדכון שונה בין מאגר למאגר, ומופיעה בעמוד המאגר בפורטל — לפעמים יומית, לפעמים בתדירות נמוכה יותר. פועל יוצא מכך: העברת בעלות שבוצעה השבוע לא בהכרח משתקפת בשליפה של היום, ורכב חדש לגמרי עשוי להיעדר.
המסקנה המוצרית פשוטה: הצג למשתמש שהמידע מקורו במאגר ציבורי ואינו בזמן אמת. אם המאגר חושף תאריך עדכון — הצג אותו. עדיף משתמש שמבין את מגבלות הנתון על משתמש שסומך על נתון מיושן בלי לדעת.
הנתונים מפורסמים כנתונים ציבוריים פתוחים, ותנאי השימוש המחייבים הם אלה שמופיעים בפורטל ובעמוד המאגר הספציפי — שם צריך לקרוא, לא בפוסטים. שלושה כללים שכדאי לאמץ בכל מקרה:
car.lior-ai.com הוא מימוש חי של כל מה שכתוב כאן: שליפה מכמה מאגרים במקביל, נרמול מספרי רישוי, מיפוי שמות עמודות בתעתיק לשדות בעברית, נתיבי כשל למשתמש, וקישור החוצה לרשם המשכונות למה שאינו קיים בנתונים הפתוחים. אפשר להזין מספר רישוי ולראות אילו שדות בפועל חוזרים ואילו לא.
🚗 לבדיקה חיה לפי מספר רישוירשימת המאגרים בשימוש: מקורות המידע • המדריך לצד הצרכן: בדיקת רכב לפני קנייה 2026 • עוד שירותי AI: lior-ai.com/tools
לא במובן הקלאסי. אין endpoint מוצרי מתועד עם גרסאות ו-SLA. מה שקיים הוא מאגרי נתונים פתוחים בפורטל data.gov.il, שרץ על CKAN, וממשק שאילתות גנרי בשם datastore_search. דרכו אפשר לשלוף רשומת רכב לפי מספר רישוי בקריאת GET רגילה, בלי אימות ובלי מפתח.
לא. השאילתות דרך datastore_search פתוחות ואינן דורשות מפתח או הרשמה. בגלל זה גם אין לך מנגנון מדידה או מכסה מובטחת, ולכן חשוב לקאשש ולהתנהג בעדינות מול שירות ציבורי.
בעמוד המאגר בפורטל, בלשונית ה-API של המשאב הרלוונטי — למשל בעמוד המאגר של רכבים פרטיים ומסחריים. רשימה מרוכזת בעברית של המאגרים שאנחנו משתמשים בהם נמצאת בעמוד המקורות. אנחנו במתכוון לא מפרסמים כאן UUID קשיח, כי מזהים מתחלפים וקוד שמקודד אותם נשבר בשקט.
תלוי בכותרות CORS שהפורטל מחזיר, והן אינן מובטחות ואינן יציבות לאורך זמן. בפועל, כמעט כל מימוש רציני מוסיף שכבת ביניים — פונקציית edge או serverless — שקוראת מצד השרת. זה נותן שליטה על CORS, קאשינג משותף, נרמול סכמה, ואיחוד של כמה מאגרים לתשובה אחת.
JSON עם success בוליאני, ומתחת ל-result: records (מערך הרשומות), total (סך ההתאמות לשאילתה, ולא מספר הרשומות שהוחזרו), fields (תיאור העמודות), ו-limit/offset. שים לב ש-CKAN יכול להחזיר HTTP 200 עם success: false, ולכן חייבים לבדוק את שני הדברים.
בעזרת limit ו-offset, כשקידום ה-offset מבוסס על מספר הרשומות שהוחזרו בפועל ולא על מה שביקשת — השרת עשוי לכפות תקרה. הוסף השהיה קצרה בין עמודים ותנאי עצירה על מערך ריק. אם אתה צריך את כל המאגר, עדיף להוריד את קובץ ה-CSV המלא מעמוד המאגר.
לא. העדכון נעשה במנות, בתדירות שמשתנה בין מאגר למאגר ומופיעה בעמוד המאגר. לכן העברת בעלות שבוצעה לאחרונה או רכב חדש מאוד עשויים לא להופיע. כדאי להציג את המגבלה הזו למשתמש במקום להסתיר אותה.
לא. תאונות ותביעות ביטוח אינן חלק מהנתונים הפתוחים, ושעבודים ועיקולים מנוהלים אצל רשם המשכונות במשרד המשפטים במערכת נפרדת לחלוטין, בעיון בתשלום סמלי. מוצר שמצהיר שהוא "בודק תאונות" על בסיס data.gov.il מצהיר משהו לא נכון.
מסירים כל תו שאינו ספרה — מקפים, רווחים, נקודות ותווי כיווניות בלתי נראים שמגיעים מהדבקה בטקסט RTL — ועובדים במחרוזות בלבד כדי לא לאבד אפסים מובילים. אין לקבוע ולידציה נוקשה של שבע ספרות, כי קיימים גם אורכים אחרים. לצמ״ה ולדו-גלגלי לעיתים נדרש טיפול נפרד.
הקוד שלך מפסיק למצוא רשומות, בלי שגיאה רועשת. שתי הגנות עוזרות: להחזיק מזהי משאבים בקונפיגורציה ולא בקוד, ולגלות סכמה בזמן ריצה מתוך result.fields במקום להניח שמות. כדאי לנטר את שיעור התשובות הריקות — עלייה פתאומית בו היא כמעט תמיד שינוי במעלה הזרם.