API שלא צריך לשלם עליו פי שלוש.

ממשק REST מלא נפתח כבר במסלול השני. אצל רוב המתחרים בישראל הוא נפתח רק בשכבה השלישית.

יש גם אוסף Postman מוכן לייבוא.

POST /api/v1/shipments
Authorization: Bearer tk_live_...
Idempotency-Key: ord-1042
Content-Type: application/json

{
  "external_id": "ORD-1042",
  "recipient": {
    "name": "רונית לוי",
    "phone": "052-123-4567"
  },
  "delivery_address": {
    "raw": "דיזנגוף 100, תל אביב",
    "notes": "קומה 3"
  },
  "delivery_window": {
    "from": "2026-08-21T09:00:00+03:00",
    "to":   "2026-08-21T13:00:00+03:00"
  },
  "service_date": "2026-08-21",
  "items": [{ "description": "חבילה", "quantity": 1, "weight_kg": 3.5 }],
  "cod": { "required": true, "amount_agorot": 14990 }
}

201 Created
{
  "object": "shipment",
  "id": "6a86673dc35a253754a924c1",
  "reference": "TT-1042",
  "external_id": "ORD-1042",
  "status": "pending",
  "livemode": true,
  "tracking_url": "https://tiktak.apps4all.net/track/xK7qP2...",
  "created_at": "2026-08-20T11:04:12.881Z"
}

ארבעה דברים שכדאי לדעת לפני שמתחילים

אימות

כל בקשה נושאת Authorization: Bearer tk_live_… או tk_test_…. המפתח מוצג פעם אחת ביצירה; אצלנו נשמר רק תקציר שלו ואי אפשר לשחזר אותו. לכל מפתח יש הרשאות משלו, והוא לא יכול לקבל יותר ממה שיש למי שיצר אותו.

מצב בדיקה

מפתח tk_test_ כותב וקורא נתוני בדיקה בלבד. הם עוברים את אותה ולידציה ואת אותה מכונת מצבים כמו נתוני אמת — אבל לא נספרים במכסה, לא מחויבים, ולא מופיעים בלוח המשלוחים. מפתח live לא רואה אותם כלל, ולהפך.

Idempotency

שלחו Idempotency-Key בכל כתיבה. שליחה חוזרת עם אותו מפתח מחזירה את התשובה המקורית במקום לבצע שוב — זה מה שמונע משלוח כפול כשהחיבור נופל אחרי שהבקשה כבר הגיעה. אותו מפתח עם תוכן שונה נדחה ב-409. המפתח נשמר 24 שעות.

נקודות קצה

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

  • GET/api/v1
  • GET/api/v1/shipments
  • POST/api/v1/shipments
  • GET/api/v1/shipments/{id}
  • PATCH/api/v1/shipments/{id}
  • DELETE/api/v1/shipments/{id}
  • GET/api/v1/shipments/{id}/events
  • GET/api/v1/routes
  • GET/api/v1/routes/{id}
  • GET/api/v1/drivers
  • GET/api/v1/customers
  • POST/api/v1/customers
  • GET/api/v1/webhooks
  • POST/api/v1/webhooks
  • PATCH/api/v1/webhooks/{id}
  • DELETE/api/v1/webhooks/{id}
  • GET/api/v1/webhooks/{id}/deliveries
  • POST/api/v1/webhooks/{id}/test

שגיאות

כל שגיאה חוזרת באותו מבנה. type לסיווג גס שעליו מסתעפים, code יציב למכונה, ו-request_id שכדאי לשמור בלוג — הוא מה שמאפשר לנו לאתר את הבקשה המדויקת שלכם.

  • 400invalid_request_errorמשהו בבקשה לא תקין
  • 401authentication_errorמפתח חסר, שגוי או שפג תוקפו
  • 403permission_errorלמפתח אין את ההרשאה הנדרשת
  • 404not_found_errorלא נמצא — או שייך למצב אחר (live מול test)
  • 409conflict_errorכפילות, או מצב שלא מאפשר את הפעולה
  • 429rate_limit_errorחריגה מקצב הבקשות של המפתח
  • 500api_errorתקלה אצלנו. שלחו לנו את ה-request_id
422 → לא. אצלנו שגיאת ולידציה היא 400.

400 Bad Request
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_failed",
    "message": "שדה recipient.name: Required",
    "param": "recipient.name",
    "doc_url": "https://tiktak.apps4all.net/developers#errors",
    "request_id": "9c1e4a77b0d2f3e5"
  }
}

Webhooks

כל קריאה חתומה ב-HMAC-SHA256 מעל {t}.{body}, בכותרת TikTak-Signature. חותמת הזמן נכללת בחתימה בכוונה: בלעדיה אפשר היה לשדר מחדש קריאה שנלכדה, והיא הייתה מאומתת. אמתו תמיד — כתובת ה-webhook שלכם היא endpoint ציבורי, וכל מי שמכיר אותה יכול לשלוח אליה.

החזירו 2xx. כל תשובה אחרת גוררת ניסיון חוזר לפי 10 שניות, דקה, 5 דקות, 30 דקות, שעתיים, 6 שעות ו-24 שעות. הפניות (3xx) נחשבות כישלון — אנחנו לא מעבירים גוף חתום לכתובת שלא הסכמנו לדבר איתה. TikTak-Event-Id יציב בין ניסיונות, אז השתמשו בו לדה-דופליקציה.

// אימות חתימה — Node
import crypto from 'node:crypto';

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map((p) => p.trim().split('=')),
  );

  // מחוץ לחלון הזמן = ניסיון שידור חוזר של הודעה ישנה
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!(age < 300)) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');

  // השוואה בזמן קבוע, לא ===
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1),
  );
}
<?php
// אימות חתימה — PHP
function tiktak_verify(string $body, string $header, string $secret): bool {
    $parts = [];
    foreach (explode(',', $header) as $piece) {
        [$k, $v] = array_map('trim', explode('=', $piece, 2));
        $parts[$k] = $v;
    }

    if (abs(time() - (int) $parts['t']) > 300) {
        return false;
    }

    $expected = hash_hmac('sha256', $parts['t'] . '.' . $body, $secret);
    return hash_equals($expected, $parts['v1']);
}

אירועים

  • shipment.createdמשלוח חדש נוצר
  • shipment.updatedפרטי משלוח עודכנו
  • shipment.assignedהמשלוח שויך לנהג
  • shipment.picked_upהמשלוח נאסף
  • shipment.in_transitהנהג יצא לדרך עם המשלוח
  • shipment.deliveredהמשלוח נמסר
  • shipment.failedניסיון מסירה נכשל
  • shipment.cancelledהמשלוח בוטל
  • shipment.eta_updatedזמן ההגעה המשוער עודכן
  • route.publishedמסלול פורסם לנהג
  • route.startedהנהג התחיל מסלול
  • route.completedמסלול הושלם
  • pod.capturedנקלטה הוכחת מסירה
  • review.submittedלקוח דירג את המסירה