MydemaPlugin API
app.mydema.gr · stage.mydema.gr

Mydema Plugin API — Entegrasyon Kılavuzu

Bu kılavuz, bir e-mağazayı veya ERP'yi Mydema'ya resmi OpenCart / WooCommerce eklentilerinin yaptığı şekilde bağlayan geliştiriciler içindir: ödeme adımında canlı kargo fiyatlarını göstermek, ödenmiş siparişleri Mydema gönderilerine (voucher'lara) dönüştürmek ve bunları takip etmek.

Bu klasörde çalıştırmaya hazır bir Postman koleksiyonu bulunur:

DosyaNedir
mydema-plugin-api.postman_collection.jsonBu kılavuzdaki tüm istekler, çağrı sırasıyla; token'ı, sepet id'sini ve seçilen rotayı bir istekten diğerine taşıyan script'lerle birlikte
mydema-staging.postman_environment.jsonStaging temel URL'si ve anahtarınız için yer tutucular
mydema-production.postman_environment.jsonCanlı ortam (production) temel URL'si ve anahtarınız için yer tutucular

#1. Ortamlar

OrtamTemel URL
Canlı (production)https://app.mydema.gr
Test (staging)https://stage.mydema.gr

Bu kılavuzdaki her yol (path) temel URL'ye (base URL) göredir. Anahtarı self-servis bağlantı akışıyla aldığınızda (bölüm 2.1), claim yanıtı kullanmanız gereken temel URL'yi bildirir (apiEndpoint).

Staging kendi verilerini ve kendi anahtarlarını kullanır; canlı ortam anahtarı staging'de çalışmaz.

#2. API anahtarı edinme

Bir API anahtarı (API key; mk_live_…, 48 karakter) tek bir siteye (Mydema'daki adıyla bir "integration") aittir. Her sitenin şunları vardır:

  • API'nin adına işlem yaptığı bir servis kullanıcısı (service user),
  • gönderilerinin bedelini ödeyen bir ön ödemeli kredi cüzdanı (credit wallet) (bölüm 7.9),
  • isteğe bağlı olarak, her girişin kontrol edildiği bir alan adı (domain) ve bir IP izin listesi (IP allowlist).

Anahtar edinmenin iki yolu vardır.

#2.1 Self-servis bağlantı (eklentiler için önerilir)

Mağaza bağlanmak için başvurur, Mydema personeli başvuruyu panelden onaylar ve mağaza anahtarını teslim alır. Bu üç endpoint herkese açıktır (token gerekmez).

  1. Rastgele bir gizli değer (secret) üretin (örneğin hex olarak 32 rastgele bayt) ve bunu kendi tarafınızda saklayın.
  2. POST/api/plugin/site-request isteğini mağaza bilgileriyle ve claimSecretHash = bu gizli değerin SHA-256 hex karşılığı ile gönderin. Bir requestId alırsınız.
  3. status değeri approved (veya rejectReason ile birlikte rejected) olana kadar POST/api/plugin/site-request/status endpoint'ini requestId + ham claimSecret ile yoklayın (polling). Birkaç dakikada bir yoklamak yeterlidir; onay elle verilir.
  4. POST/api/plugin/site-request/claim isteğini aynı iki değerle gönderin. apiKey, apiEndpoint ve integrationId alırsınız. Her teslim alma (claim) işlemi yeni bir anahtar üretir ve öncekini geçersiz kılar, bu yüzden anahtarı bir kez teslim alın ve saklayın.

#2.2 Mydema tarafından verilen anahtar

Mydema personeli siteyi panelde oluşturup anahtarı size doğrudan gönderebilir. Bu anahtar, teslim alınmış bir anahtarla tamamen aynı şekilde çalışır.

Anahtarı bir parola gibi saklayın. Mydema yalnızca hash'ini tutar ve anahtarı bir daha gösteremez; kaybolan bir anahtar, yeni bir teslim alma işlemi ya da Mydema'dan yeni bir anahtar demektir.

#3. Kimlik doğrulama

#3.1 Giriş yapma

HTTP
POST /api/auth/plugin-login
Content-Type: application/json

{"apiKey": "mk_live_…", "apiSecret": "", "domain": "myshop.gr"}
AlanZorunluNotlar
apiKeyevetmk_live_… anahtarı
apiSecretevet, "" olarakmk_live_ anahtarları için boş bir string olmalıdır. (Boş olmayan bir değer, eski anahtar + secret düzenine geçirir.)
domainsitenin bir alan adı varsaMağazanızın host adı. Şema (scheme), yol, port ve baştaki www. her iki tarafta da yok sayılır.

Sitenin bir IP izin listesi varsa, istek listelenen adreslerden birinden gelmelidir (birebir eşleşme, CIDR aralıkları desteklenmez). Ret mesajı, Mydema'nın gördüğü adresi size bildirir.

Yanıt (kısaltılmış):

JSON
{
  "data": {
    "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9…",
    "expiresAt": "2026-10-31T07:59:12+00:00",
    "expiresIn": 43200,
    "integration": {"id": 8, "name": "My Shop", "platform": null, "domain": "myshop.gr"},
    "tokenData": {
      "user": {"id": 260, "userRole": "PLUGIN", "customer": 273},
      "company": {"id": 4, "name": "Customers Company", "type": "CUS"},
      "plugin": true,
      "integrationId": 8,
      "pluginCustomerId": 273,
      "pluginCustomerName": "Customers Company"
    }
  }
}

integrationId ve pluginCustomerId (cüzdan sahibi) değerlerini saklayın; birkaç endpoint bunları path içinde ister.

#3.2 Token'ı kullanma

Token'ı diğer tüm isteklerde gönderin:

HTTP
Authorization: Bearer <accessToken>

Tam olarak Bearer + bir boşluk; kontrol büyük/küçük harfe duyarlıdır.

#3.3 Geçerlilik süresi ve yenileme

  • Token 12 saat, kayan süreyle geçerlidir: her başarılı istek, son geçerlilik zamanını o andan itibaren 12 saat sonraya taşır. Düzenli trafiği olan bir mağaza aynı token'ı süresiz olarak kullanmaya devam eder.
  • Token hâlâ geçerliyken tekrar giriş yapmak aynı token'ı döndürür; böylece aynı mağazanın birden fazla worker'ı bu token'ı güvenle paylaşabilir.
  • Token artık geçerli olmadığında, her endpoint şu gövdeyle HTTP 401 döndürür (not: data / error sarmalayıcısı yoktur):

    JSON
    {"message": "User timed out.", "code": "TOKEN_EXPIRED"}
    codeAnlamı
    MISSING_TOKENAuthorization header'ı yok / hatalı biçimde ya da token bilinmiyor
    TOKEN_EXPIRED12 saatten uzun süre hiç istek yapılmamış
    TOKEN_CHANGEDBu site için daha yeni bir token üretilmiş

    Bunlardan herhangi birinde: tekrar giriş yapın ve isteği bir kez tekrarlayın.

Giriş hataları, standart hata zarfı (error envelope) içinde HTTP 403 olarak döner (bölüm 4.3); örneğin Invalid credentials., Domain not allowed for this key., IP not allowed for this key. Detected IP: ….

#4. Ortak kurallar: istekler, yanıtlar, hatalar

#4.1 Gönderilen istekler

  • JSON gövdeler, UTF-8, Content-Type: application/json. Accept: application/json gönderin.
  • Listeleme ve arama endpoint'leri POST'tur, yalnızca okuma yapsalar bile. Bunları GET ile çağırmak ya 405 döndürür ya da farklı bir HTTP route'una düşüp başarısız olur.
  • Para birimi EUR'dur. Ağırlıklar kg cinsindendir. Paket ölçüleri isteklerde cm cinsindendir (Mydema bunları metre olarak saklar ve döndürür).

#4.2 Başarılı yanıtlar

HTTP 200 ve bir data nesnesi:

JSON
{"data": {"basketId": "695bca64-c645-4398-8b67-c16adcc89de4"}}

#4.3 Hata yanıtları

Mydema birden fazla hata biçimi kullanır. Hepsini ele alın.

A. Standart zarf (çoğu endpoint):

JSON
{"error": {"message": "{\"message\":\"District is not valid.\",\"code\":400}", "code": 500, "file": null, "line": null}}

error.message çoğunlukla kendisi de bir JSON string'idir. Okunabilir message değerini ve kastedilen code değerini (400 hatalı girdi, 403 size ait değil, 404 bulunamadı, 409 çakışma) almak için onu ikinci kez decode edin. Geçerli bir JSON değilse düz metin olarak kullanın.

B. Framework hatası (bazı endpoint'ler; bölüm 7'de "B" ile işaretlenmiştir):

JSON
{"type": "https://tools.ietf.org/html/rfc2616#section-10", "title": "An error occurred", "status": 500, "detail": "Internal Server Error"}

Canlı ortamda detail geneldir, dolayısıyla nedeni göremezsiniz. Göndermeden önce girdinizi doğrulayın (bölüm 6) ve gönderdiğiniz isteği loglayın.

C. Token hataları: HTTP 401, {"message": …, "code": "TOKEN_EXPIRED"} (bölüm 3.3).

D. Bağlantı endpoint'leri (/api/plugin/site-request*), gerçek HTTP durum koduyla birlikte temiz bir zarf kullanır: {"error": {"message": "Request is not approved.", "code": 409}}.

E. Geçici veritabanı kilidi: Retry-After header'ı ile HTTP 503 ve {"error": {"message": "…temporary database lock…", "code": "DB_RETRY"}}. Bekleyip tekrar deneyin.

#4.4 HTTP durum kodları

DurumAnlamıTekrar denenmeli mi?
200Başarılı—
401Token sorunu (bölüm 3.3)Tekrar giriş yapın, ardından bir kez yeniden deneyin
403Giriş reddedildiHayır; anahtarı / alan adını / IP'yi düzeltin
404, 405Yanlış yol veya yanlış HTTP metoduHayır
500İş kuralı veya doğrulama hatası (hatalı girdi, bulunamadı, yetersiz kredi…) — asıl neden gövdededirHayır: aynı istek yine başarısız olur
503Geçici veritabanı kilidiEvet, Retry-After sonrasında
Ağ hatası / zaman aşımı—Okuma istekleri ve create: evet. complete: evet, sepet başına güvenlidir (kural 8.2)

Doğrulama ve iş kuralı hatalarının çoğu 4xx değil, HTTP 500 olarak döner. 500'ü sunucu kesintisi olarak değerlendirmeyin ve körü körüne tekrar denemeyin.

#4.5 Liste isteği biçimi

"ListRequest" olarak belirtilen endpoint'ler şu isteğe bağlı gövdeyi kabul eder:

AnahtarTürNotlar
paramsobjectEndpoint'e özgü filtreler, ör. {"state": 65}
limitintSayfa boyutu. Çoğu listede varsayılan değer yoktur: gönderilmezse her şey döner
offsetintAtlanacak satır sayısı
orderByobjectör. {"name": "ASC"}

{} geçerli bir gövdedir. Sayılar string değil, sayı olmalıdır.

Sonuçlar {"data": {"<items>": [...], "meta": {"total": 17}}} biçimindedir.

#5. Entegrasyon akışına genel bakış

 tek seferlik kurulum          ödeme adımı (alışveriş sepeti başına)      sipariş (ödenmiş sipariş başına)
 ────────────────────          ─────────────────────────────────────      ────────────────────────────────
 plugin-login                  create (sepet)  ──► search-routes          update-basket (gerçek iletişim, not)
 shipment/templates            (çıkış deposu başına bir sepet)            search-routes (güncel)
 suppliers (depolar)           rota başına Σ segment.price göster         set-routes (seçilen rota)
 konum sorguları               rota yok mu? ──► scenarios                 kredi bakiyesi kontrolü
                                                                          payment-complete (creditAmount)
                                                                          complete  ──► transportation id
                                                                          voucher-pdf, takip
  1. Kurulum. Giriş yapın. Paket şablonlarını yükleyin. Malların gönderildiği her depoyu bir depo / alım noktası (supplier) olarak kaydedin (ya da her şey için tek bir çıkış ilçesi (district) kullanın). Müşterilerinizin adreslerini Mydema ilçe id'lerine eşleyin (bölüm 6.1).
  2. Ödeme adımında fiyat teklifi. Alışveriş sepetindeki her çıkış deposu için paketler, gönderici (depo) ve alıcı ile bir sepet (basket) oluşturun, ardından rotaları (route) arayın. Her rota bir kargo seçeneğidir; fiyatı, rota ayaklarının (segment) price değerlerinin toplamıdır (bölüm 7.6). basketId ve rotanın kimliğini (segmentlerin routeId değerleri) müşterinin seçimiyle birlikte saklayın.
  3. Sipariş → gönderi. Sipariş ödendiğinde sepeti müşterinin gerçek iletişim bilgileri ve notuyla güncelleyin, rotaları yeniden arayın (fiyatlar o anda günceldir), seçilen rotayı set-routes ile gönderin, ödemeyi payment-complete ile kaydedin, ardından complete endpoint'ini bir kez çağırın. Mydema taşıma siparişi (transportation order) id'sini alırsınız.
  4. Sonrası. Voucher PDF'ini indirin ve durum için takip endpoint'ini yoklayın.

Birden fazla depodan mal içeren bir alışveriş sepeti birden fazla sepet demektir: her depo (gönderici) için bir tane; her biri ayrı ayrı fiyatlandırılır ve tamamlanır. Resmi eklentiler bunların toplamını tek bir kargo fiyatı olarak gösterir.

#6. Referans verileri: konumlar, paketler, kişiler

#6.1 Konumlar

Mydema'nın konum ağacı ülke → il (state) → belediye (city) → ilçe (district) şeklindedir. Canlı ortamda state bir Yunan iline (νομός, prefecture), city ise bir belediyeye (municipality) karşılık gelir.

Gönderi endpoint'leri göndericinin ve alıcının ilçe id'sine ihtiyaç duyar. Rotalar ilçeler arasında işler ve yalnızca hizmet verilen ilçelerin rotası vardır.

ElinizdekiKullanın
Müşterinin yazdığı bir yer adıPOST/api/location/district/avail-autocomplate → locations[].id (yalnızca hizmet verilen ilçeler)
Bir il; hizmet verilen ilçelerini istiyorsunuzparams.state ile POST/api/location/district/avail-list → locations[].districtId
Bir posta koduPOST/api/autocomplate/location/postal-code → yer adları (id değil); ardından adı yukarıdaki iki çağrıyla eşleştirin
İl adı → state idPOST/api/location/state/list

Hizmet verilen tam liste (filtresiz avail-list) birkaç saat önbelleğe alınabilecek kadar küçüktür.

#6.2 Paketler

Paket kataloğunu günde bir kez GET/api/shipment/templates üzerinden yükleyin. Bu yazının yazıldığı tarihteki değerler (Ekim 2026):

KodTürHacim aralığı (m³)Faturalanan (m³)Ağırlık (kg)Taban ölçüsü (cm)
SM3 (koli)0 – 0.200.200 – 80—
MM3 (koli)0.2001 – 0.500.500 – 80—
LM3 (koli)0.5001 – 1.001.000 – 80—
XLM3 (koli)1.0001 – 1.601.600 – 80—
PEUR1PL (palet)—1.9280 – 100080 × 120, yükseklik 200
PEUR2PL (palet)—2.4080 – 1000100 × 120, yükseklik 200
PMNI6PL (palet)—0.4880 – 30060 × 80, yükseklik 100

Mydema bu değerleri değiştirebilir; bunları koda gömmek yerine API'den okuyun.

Paket nesnesi (create, update-basket, quote-districts tarafından kullanılır):

AlanTürZorunluNotlar
weightTypestringevetM3 = koli / paket, PL = palet, KG = özel ürünler
weightnumberevetkg, tek bir koli
width, height, depthnumberevetcm
isSensitiveboolhayırKırılacak eşya; varsayılan false
shipmentType, shipmentSizestringaşağıya bakınŞablon / özel ürün kodları
productWidth, productHeight, productDepthnumberhayırÜrünün kendi ölçüsü (cm). Yalnızca voucher üzerine basılır; fiyatlandırmada kullanılmaz

Fiyat nasıl belirlenir:

  • M3 (koli): hacme (width × height × depth, m³) ve ağırlığa göre. Tek bir koli en fazla 220 kg olabilir. Her fiziksel koli için bir paket gönderin. shipmentType ve shipmentSize alanlarını göndermeyin — bilinmeyen bir kod tüm isteğin Size is not valid / Type is not valid hatasıyla başarısız olmasına yol açar.
  • PL (palet): taban ölçüsüne göre: width ve depth, yukarıdaki palet taban ölçülerinden biri olmalıdır (iki yönde de olabilir, ör. PEUR1 için 80 ve 120). height yük yüksekliğidir; fiyatı etkilemez. shipmentType / shipmentSize göndermeyin.
  • KG (özel ürünler): hem shipmentType hem shipmentSize zorunludur ve Mydema'nın o ürün için size verdiği kodlar olmalıdır.

Birden fazla aynı koli = birden fazla paket nesnesi. weight değeri 0 olan ve shipmentType içermeyen bir paket sessizce yok sayılır.

#6.3 Gönderici ve alıcı

JSON
{
  "firstName": "Μαρία",
  "lastName": "Παπαδοπούλου",
  "email": "[email protected]",
  "phone": "6900000000",
  "customerType": "I",
  "taxId": "",
  "address": {
    "address": "Οδός Παραλήπτη 5",
    "postalCode": "53100",
    "district": 158,
    "city": null,
    "officeName": "",
    "taxOffice": "",
    "location": ""
  }
}
AlanNotlar
customerTypeI bireysel, S kurumsal
taxIdKurumsal müşteriler için Yunan vergi numarası (ΑΦΜ), en fazla 20 karakter
address.districtZorunlu. İlçe id'si (bölüm 6.1). Eksik veya bilinmeyen bir ilçe isteğin başarısız olmasına yol açar
address.cityBelediye id'si ya da ilçeden alınması için null (önerilir)
email, phoneSipariş tamamlarken gerçek değerler kullanın: Mydema gönderi oluşturulduğunda alıcıya e-posta gönderir ve kurye telefon numarasını görür. Fiyat teklifi aşamasında yer tutucu değerler sorun değildir

Gönderici sizin deponuzdur. customerType olarak S ve deponun ilçesini kullanın.

#7. Endpoint referansı

7.1'dekiler dışındaki tüm endpoint'ler Authorization: Bearer <token> gerektirir. "Hatalar: A/B" ifadesi bölüm 4.3'teki hata biçimlerini belirtir.

#7.1 Bağlantı (herkese açık)

#POST/api/plugin/site-request

AlanZorunluNotlar
domainevetMağaza host adı
companyNameeveten fazla 191
contactNameeveten fazla 100
contactEmailevetOnay e-postası bu adrese gider
claimSecretHashevetGizli değerinizin SHA-256 hex karşılığı (64 karakter)
platformhayıropencart3, opencart4, woocommerce, magento2; diğer değerler boş olarak saklanır
siteName, pluginVersion, taxId, taxOffice, address, postalCode, city, contactPhonehayırFatura / iletişim bilgileri
JSON
{"data": {"requestId": "9b2f8f8e-3c1a-4f7e-9d1e-1a2b3c4d5e6f", "status": "pending"}}

Aynı alan adı ve gizli değer tekrar gönderilirse aynı başvuru döner. Hatalar (D biçimi): 400 geçersiz alan, 409 bu alan adı için açık başka bir başvuru var, 429 tek bir IP'den saatte 5'ten fazla başvuru.

#POST/api/plugin/site-request/status

Gövde: {"requestId": "…", "claimSecret": "<raw secret>"}

JSON
{"data": {"status": "approved", "siteName": "My Shop", "domain": "myshop.gr", "rejectReason": null}}

status: pending, approved, rejected, claimed. Bilinmeyen id veya yanlış gizli değer → 404.

#POST/api/plugin/site-request/claim

Gövde: {"requestId": "…", "claimSecret": "<raw secret>"}

JSON
{"data": {"apiKey": "mk_live_…", "apiEndpoint": "https://app.mydema.gr", "integrationId": 57}}

Her çağrı anahtarı yeniler (rotate). Hatalar: 404, 409 onaylanmamış, 410 site artık aktif değil.

#7.2 Kimlik doğrulama

EndpointNotlar
POST/api/auth/plugin-loginbölüm 3.1
POST/api/auth/plugin-autologinSatıcının tarayıcısında parolasız olarak bir Mydema panel sayfası (kredi yükleme ödemesi) açar (bölüm 7.9)

#7.3 Konumlar

Hepsi POST'tur. Hatalar: B.

#POST/api/location/district/avail-autocomplate

Bir adla eşleşen hizmet verilen ilçeler (Yunanca veya Latin harfleriyle, aksanlar isteğe bağlı; eşleşme yoksa bulanık eşleşmeye (fuzzy match) geçer).

Gövde: {"term": "Κομοτην", "limit": 10} (limit varsayılanı 100)

JSON
{"data": {"locations": [{
  "id": 1, "name": "Κομοτηνή", "nameLatin": "Komotini", "cityName": "Κομοτηνή", "CountryName": "Ελλάδα",
  "city": {"id": 67, "name": "Κομοτηνή", "state": {"id": 65, "name": "Ροδόπης"}, "country": {"code": "GR"}},
  "country": {"code": "GR", "name": "Ελλάδα"}
}]}}

(CountryName gerçekten büyük C ile başlar.)

#POST/api/location/district/avail-list — ListRequest

Hizmet verilen tüm ilçeler, düz liste halinde. Filtreler: params.state (il id'si), params.city (belediye id'si). limit olmadan hepsini alırsınız.

Gövde: {"params": {"state": 65}}

JSON
{"data": {
  "locations": [{"districtId": 1, "name": "Κομοτηνή", "cityId": 67, "cityName": "Κομοτηνή", "stateId": 65, "stateName": "Ροδόπης"}],
  "locationDistricts": ["…same districts in the long format of avail-autocomplate…"],
  "meta": {"total": 4, "limit": null, "offset": 0}
}}

locations kullanın; locationDistricts eski istemciler için korunmaktadır.

#POST/api/autocomplate/location/postal-code

(autocomplate yazımı path'in bir parçasıdır.) Gövde: {"term": "69131", "limit": 50} (önek eşleşmesi; limit varsayılanı 10).

JSON
{"data": {"locationPostalCodes": [
  {"id": 12752, "code": "69131", "district": "ΑΓΙΑ ΒΑΡΒΑΡΑ", "city": "ΡΟΔΟΠΗΣ"},
  {"id": 12754, "code": "69131", "district": "ΚΟΜΟΤΗΝΗ", "city": "ΡΟΔΟΠΗΣ"}
]}}

district yerleşim yerinin adı, city ise ilin adıdır — id değil, metin. Bir posta kodu birden fazla yerleşim yerini kapsayabilir.

#POST/api/location/state/list — ListRequest

Gövde: {"orderBy": {"name": "ASC"}}

JSON
{"data": {"locationStates": [{"id": 65, "name": "Ροδόπης", "nameLatin": null, "originalName": "Ροδόπης", "country": {"code": "GR"}}], "meta": {"total": 59}}}

#POST/api/location/city/avail-list — ListRequest

Bir ile ait tüm belediyeler. params.state zorunludur (olmadan liste boş döner).

Gövde: {"params": {"state": 65}} → {"data": {"locationCities": [{"id": 542, "name": "Αίγειρος", "state": {"id": 65, "name": "Ροδόπης"}}], "meta": {"total": 14}}}

#7.4 Paket şablonları

#GET/api/shipment/templates

JSON
{"data": {"templates": [
  {"code": "S", "name": "Small", "shipmentType": "M3", "lengthCm": 0, "widthCm": 0, "heightCm": 0,
   "minWeightKg": 0, "maxWeightKg": 80, "minVolumeM3": 0, "maxVolumeM3": 0.2, "volumeM3": 0.2,
   "billingVolumeM3": 0.2, "isPallet": false, "displayOrder": 1},
  {"code": "PEUR1", "name": "Standard EUR 1 80x120x200", "shipmentType": "PL", "lengthCm": 80, "widthCm": 120, "heightCm": 200,
   "minWeightKg": 80, "maxWeightKg": 1000, "minVolumeM3": null, "maxVolumeM3": null, "volumeM3": 1.92,
   "billingVolumeM3": 1.92, "isPallet": true, "displayOrder": 5}
]}}

Nasıl kullanılacakları için bölüm 6.2'ye bakın. Hatalar: A.

#7.5 Depolar / alım noktaları (suppliers)

Bir depo / alım noktası (supplier), mallarınızın teslim alındığı bir yerdir. Onun ilçesi, o mallara ait sepetlerin gönderici ilçesidir. Her site yalnızca kendi alım noktalarını görür ve düzenler. Hatalar: A (yetki sorunları: B).

Alım noktası (supplier) nesnesi

JSON
{
  "id": 3, "integrationId": 8, "companyId": 4, "companyUserId": 260,
  "name": "Αποθήκη Κομοτηνής", "pickupAddress": "Οδός Δοκιμής 1",
  "districtId": 1, "districtName": "Κομοτηνή", "cityId": 67, "cityName": "Κομοτηνή", "postalCode": "69100",
  "latitude": null, "longitude": null, "googlePlaceId": null,
  "contactName": "Γιώργος", "contactPhone": "2531000000", "contactEmail": null,
  "pickupHoursStart": "09:00:00", "pickupHoursEnd": "17:00:00",
  "notes": null, "externalCode": "WH-1", "isActive": true,
  "createdAt": "2026-10-30T19:58:41+00:00", "updatedAt": "2026-10-30T19:58:41+00:00"
}

Yazılabilir alanlar: name (zorunlu, en fazla 200), pickupAddress (zorunlu, en fazla 500), districtId (belediye bundan türetilir), postalCode, latitude, longitude, googlePlaceId, contactName, contactPhone, contactEmail, pickupHoursStart, pickupHoursEnd ("09:00"), notes, externalCode (sizin kendi id'niz, site başına benzersiz).

EndpointGövdeYanıt
POST/api/supplieralım noktası alanları{"data": {"supplier": {…}}}
GET/api/supplier/{id}—{"data": {"supplier": {…}}}
PUT/api/supplier/{id}yalnızca değişecek alanlar{"data": {"supplier": {…}}}
DELETE/api/supplier/{id}—{"data": {"deleted": true}}
POST/api/supplier/list{}{"data": {"suppliers": […], "total": 1}}
GET/api/integration/{integrationId}/supplier—Liste ile aynı
POST/api/supplier/sync{"integrationId": 8, "rows": [{…, "externalCode": "WH-1", "cityId": 67}]}{"data": {"created": 1, "updated": 0, "errors": []}}

Notlar:

  • PUT isteğinde integrationId göndermeyin (ya da "koru" anlamına gelen 0 gönderin). "districtId": null göndermek ilçeyi temizler.
  • sync, externalCode değerine göre upsert yapar (çağrı başına en fazla 200 satır) ve her satırda cityId ister. Satır hataları, satırın index değeriyle birlikte errors içinde listelenir; geri kalanlar kaydedilir.
  • Silinmiş bir alım noktasının externalCode değeri yeniden kullanılamaz.
  • Sık görülen hatalar: District is not valid., Please select a district., Supplier not found., Supplier with this externalCode already exists for this integration. (409).

#7.6 Gönderiler: fiyat teklifi

#POST/api/transportation/create — sepet oluşturma

JSON
{
  "packages": [{"weightType": "M3", "weight": 25, "width": 58.48, "height": 58.48, "depth": 58.48, "isSensitive": false}],
  "sender":   {"firstName": "Αποθήκη", "lastName": "Κομοτηνής", "email": "[email protected]", "phone": "2531000000", "customerType": "S",
               "address": {"address": "Οδός Δοκιμής 1", "postalCode": "69100", "district": 1, "city": null}},
  "receiver": {"firstName": "Μαρία", "lastName": "Παπαδοπούλου", "email": "[email protected]", "phone": "6900000000", "customerType": "I",
               "address": {"address": "Οδός Παραλήπτη 5", "postalCode": "", "district": 158, "city": null}}
}
JSON
{"data": {"basketId": "695bca64-c645-4398-8b67-c16adcc89de4"}}

Sepet, geçici bir fiyat teklifi / sipariş taslağıdır. Henüz hiçbir şey rezerve edilmez. Hatalar: B. Sık görülen nedenler: bilinmeyen bir district, Size is not valid / Type is not valid (bkz. 6.2), eksik sender veya receiver. Sipariş notunu daha sonra update-basket ile gönderin (buradaki comments alanı 10 karakterle sınırlıdır).

#POST/api/transportation/search-routes

Gövde: {"basketId": "…"}

JSON
{"data": {
  "basketId": "695bca64-c645-4398-8b67-c16adcc89de4",
  "serviceFee": 10,
  "serviceFees": [{"total": 10.07, "serviceFee": 0, "tax": 0.22}],
  "routes": [[
    {"startPoint": 3, "endPoint": 698, "company": 15, "onlyB2B": false, "routeId": 78,
     "price": 5.26, "tax": 1.01, "currency": "EUR", "hasAddressDelivery": true,
     "deliveryDoor": false, "carrier": false, "deliveryDoorPrice": 0, "deliveryDoorTax": 0, "carrierPrice": 0, "carrierTax": 0,
     "data": {"route": "Ροδόπης, Κομοτηνή-Θεσσαλονίκη, Κέντρο Διαλογής", "company": "ΚΟΚΟΒΙΔΗΣ ΦΡΙΓΚΟΤΡΑΝΣ",
              "endPointName": "Θεσσαλονίκη, Κέντρο Διαλογής", "endPointAddress": "Π.Σ.Ο. Θεσ/νίκης-Ωραιοκάστρου\nΤηλέφωνα : 2310 785 050",
              "companyPrices": [7095], "price": 4.78, "currency": "EUR"},
     "packagePrices": [{"weightType": "M3", "weight": 25, "width": 0.5848, "height": 0.5848, "depth": 0.5848,
                        "shipmentSize": null, "shipmentType": null, "isSensitive": false, "productDimsCm": null,
                        "price": 4.78, "tax": 0.92, "basePrice": 3.86}],
     "basePrice": 4.25, "serviceFee": 0},
    {"startPoint": 698, "endPoint": 179, "company": 18, "onlyB2B": false, "routeId": 385,
     "price": 5.84, "tax": 1.13, "currency": "EUR", "hasAddressDelivery": false,
     "deliveryDoor": false, "carrier": false, "deliveryDoorPrice": 0, "deliveryDoorTax": 0, "carrierPrice": 0, "carrierTax": 0,
     "data": {"route": "Θεσσαλονίκη, Κέντρο Διαλογής-Φλώρινας, Φλώρινα", "company": "ΣΑΛΒΑΡΙΝΑΣ ΑΛ. ΙΩΑΝΝΗΣ",
              "endPointName": "Φλώρινας, Φλώρινα", "endPointAddress": "Λεωφόρος Αβέρωφ 47, Φλώρινα\nΤηλέφωνο: 2385023995",
              "companyPrices": [953], "price": 5.31, "currency": "EUR"},
     "packagePrices": ["…"], "basePrice": 4.71, "serviceFee": 0}
  ]]
}}

Nasıl okunur:

  • routes bir rotalar listesidir; her rota, taşıyıcı başına bir tane olmak üzere rota ayaklarından (segment, leg) oluşan bir listedir. Örnekte iki ayaklı tek bir rota vardır: Komotini → Thessaloniki aktarma merkezi → Florina.
  • Müşteriye gösterilen fiyat = rotanın segmentlerinin Σ price değeri; KDV ve Mydema hizmet bedeli dahildir (burada 5.26 + 5.84 = 11.10 €). İçindeki KDV Σ tax değeridir (2.14 €).
  • serviceFees veya data.price değerlerini müşteri fiyatı olarak kullanmayın; bunlar dahili rakamlardır.
  • Teslimat şekli son segmentten anlaşılır: hasAddressDelivery: true = alıcının adresine teslim edilir; false = alıcı gönderiyi taşıyıcının data.endPointName / data.endPointAddress adresindeki şubesinden teslim alır.
  • Gösterim için taşıyıcı adları: her segmentin data.company değeri.
  • Rotalar sıralı değildir. Herhangi bir doğrudan (tek ayaklı) rota varsa, yalnızca doğrudan rotalar döndürülür.
  • HTTP 200 ile routes: [], bu kombinasyona hiçbir taşıyıcının hizmet vermediği anlamına gelir (bkz. scenarios).

Search-routes sepeti değiştirmez; gerektiği kadar sık çağırabilirsiniz.

#POST/api/transportation/scenarios

Boş bir sonucu açıklar.

Gövde: {"receiverDistrictId": 158, "isB2B": false, "hasAfm": false}

JSON
{"data": {"inService": true, "scenarios": [{"type": "IN_SERVICE"}, {"type": "B2C_SMS_NOTIFY"}]}}

İlçeye hizmet verilmiyorsa: {"inService": false, "scenarios": [{"type": "HUB_PICKUP", "hub": {"transferLocationId": 48, "name": "…", "address": "…", "cityId": 5, "districtId": 101}}]} (hub null olabilir). Hatalar: A.

#POST/api/transportation/quote-districts — birden fazla varış noktasını tek seferde fiyatlandırma

Müşterinin bölgesini bildiğiniz ama tam ilçeyi bilmediğiniz durumlar için: aynı alışveriş sepetini tek çağrıda, sepet oluşturmadan, en fazla 15 ilçeye göre fiyatlandırır.

Gövde: create gövdesi artı "districtIds": [158, 1]. Alıcının address.district değeri sırayla her bir id ile değiştirilir.

JSON
{"data": {"districts": [
  {"districtId": 158, "found": true, "name": "Φλώρινα", "nameLatin": "Florina", "cityId": 192, "cityName": "Φλώρινα",
   "serviceFee": 10, "serviceFees": ["…"], "routes": [["…segments as in search-routes…"]]},
  {"districtId": 999999, "found": false, "routes": [], "serviceFees": []}
]}}

Müşteri birini seçtiğinde, o ilçe için create ile bir sepet oluşturun ve normal şekilde devam edin. Hatalar: B.

#7.7 Gönderiler: sipariş → voucher

#POST/api/transportation/update-basket

Sepetin paketlerini, göndericisini ve alıcısını değiştirir ve sipariş notunu ayarlar.

JSON
{"basketId": "…", "packages": ["…same as create…"], "sender": {"…": "…"}, "receiver": {"…": "…"},
 "receiverComment": "Παρακαλώ τηλεφωνήστε πριν την παράδοση"}

Yanıt: {"data": {"basketId": "…"}}. Hatalar: B.

  • packages alanını her zaman yeniden gönderin: gönderilmezse sepet boş kalır.
  • Sipariş anında alıcıya müşterinin gerçek adını, telefonunu ve e-postasını yazmak için kullanın.
  • receiverComment, gönderide alıcının talimatları olarak görünür.

#POST/api/transportation/set-routes

Seçilen rotayı kaydeder. routes, tek bir rotanın segmentlerini içerir (rotalar listesini değil); bunlar aynı sepetin search-routes yanıtından kopyalanır:

JSON
{"basketId": "…", "routes": [
  {"company": 15, "startPoint": 3, "endPoint": 698, "routeId": 78, "price": 5.26, "tax": 1.01, "serviceFee": 0,
   "packagePrices": ["…as returned…"], "deliveryDoor": false, "deliveryDoorPrice": 0, "carrier": false, "carrierPrice": 0,
   "hasAddressDelivery": true},
  {"company": 18, "startPoint": 698, "endPoint": 179, "routeId": 385, "price": 5.84, "tax": 1.13, "serviceFee": 0,
   "packagePrices": ["…"], "deliveryDoor": false, "deliveryDoorPrice": 0, "carrier": false, "carrierPrice": 0,
   "hasAddressDelivery": false}
]}

hasAddressDelivery dışında yukarıdaki her alan zorunludur; packagePrices boş olmamalıdır. Değerleri tam olarak search-routes endpoint'inin döndürdüğü şekilde gönderin. Yanıt: {"data": {"basketId": "…"}}. Hatalar: B.

#POST/api/transportation/payment-complete

Gönderinin nasıl ödendiğini kaydeder. Ön ödemeli cüzdan kullanan siteler için (tüm eklenti siteleri), creditAmount değerini rota toplamına ayarlayın; böylece bu tutar complete sırasında cüzdandan düşülür.

JSON
{"basketId": "…", "totalPrice": 11.10, "creditAmount": 11.10, "status": "success",
 "conversationId": "order-1001", "paymentType": "CREDIT", "currency": "EUR", "bank": "MyShop", "card": "MyShop"}
AlanNotlar
totalPriceSeçilen rotanın Σ segment price değeri
creditAmountCüzdandan düşülecek tutar. totalPrice ile aynı değeri kullanın
statussuccess olmalıdır, aksi halde complete sepeti reddeder
conversationIdSizin sipariş referansınız, en fazla 50
paymentType, currency, bank, cardZorunlu, boş olmayan string'ler; CREDIT, EUR ve mağaza adınız uygundur

Yanıt: {"data": {"basketId": "…"}}. Henüz hiçbir ücret alınmaz. Hatalar: B.

#POST/api/transportation/complete

Sepetten Mydema taşıma siparişini (voucher'ı) oluşturur. Gövde: {"basketId": "…"}.

JSON
{"data": {"transportations": {"id": 5123, "orderNumber": "MY-1042", "transferStatus": {"code": "CR", "name": "Created"},
  "totalPrice": 11.1, "shipments": ["…"], "details": ["…"]}}}

Tam nesne GET/api/transportation/{id} (7.8) ile aynı yapıdadır. data.transportations.id değerini saklayın. Hatalar: B.

Ne olur:

  • creditAmount sitenin cüzdanından düşülür. Kredi yetersizse istek başarısız olur (Insufficient credit.) ve hiçbir şey oluşturulmaz. Önce bakiyeyi kontrol edin (7.9).
  • Rotadaki taşıyıcılar bilgilendirilir, alıcı bir e-posta alır (alıcı e-postası gerçekse) ve gönderici adresine bir onay e-postası gider.
  • Aynı sepet için tekrarlamak güvenlidir. Bir sepet gönderiye dönüştükten sonra complete aynı gönderiyi tekrar döndürür ve hiçbir ücret almaz; o sepet için set-routes ve payment-complete yok sayılır (kural 8.2).

Ön koşullar, sırasıyla: sepet mevcut; ödeme status: success ile kaydedilmiş (aksi halde Payment is not valid); yeterli kredi.

#7.8 Gönderiler: voucher ve takip

#GET/api/transportation/{id}/voucher-pdf?status=confirmed

Voucher'ı application/pdf olarak döndürür (Content-Disposition: attachment; filename="MY-1042.pdf"). status, confirmed veya temporary olabilir; yalnızca X-Mydema-Voucher-Status yanıt header'ını belirler. Hatalar: A.

#GET/api/transportation/{id}

JSON
{"data": {"transportationOrder": {
  "id": 5123, "orderNumber": "MY-1042", "totalPrice": 11.1, "taxTotal": "2.14", "serviceFee": 0,
  "transferStatus": {"code": "CR", "name": "Created"},
  "senderComment": "", "deliverComment": " Παρακαλώ τηλεφωνήστε πριν την παράδοση",
  "sendAddress": {"…": "…"}, "deliverAddress": {"…": "…"},
  "shipments": [{
    "id": 9001, "orderNumber": "MY-1042",
    "route": {"id": 78, "route": "Κομοτηνή - Θεσσαλονίκη", "company": {"id": 15, "name": "…", "phone": "…"}},
    "shipmentStatus": {"code": "TR", "name": "In transit"},
    "actions": [{"actionDate": "2026-10-31", "shipmentStatus": {"code": "TR", "name": "In transit"}}],
    "packages": ["…"]
  }],
  "details": ["…one per package…"],
  "createdAt": "2026-10-30 10:00:00", "updatedAt": "2026-10-31 09:12:00"
}}}

shipments her ayak için bir kayıt içerir; actions o ayağın geçmişidir. Hatalar: A (Company not found. sipariş id'sinin mevcut olmadığı anlamına gelir).

Sipariş transferStatus.codeAnlamı
CROluşturuldu
PPÖdeme bekliyor
PAÖdendi
TRTaşımada
CMTamamlandı
CLİptal edildi
Gönderi shipmentStatus.codeAnlamı
DOSipariş oluşturuldu
PEBeklemede
RETaşıyıcı tarafından teslim alındı
TRYolda
DTDağıtımda
DETeslim edildi

Webhook yoktur; bu endpoint'i yoklayın (örneğin açık gönderiler için saatte bir).

#7.9 Kredi cüzdanı

Gönderiler sitenin ön ödemeli cüzdanından ödenir. Üç endpoint'in tümü giriş yapmış sitenin cüzdanı üzerinde çalışır. Hatalar: B.

#GET/api/customer/credit/balance

JSON
{"data": {"balance": 42.5, "currency": "EUR"}}

#GET/api/customer/credit/transactions?page=1&limit=50

limit 1–200, varsayılan 50.

JSON
{"data": {"transactions": [
  {"id": 9, "type": "shipment", "amount": -11.1, "balanceAfter": 31.4, "currency": "EUR",
   "refType": "basket", "refId": "695bca64-…", "description": "Shipment payment", "createdAt": "2026-10-30 14:02:11"}
], "total": 1, "page": 1, "limit": 50}}

type: topup, shipment, refund. createdAt, saat dilimi eki olmadan sunucunun yerel saatidir.

#POST/api/customer/credit/topup + ödeme sayfası

Kredi yüklemeleri Mydema'nın kendi ödeme sayfasında kartla ödenir.

  1. {"amount": 50} ile POST/api/customer/credit/topup (net tutar, 1–1000 €). Yanıt: {"data": {"basketId": "…", "customerId": 273, "amount": 50, "tax": 12, "chargeTotal": 62, "currency": "EUR"}} (%24 KDV eklenir).
  2. {"redirect": "/customer/<customerId>/credit/payment/<basketId>/init"} ile POST/api/auth/plugin-autologin → {"data": {"token": "…"}} (2 dakika geçerli).
  3. <base URL>/plugin/auto-login?t=<token> adresini satıcının tarayıcısında açın. Mydema satıcının oturumunu açar ve kartla ödeme sayfasını gösterir. Ödeme başarılı olduğunda bakiye artar.

#7.10 Opsiyonel

#POST/api/afm — Yunan vergi numarası sorgulama

Gövde: {"afmNumber": "099999999"} (9–12 karakter). Hatalar: B.

JSON
{"data": {"taxData": {"taxId": "099999999", "taxOffice": "ΦΑΕ ΑΘΗΝΩΝ", "name": "ACME ΑΕ", "address": "ΟΔΟΣ 1", "zip": "10431", "location": "ΑΘΗΝΑ"}}}

Bilinmeyen bir numara aynı yapıyı boş string'lerle döndürür.

#Site ayarları

Siteniz için Mydema'da saklanan serbest anahtar/değer ayarları.

EndpointNotlar
GET/api/integration/{integrationId}/settings{"data": {"settings": {"currency": {"key": "currency", "value": "EUR", "valueType": "string"}}}} (boşken [])
GET/api/integration/{integrationId}/settings/{key}{"data": {"setting": {…}}}; bilinmeyen anahtar → Setting not found.
PUT/api/integration/{integrationId}/settings{"settings": [{"key": "currency", "value": "EUR"}]} → {"data": {"written": 1, "errors": []}}
DELETE/api/integration/{integrationId}/settings/{key}{"data": {"deleted": true}}

İzin verilen anahtarlar: free_shipping_threshold, hide_shipping_cost, currency, language, default_pickup_supplier_id, default_pickup_branch_id, buffer_ratio, risk_premium_ratio, auto_voucher_email_to, voucher_email_subject, pickup_warning_html, emergency_mode_enabled, fallback_template_under_35kg, fallback_template_over_35kg. Diğer anahtarlar errors içinde bildirilir.

#Voucher ödemeleri (haftalık faturalar)

Mydema'nın sitenin voucher'ları için haftalık partiler halinde düzenlediği, banka havalesiyle ödenen faturalar. Yalnızca Mydema sitenizi bu şekilde faturalandırıyorsa geçerlidir. İki GET dışında hepsi POST'tur.

EndpointGövdeYanıt
POST/api/voucher-payment/summary{}{"data": {"count": 0, "totalAmount": 0, "oldestDueDate": null}}
POST/api/voucher-payment/list{"status": "unpaid", "page": 1, "perPage": 50} (ayrıca weekBatchId, fromDate, toDate){"data": {"payments": [], "page": 1, "perPage": 50}}
GET/api/voucher-payment/{id}—{"data": {"payment": {…}}}
GET/api/voucher-payment/{id}/invoice-pdf—application/pdf

#8. Entegrasyon kuralları ve tuzaklar

8.1 İş kuralı hataları HTTP 500'dür. Durum kodu yerine gövdeyi okuyun (bölüm 4.3) ve 500 yanıtlarını otomatik olarak tekrar denemeyin. Yalnızca 503 ve ağ hataları tekrar denemeye değer.

8.2 complete her sepet için idempotent'tir. Bir sepet en fazla bir gönderiye dönüşür: aynı sepet için complete tekrar çağrıldığında mevcut gönderi (aynı id) döner ve cüzdandan tekrar ücret alınmaz; bu yüzden zaman aşımından sonra endpoint'i yeniden çağırmanız yeterlidir. İlk çağrı hâlâ sürerken ikinci çağrı This basket is already being completed. Please try again in a moment. hatasıyla başarısız olur — birkaç saniye sonra tekrar deneyin. complete birkaç saniye sürebilir (yanıt vermeden önce e-postaları gönderir); en az 30 sn'lik bir zaman aşımı kullanın. Yine de dönen id'yi siparişinizle ilişkilendirerek saklayın: birden fazla depodan mal içeren bir siparişin her depo için bir sepeti ve bir gönderisi olur.

8.3 Müşterinin gördüğü rotayı sipariş anında yeniden fiyatlandırın. Sipariş anında search-routes çağırın, aynı routeId değerlerine sahip rotayı seçin ve tam olarak bu segmentleri set-routes endpoint'ine gönderin. Fiyatlar teklif ile sipariş arasında değişebilir.

8.4 complete öncesinde cüzdanı kontrol edin. GET/api/customer/credit/balance ≥ rota toplamı olmalıdır; aksi halde satıcıdan kredi yüklemesini isteyin (7.9) ve daha sonra tekrar deneyin.

8.5 Depo başına bir sepet. Farklı ilçelerden çıkan mallar farklı gönderilerdir. Her gönderici için ayrı bir sepet oluşturun, fiyatlandırın ve tamamlayın.

8.6 Adlar değil, ilçeler. Her fiyat teklifi iki taraf için de bir ilçe id'si gerektirir. create çağırmadan önce müşterinin adresini hizmet verilen bir ilçeye çözümleyin (6.1); adresi yalnızca bir ile kadar daraltabiliyorsanız quote-districts kullanın ve seçimi müşteriye bırakın.

8.7 Koli paketleri: shipmentType / shipmentSize göndermeyin. Bilinmeyen bir kod tüm isteği başarısız kılar (6.2).

8.8 update-basket isteğinde paketleri yeniden gönderin. Gönderilmezlerse sepet boşalır.

8.9 Sipariş anında alıcının gerçek iletişim bilgileri. Mydema alıcıya e-posta gönderir ve kurye alıcıyı arar; teklif aşamasındaki yer tutucular complete öncesinde update-basket ile değiştirilmelidir.

8.10 Token'ı saklayın. Bir kez giriş yapın, token'ı yeniden kullanın ve yalnızca 401 aldıktan sonra tekrar giriş yapın (3.3). Her istekte giriş yapmak gereksizdir.