Bi Express API
Sitenizden Bi Express'e kargo oluşturun, takip edin ve kurye zimmetinden önce iptal edin. Kargo durumu değiştiğinde webhook ile haber alın.
- Adres:
https://bi.express/api/v1 - Biçim: JSON. Her istekte
Accept: application/jsongönderin. - Kimlik:
Authorization: Bearer <token>. Token'ı Bi Express yöneticisi firmanız için üretir (admin paneli → Firmalar → firma → "API token oluştur"); her firmanın tek bir aktif token'ı olur. Yeni token üretildiğinde eskisi geçersizleşir. - Ücret: Kargo ücretini Bi Express belirler; istekte gönderilen ücret dikkate alınmaz.
package_type_idgönderilmezse varsayılan paket tipinin fiyatı uygulanır. - Limit: Token başına dakikada 60 istek. Aşılırsa
429döner. - Her token yalnızca kendi firmasının kargolarını görür ve yönetir.
Token yetkileri ve IP kısıtı
Token iki yetkiden biriyle verilir:
| Yetki | İzin verilen |
|---|---|
| Okuma + yazma (varsayılan) | Tüm uç noktalar |
| Yalnızca okuma | GET uç noktaları ve POST /labels (etiket linki üretir, veri değiştirmez) |
Yalnızca okuma yetkisi olan token ile POST /cargoes veya POST /cargoes/{code}/cancel çağrılırsa şu yanıt döner: 403 {"message": "Bu token yalnızca okuma yetkili; bu işlem için yazma yetkisi gerekiyor."}. Bu özellikten önce üretilmiş token'lar tam yetkilidir ve çalışmaya devam eder.
IP kısıtı: Firmanız için izinli IP listesi tanımlanabilir. Her satıra bir IP ya da CIDR yazılır; IPv4 ve IPv6 desteklenir (ör. 203.0.113.10, 198.51.100.0/24, 2001:db8::/32). Liste boşsa her IP'den erişilebilir. Listede olmayan bir IP'den gelen istek şu yanıtı alır: 403 {"message": "Bu IP adresinden erişime izin verilmiyor: <ip>"}. Kontrol, sunucuya doğrudan bağlanan IP'ye göre yapılır; X-Forwarded-For başlığı dikkate alınmaz.
Bölgeler
7 teslimat bölgesi vardır: Lefkoşa, Gazimağusa, Girne, Güzelyurt, İskele, Lefke ve Karpaz. Her bölgenin köy/mahalle listesi /cities yanıtında gelir:
[ { "id": 3, "name": "Girne", "places": [ { "id": 101, "name": "Alsancak" }, { "id": 102, "name": "Çatalköy" } ] } ]
id'leri sabit kodlamayın; listeyi günde bir kez çekip önbelleğe alın.
Uç noktalar
| Yöntem | Yol | Ne yapar |
|---|---|---|
| GET | /cities |
Teslimat bölgeleri ve köy/mahalleleri (id, name, places[]) |
| GET | /package-types |
Paket tipleri (id, name, price); price firmanıza uygulanan ücrettir (size özel teklif varsa o) |
| POST | /cargoes |
Kargo oluşturur |
| GET | /cargoes |
Kargo listesi (filtre, sayfalama, senkronizasyon) |
| GET | /cargoes/{code} |
Kargo durumu ve geçmişi |
| POST | /cargoes/{code}/cancel |
Kargoyu iptal eder (zimmetten önce) |
| GET | /cargoes/{code}/label |
Etiket yazdırma linki (1 saat geçerli) |
| POST | /labels |
Toplu etiket yazdırma linki (en fazla 200 kargo) |
| GET | /balance |
Bakiye: alacak, ödemeye hazır, kuryede, ödenen, son ödemeler |
| GET | /statements?month=YYYY-MM |
Aylık ekstre (JSON veya CSV) |
| GET | /payouts |
Size yapılan ödemeler ve her ödemenin kapattığı kargolar |
Kargo oluştur: POST /cargoes
| Alan | Tip | Zorunlu | Not |
|---|---|---|---|
full_name |
string ≤255 | evet | Alıcı adı |
phone |
string ≤20 | evet | Alıcı telefonu |
address |
string ≤2000 | evet | Açık adres |
city_id |
int | evet | /cities listesinden |
place_id |
int | hayır | Köy/mahalle; city_id bölgesine ait olmalı (aksi 422) |
package_type_id |
int | hayır | /package-types listesinden; kargo ücreti buradan hesaplanır |
packages_count |
int 1–100 | hayır | Koli adedi; her koli için ayrı etiket basılır ("Koli 1/3") |
is_payment_on_delivery |
bool | hayır | Kapıda ödeme |
package_price |
sayı ≥0 | is_payment_on_delivery false değilse evet |
Kapıda tahsil edilecek tutar |
cod_method |
cash / pos |
hayır | Alıcının kapıda nasıl ödeyeceği (kurye POS cihazı alsın mı). Boşsa cash; kapıda ödeme yoksa yok sayılır ve null döner. Firma kapıda POS hizmeti almıyorsa pos → 422 (cod_method) |
is_exchange / is_return |
bool | hayır | Değişim / iade gönderisi |
desc |
string ≤1000 | hayır | Kurye notu |
external_ref |
string ≤100 | önerilir | Sizin sipariş numaranız |
Tekrar güvenliği: Aynı external_ref ile tekrar istek atarsanız yeni kargo açılmaz; mevcut kargo 200 ile döner (ilk oluşturmada 201). Ağ hatasında isteği güvenle tekrarlayabilirsiniz.
curl -X POST https://bi.express/api/v1/cargoes \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"full_name":"Ayşe Yılmaz","phone":"05331234567","address":"Ortaköy, Lefkoşa",
"city_id":1,"package_type_id":2,"is_payment_on_delivery":true,
"package_price":450,"external_ref":"WC-1042"}'
{
"code": "K7Q2M9XA",
"external_ref": "WC-1042",
"delivery_code": "4821",
"tracking_url": "https://bi.express/takip?kod=K7Q2M9XA",
"status": "Pending",
"status_label": "Kargo oluşturuldu",
"cancelable": true,
"courier": null,
"cod_method": "cash"
}
delivery_code, alıcının teslimatta kuryeye söylediği 4 haneli koddur. Alıcıya iletin.
Her kargo yanıtında bir de finance nesnesi gelir:
"finance": { "fee": 250, "cod_amount": 450, "pos_bisiparis": 0, "havale_bisiparis": 0, "pos_commission": 0, "net": 200, "cod_collected_at": null, "paid_out_at": null }
fee kargo ücretidir. cod_amount kapıda tahsil edilecek tutardır; kapıda ödeme yoksa 0 olur. pos_bisiparis, kapıda kendi POS cihazınıza ya da hesabınıza (havale) Bi-siparis adına giren tutardır (havale_bisiparis bunun havale kısmı); bu para Bi Express'e girmediği için ödemenizden düşülür. pos_commission, firmanız Bi Express POS'unu kullanıyorsa POS ile çekilen tutarın %4'üdür ve ödemenizden kesilir; kendi POS'unuzu kullanıyorsanız 0'dır (POS sahibi firma ayarlarında belirlenir). net, size ödenecek tutarı gösterir (cod_amount - fee - pos_bisiparis - pos_commission); negatifse kargo ücreti size ödenecek tutardan düşülür. cod_collected_at kuryenin parayı tahsil ettiği, paid_out_at tutarın size aktarıldığı zamandır.
cod_method: kapıda ödeme şekli (cash / pos); kapıda ödeme yoksa null.
courier: kargo bir kuryeye zimmetlenince {"name": "Ali Veli", "phone": "05331112233"}, zimmetlenmemişse null. Telefon kayıtlı değilse phone alanı null olur. cancelable: şu an POST /cancel ile iptal edilebilir mi.
Kargo sorgula: GET /cargoes/{code}
Yukarıdaki alanlara ek olarak city, place (yoksa null) ve history döner. history en yeni olay başta olacak şekilde sıralıdır:
{ "...": "...", "city": "Lefkoşa", "place": "Gönyeli",
"history": [ { "status": "Received", "status_label": "Teslim alındı", "note": null, "at": "2026-11-04T10:12:00+02:00" } ] }
İptal: POST /cargoes/{code}/cancel
Kargo yalnızca "Kargo oluşturuldu" durumundaysa ve henüz bir kuryeye zimmetlenmediyse iptal edilebilir. Aksi halde 409 döner; bu durumda iptal için Bi Express ile iletişime geçin.
Kargo listesi: GET /cargoes
Yalnızca kendi firmanızın kargolarını en yeniden eskiye doğru listeler. Her öğe, tekil kargo yanıtıyla aynı yapıdadır (code, status, courier, finance…).
| Parametre | Açıklama |
|---|---|
status |
Durum değeri (Pending, Received, OutForDelivery, Delivered…) |
from, to |
Oluşturma tarihi aralığı (YYYY-MM-DD, iki uç dahil) |
updated_since |
ISO 8601 zaman (ör. 2026-11-02T09:30:00+03:00). Bu andan sonra değişen kargolar döner. Senkronizasyon için son çekiş zamanınızı gönderin; webhook kaçırdığınız değişiklikleri böyle yakalarsınız |
external_ref |
Sipariş numaranız |
per_page |
Sayfa boyu: varsayılan 50, en fazla 100. Sayfalar arasında page ile gezilir |
Yanıt: data (kargolar), current_page, last_page, per_page, total, next_page_url.
Etiket yazdırma
- Tek kargo:
GET /cargoes/{code}/label - Toplu:
POST /labels, gövde{"codes": ["AB12CD34", "..."]}. En fazla 200 kod; hepsi firmanıza ait olmalı. Ait olmayan kod varsa404döner ve bulunamayan kodlar mesajda yazar.
{ "url": "https://bi.express/etiketler/imzali?ids=...&signature=...", "expires_at": "2026-11-04T15:00:00+02:00" }
url 1 saat geçerlidir ve oturum ya da token gerektirmez. Tarayıcıda açılınca 100×100 mm etiket sayfası ve yazdırma penceresi gelir. Süresi dolmuş veya değiştirilmiş link 403 döner. Kargonun packages_count değeri kaç ise o kadar etiket basılır; her etikette "Koli 1/3" gibi sıra yazar, barkod hepsinde kargo kodudur.
Teslim edilemedi
Kurye teslim edemezse kargo Dağıtımda → Transferde durumuna döner ve bir sebep kaydedilir. Kargo daha sonra tekrar dağıtıma çıkabilir; bunun için ayrı bir durum yoktur.
Her kargo yanıtında şu alanlar da gelir:
| Alan | Açıklama |
|---|---|
delivery_attempts |
Başarısız teslim denemesi sayısı |
last_failure |
Son başarısız deneme: {"reason": "adreste_yok", "reason_label": "Alıcı adreste yok", "at": "..."}. Hiç başarısız deneme yoksa null |
history[] kayıtlarında failure_reason ve failure_reason_label alanları vardır; başarısız deneme olmayan kayıtlarda null olur.
| Sebep kodu | Açıklama |
|---|---|
adreste_yok |
Alıcı adreste yok |
telefon_kapali |
Telefon kapalı / ulaşılamadı |
reddetti |
Alıcı teslim almadı |
adres_hatali |
Adres hatalı / bulunamadı |
erteleme |
Alıcı ileri tarihe erteledi |
diger |
Diğer |
Herkese açık takip sayfasında yalnızca "Teslim edilemedi — " görünür; kuryenin notu gösterilmez.
Aylık ekstre: GET /statements?month=YYYY-MM
O ay içinde teslim edilen kargoları listeler. Ay, teslim tarihine göre belirlenir; böylece her kargo yalnızca bir ayın ekstresinde yer alır. İade ve iptal edilen kargolar ekstreye girmez. Bir kargonun size ödenip ödenmediğini satırdaki paid_out_at alanından görürsünüz.
{
"month": "2026-10", "currency": "TRY",
"rows": [{ "code": "AB12CD34", "external_ref": "WC-1001", "status": "Delivered",
"delivered_at": "2026-10-15T10:00:00+03:00", "cod_amount": 1000, "fee": 250, "net": 750,
"cod_collected_at": "2026-10-15T10:00:00+03:00", "paid_out_at": null }],
"totals": { "count": 1, "cod_amount": 1000, "fee": 250, "net": 750, "paid_net": 0, "unpaid_net": 750 }
}
?format=csv eklerseniz Excel'de doğrudan açılan bir CSV dosyası iner (ekstre-2026-10.csv). Ayraç ;, ondalıklar virgüllüdür ve en altta TOPLAM satırı vardır.
Bakiye: GET /balance
Firmanızın Bi Express nezdindeki hesap özetini döner. Tutarlar TL cinsindendir ve kargo ücretleri düşülmüş net değerlerdir. Rakamlar, Bi Express'in tahsilat ekranındakiyle birebir aynı hesaplanır.
{
"currency": "TRY",
"receivable": { "amount": 750, "count": 3 },
"ready_to_pay": { "amount": 500, "count": 2 },
"in_courier": { "amount": 250, "count": 1 },
"paid": { "amount": 50, "count": 1, "last_paid_at": "2026-11-03T12:00:00+02:00" },
"payouts": [ { "paid_at": "2026-11-03T12:00:00+02:00", "count": 1, "amount": 50 } ]
}
| Alan | Anlamı |
|---|---|
receivable |
Toplam alacağınız: tahsil edilmiş ama size henüz ödenmemiş tutar. Kapıda ödemesiz teslimatların ücreti düşülmüştür |
ready_to_pay |
receivable'ın kasaya girmiş, ödemeye hazır kısmı |
in_courier |
receivable'ın hâlâ kuryede olan kısmı |
paid |
Bugüne kadar size ödenen toplam |
payouts |
Son 20 ödeme, en yeni başta (id, paid_at, count, amount, method) |
advance |
Avans: size yapılan ödemelerden henüz hiçbir kargoya mahsup edilmemiş kısım (≥ 0) |
due |
Kalan borç = ready_to_pay − advance. Avans fazlaysa negatif olabilir |
Ödemeler: GET /payouts
Firmanıza yapılan ödemeleri listeler; en yeni ödeme başta gelir. Sayfa boyu per_page ile ayarlanır (1–100, varsayılan 20).
{ "data": [ { "id": 12, "paid_at": "2026-11-04T15:00:00+03:00", "amount": 5000, "method": "Havale", "note": "Ekim", "count": 2,
"cargoes": [ { "code": "GJQ5RENX", "external_ref": "BX1384655", "net": 4210.5 } ] } ],
"current_page": 1, "last_page": 1, "total": 1 }
Ödeme nasıl kargolara dağıtılır: Ödemeler tutar olarak yapılır ve parçalı olabilir. Tutar önce ücret kesintilerine (net ≤ 0 olan kargolar) mahsup edilir. Sonra en eski tahsilattan başlanarak kargolar sırayla kapanır. Tutarın yetmediği ilk kargoda durulur ve artan kısım avans olarak sonraki ödemeye aktarılır. Bi Express seçtiği kargoları doğrudan "ödendi" olarak da işaretleyebilir; bu durumda ödeme tutarı tam olarak o kargoların net toplamıdır. Ekstre satırlarındaki sender_payout_id alanı, kargoyu hangi ödemenin kapattığını gösterir.
Durumlar
status |
Anlamı |
|---|---|
Pending |
Kargo oluşturuldu |
Received |
Teslim alındı (depoda) |
InTransfer |
Transferde |
OutForDelivery |
Dağıtımda |
Delivered |
Teslim edildi (son durum) |
Returned |
İade edildi (son durum) |
Canceled |
İptal edildi (son durum) |
Hatalar
| Kod | Ne zaman |
|---|---|
401 |
Token yok ya da geçersiz |
403 |
Token bir gönderici firmaya ait değil, token yalnızca okuma yetkili, IP izinli değil ya da etiket linkinin süresi dolmuş |
404 |
Kargo yok ya da başka bir firmaya ait |
409 |
İptal artık mümkün değil |
422 |
Doğrulama hatası: {"message": "...", "errors": {"alan": ["..."]}} |
429 |
İstek limiti aşıldı; Retry-After başlığındaki süre kadar bekleyin |
Webhook
Bi Express yöneticisi firmanız için bir webhook URL (yalnızca https) ve bir imza anahtarı (secret) tanımlar. Şu durumlarda bu adrese POST atılır:
event |
Ne zaman |
|---|---|
cargo.status_changed |
Kargo oluşturulduğunda ve durumu her değiştiğinde |
cargo.courier_assigned |
Kargo bir kuryeye zimmetlendiğinde (durum değişmeden). Bu andan itibaren iptal edilemez |
cargo.delivery_failed |
Teslim denemesi başarısız olduğunda. status = InTransfer; failure_reason ve failure_reason_label dolu gelir |
payout.created |
Size bir ödeme kaydedildiğinde: {"payout_id", "amount", "paid_at", "method", "note", "cargoes": [{"code", "external_ref", "net"}], "advance_after", "currency"}. Yalnızca ücret mahsubu yapılan kapatmalarda amount 0 veya negatif olabilir |
payout.reverted |
Hatalı girilen son ödeme geri alındığında: {"payout_id", "amount", "cargoes": [{"code", "external_ref"}], "advance_after", "currency"}. Listelenen kargolar yeniden "ödenecek" durumuna döner |
ping |
Panelden "Deneme bildirimi gönder" ile atılan test isteği: {"event": "ping", "event_id": "...", "at": "..."}. 2xx dönmeniz yeterli |
cargo.status_changed ve status = Delivered olduğunda gövdeye ek olarak payments (kapıda alınan ödeme satırları: method cash/pos/havale, amount, owner, pos_owner, pos_ref, card_last4, installments, has_receipt; dekont fotoğrafı gönderilmez) ve finance (yukarıdaki yapı) eklenir.
Bütün bildirimlerde failure_reason ve failure_reason_label alanları bulunur; cargo.delivery_failed dışındaki olaylarda null gelir.
{
"event": "cargo.status_changed",
"event_id": "9b2f4c1e-6a7d-4e3b-8f0a-2c5d1e7b9a40",
"code": "K7Q2M9XA",
"external_ref": "WC-1042",
"status": "Delivered",
"status_label": "Teslim edildi",
"note": null,
"cancelable": false,
"courier": { "name": "Ali Veli", "phone": "05331112233" },
"tracking_url": "https://bi.express/takip?kod=K7Q2M9XA",
"at": "2026-11-04T15:40:00+02:00"
}
event_id her bildirim için benzersiz bir metindir (UUID); tekrar denemelerde aynı kalır.
Başlıklar: X-BiExpress-Event, X-BiExpress-Timestamp (unix saniye), X-BiExpress-Signature.
İmza doğrulama: hex(HMAC-SHA256(timestamp + "." + ham_gövde, secret)) değeri X-BiExpress-Signature başlığına eşit olmalı. Gövdeyi JSON'a çevirmeden önce ham haliyle imzalayın. 5 dakikadan eski timestamp'leri reddedin.
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_BIEXPRESS_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_BIEXPRESS_SIGNATURE'] ?? '';
$ok = abs(time() - (int) $ts) < 300
&& hash_equals(hash_hmac('sha256', $ts.'.'.$body, BIEXPRESS_WEBHOOK_SECRET), $sig);
if (! $ok) { http_response_code(401); exit; }
$event = json_decode($body, true);
// event_id ile tekrarları ayıklayın, sonra siparişi güncelleyin
http_response_code(200);
Teslim garantisi: 10 saniye içinde 2xx dönmezseniz istek tekrar denenir. Yaklaşık 30 sn, 2 dk, 10 dk, 30 dk ve 1 saat aralıklarla toplam 6 deneme yapılır. Yönlendirmeler (3xx) izlenmez. Aynı olay birden fazla kez gelebilir; event_id ile tekrarları ayıklayın.
Kayıt ve tekrar gönderim: Her bildirim denemesi Bi Express tarafında 30 gün saklanır; HTTP kodu, yanıtın ilk 1000 karakteri ve hata mesajı kaydedilir. Size ulaşmayan bir bildirimi destek ekibimiz panelden "Tekrar gönder" ile yeniden iletebilir. Tekrar gönderilen bildirim yeni bir event_id taşır ve içeriği ilk gönderildiği anki haliyle aynıdır. Kargonun güncel durumu için GET /cargoes/{code} kullanın.
Not: cancelable ve courier alanları bildirimin gönderildiği andaki durumu gösterir. İptal edip edemeyeceğinizi kesin olarak POST /cancel yanıtı belirler.
Ops (kurye) API — iç kullanım
Bi-siparis personelinin Bi Express kargolarını (her firmanın) dağıtması içindir; firma token'larıyla çağrılamaz.
Token: sunucuda php artisan ops:token <email> (yalnız ops yetkisi, panele giremeyen ops rollü kullanıcı; her çalıştırma eski token'ı iptal eder). Önek /api/v1/ops, limit 120 istek/dk. Sunucu ortamında OPS_ALLOWED_IPS (virgüllü IP/CIDR) doluysa yalnız bu IP'lerden erişilir, diğerleri 403 ip_not_allowed; boşsa kısıt yok.
Kurye, isteğin courier_phone alanıyla (her biçim +90XXXXXXXXXX'e çevrilir) kurye rolündeki kullanıcıdan bulunur; bulunamazsa 403 + "code": "courier_not_found".
Kargo yanıtında cod_method (cash / pos / kapıda ödeme yoksa null) alıcının kapıda nasıl ödeyeceğini gösterir: pos ise kurye POS cihazı almalıdır.
Hatalar diğer uçlarla aynı {"message", "errors"} biçimindedir; iş kuralı hatalarında ayrıca code gelir: courier_not_found, assigned_to_other (+ courier), not_assigned, closed, invalid_status, amount_mismatch (+ cash_in_hand), courier_inactive, phone_taken.
| Yöntem | Yol | Gövde |
|---|---|---|
| GET | /cargoes/{code}?courier_phone= |
— |
| POST | /cargoes/{code}/assign |
courier_phone, force? (başka kuryedeyse devralır) |
| POST | /cargoes/{code}/out-for-delivery |
courier_phone |
| POST | /cargoes/{code}/deliver |
courier_phone, delivery_code, payments[], amount_diff_reason?, proof_photo (base64), note? |
| POST | /cargoes/{code}/fail |
courier_phone, reason, note? |
| POST | /cargoes/{code}/price |
package_price (yalnız düşürülür), reason, courier_phone? |
| GET | /couriers/{phone}/cargoes |
— açık zimmetler |
| GET | /couriers/{phone}/cash |
— kuryedeki nakit |
| POST | /couriers/{phone}/settle |
amount (kuryedeki nakde eşit olmalı) |
| POST | /couriers |
phone, name, external_id — kurye tanımla/güncelle (upsert), active=true; telefon başka aktif kuryedeyse 409 phone_taken |
| POST | /couriers/{phone}/deactivate |
— pasif kurye zimmet alamaz, dağıtıma çıkamaz, teslim edemez (403 courier_inactive); listeler, kasa ve devir çalışır |
payments[] satırı: method (cash/pos/havale), amount; pos için pos_ref zorunlu; pos_owner gönderilebilir ama yok sayılır (POS sahibini firmanın admin ayarı belirler, yanıttaki pos_owner bu değeri gösterir: firmanın kendi POS'u → bisiparis, Bi Express POS'u → biexpress ve %4 komisyon), card_last4, installments (1–12) isteğe bağlı; havale için owner (bisiparis/biexpress, paranın gönderildiği hesap) ve receipt_photo (base64 JPEG/PNG/HEIC, en fazla 6 MB) zorunlu. Kapıda ödemede satır toplamı tahsil tutarına eşit değilse amount_diff_reason zorunludur ve tahsil tutarı toplanan tutara çekilir. Firma kapıda POS hizmeti almıyorsa (kargo yanıtında customer.pos_allowed = false) pos satırı 422 payments hatası verir. Kasaya yalnız cash satırları girer; nakit yoksa kargo teslimde kasaya alınmış sayılır.
delivery_code iade gönderisinde (kargo yanıtında is_return = true) zorunlu değildir; gönderilirse yine doğrulanır. Diğer kargolarda zorunludur. proof_photo her durumda zorunludur.
deliver ve fail isteğe bağlı location alır: {"lat", "lng", "accuracy"?, "at"?} (lat −90..90, lng −180..180, accuracy ≥ 0). Olay kaydına yazılır; kargo yanıtında son konum last_location olarak döner. Mesafe kontrolü yapılmaz.