Herkese açık API dokümantasyonu
Giriş yap
API dokümantasyonu

TourAPI – API El Kitabı v1

Alıcı API'si v1: akış, kurallar, hata kataloğu ve örnekler – her örnek her derlemede test olarak çalışır.

İçindekiler

Çeviri, durum 2026-10-09. Geçerli olan Almanca sürümdür.

Bu dokümantasyon giriş yapmadan okunabilir ve erişim bilgisi içermez. Araçlar ve yapay zeka ajanları için: llms.txt · llms-full.txt · handbuch.tr.md · openapi.yaml

El kitabında ara

Bir seyahat portalını veya bir tur operatörü sistemini TourAPI'ye bağlayan geliştiriciler için. Durum: 30.09.2026, API sürümü v1; değişiklikler değişiklik günlüğünde yer alır. Makine tarafından okunabilir: openapi.yaml (OpenAPI 3.1). Yapay zeka ajanları (Claude Code, Codex, Antigravity) için: akış, biçimler ve yeniden deneme kurallarının özeti portalda /doku/agenten.md altında; giriş yaptıktan sonra kendi erişiminizle doldurulmuş olarak (“Yapay zeka ajanları” sayfası, AGENTS.md olarak indirme).

Bu el kitabındaki her örnek bir testtir. İstekler beispiele/ altında (JetBrains ve VS Code REST istemcilerinin formatında) yer alır ve her derlemede test verileriyle yeni başlatılmış bir TourAPI örneğine karşı çalıştırılır; gösterilen yanıtlar bu sırada gerçek yanıtlarla karşılaştırılır.

KısaltmaAnlamıEndpoint
BAMüsaitlik ve fiyat sorgulama, arama/v1/search, /v1/price, /v1/prices
BAsabit tarih olmadan açık arama (anahtar başına yetki)/v1/search/open (Bölüm 4a)
BAbir otelin tarih matrisi (anahtar başına yetki)/v1/search/open/dates (Bölüm 4b)
–kendi anahtarının geçerli sınırları/v1/limits (Bölüm 4c)
Brezervasyon yapma/v1/book
–Rezervasyon bilgisini okuma/v1/booking
Siptal etme/v1/cancel
–Düğümün sağlık durumu/v1/health (Bölüm 1.8)
–EDF teslimatı (önbellek dışa aktarımı)/v1/export/edf/full, /changes, /ack (Bölüm 10)
–Sandbox: test anahtarları, test rezervasyonları, senaryolartest anahtarıyla tüm endpoint'ler (Bölüm 11)
–Otel içerikleri (ana veriler, konum, metinler, görseller, olanaklar)/v1/content/hotels, /changes, /catalog (Bölüm 12)

Konsol/ajanlar: Alıcı API'sinin parçası değildir (tur operatörü konsolunun POST /changes endpoint'i, oturum üzerinden giriş, API anahtarı yok; açıklama ve sınırlar işletmeciden talep üzerine).

1. Başlangıç

1.1 Erişim

  • Her istek API anahtarını X-Api-Key header'ında taşır. Oturum yoktur, token yoktur.
  • Anahtarı tur operatörü verir (erişim paketi). Bir anahtar tam olarak bir tur operatörüne (kiracı) ve isteğe bağlı olarak bir müşteri grubuna aittir. Müşteri grubu özel fiyatları ve, kontenjan grubu olarak, rezervasyonun yapıldığı kontenjanı belirler. Grubu olmayan bir anahtar temel sözleşme üzerinde çalışır. Bir müşteri grubunun hangi türde olduğunu tur operatörü belirler:
    • Kontenjan grubu (kontenjanlı satış ortağı): yalnızca kendisine tahsis yapılmış otelleri görür ve bu tahsise karşı rezervasyon yapar. Diğer oteller bu anahtar için yoktur (404 ERR_HOTEL_NOT_FOUND, aramada ve /v1/destinations içinde yer almaz). O anda hiç tahsisi yoksa hiçbir otel görmez.
    • Fiyat grubu: tur operatörünün tüm otellerini görür ve genel stoktan grubu olmayan bir anahtar gibi rezervasyon yapar – yalnızca kendi fiyatıyla.
  • Bir müşteri grubunun özel fiyatı (tahsisli veya tahsissiz) bir otel için hesaplanamıyorsa (tur operatöründe hatalı fiyat kampanyası), otel bu grup için yoktur (404 ERR_HOTEL_NOT_FOUND, aramada diagnostics.reasons içinde neden ERR_HOTEL_NOT_FOUND, /v1/book de 404 ile reddeder) – asla sessizce temel fiyat kullanılmaz.
  • Kiracı, müşteri grubu ve fiyatlar yalnızca anahtardan gelir, asla bir parametreden gelmez. Diğer tur operatörlerinin otelleri bu anahtar için yoktur (404).
  • Eksik, bilinmeyen veya iptal edilmiş anahtar: 401 ERR_UNAUTHORIZED. Askıya alınmış bir tur operatörünün geçerli anahtarı: 403 ERR_TENANT_SUSPENDED. Devre dışı bırakılmış bir müşteri grubunun anahtarı: 403 ERR_KEY_GROUP_INACTIVE (asla sessizce temel sözleşme).
  • Anahtarlar tarayıcı koduna veya loglara konmaz. Anahtarını kaybeden, anahtarı tur operatörüne iptal ettirir – ya da (tur operatörü etkinleştirdiyse) iş ortağı portalında kendisi iptal eder.
  • İş ortağı portalındaki anahtarlar: Orada erişiminizin anahtarlarını görür, tur operatörünün serbest bıraktığı canlı anahtarları teslim alır, canlı anahtarları yeniler ve kendi anahtarlarınızı iptal edersiniz. Portal ham değeri yalnızca bir kez gösterir. API iptal edilen bir anahtarı birkaç saniye içinde reddeder (401).
  • Yenileme: Yeni canlı anahtar aynı haklara sahiptir (müşteri grubu, dışa aktarma, içerik yetkisi, arama profili). Önceki anahtar tur operatörünün belirlediği süre boyunca geçerli kalır (varsayılan 24 saat, 1 saat ile 7 gün arası) ve ardından saniyesinde reddedilir (401 ERR_UNAUTHORIZED). Bu süre içinde tüm sistemleri yeni anahtara geçirin. Yeni anahtarı ancak önceki anahtarın süresi dolduktan sonra yeniden yenileyebilirsiniz – böylece aynı anda en fazla iki anahtar geçerli olur.
  • Geliştirme için test anahtarları (tk_test_…) vardır: canlı anahtarla aynı veriler, rezervasyonlar yalnızca sandbox'ta (Bölüm 11). Her yanıt modu X-TourAPI-Mode header'ında belirtir.

1.2 Temel URL, iletim, sürüm

  • Temel URL erişim paketinde yer alır; örneklerde {{baseUrl}}.
  • Tüm yollar /v1 ile başlar. v1 içinde nelerin değişebileceğini uyumluluk taahhüdü düzenler (Bölüm 1.9). Bilinmeyen yanıt alanlarını lütfen yok sayın.
  • İstekler: UTF-8 JSON, Content-Type: application/json, en fazla 1 MiB body. GET olanlar /v1/booking, /v1/destinations, /v1/health ve EDF teslimatıdır (/v1/export/edf/full, /changes), diğerlerinin tümü POST'tur; yanlış metot: 405 ERR_METHOD_NOT_ALLOWED.
  • Yanıtlardaki zaman damgaları (bookedAt, updatedAt) UTC olarak RFC 3339 formatındadır, ör. 2026-09-26T08:15:03Z.
  • Sunucu bir bağlantıyı 15 sn okuma veya 15 sn yazma sonrasında keser. İstisna: EDF teslimatının paketleri (/v1/export/edf/full, /changes) 10 dakikaya kadar yazabilir (Bölüm 10).
  • Bilinmeyen bir yol (ör. /v1/… içinde yazım hatası) JSON gövdesi olmadan 404 döner (text/plain, errorCode yok); diğer tüm hatalar 1.6'daki biçimdedir.

1.3 Tarih, konaklama, referans tarihi

  • Tarih alanları saat ve saat dilimi olmadan JJJJ-MM-TT biçiminde takvim günleridir.
  • Bir konaklama yarı açıktır: checkIn ilk gecedir, checkOut ayrılış günüdür. checkIn=2026-10-26, checkOut=2026-10-28 iki gecedir.
  • Referans tarihi, Europe/Berlin saat dilimindeki sunucu tarihidir. Erken rezervasyon ve dönem kuralları her zaman bununla hesaplanır. İstek alanı now artık yalnızca tolere edilir: boş bırakın. Farklı bir tarih gönderilirse, sorgu yine de sunucu tarihiyle hesaplanır ve bunu warnings içinde belirtir; /v1/book farklı bir priceCheck.now değerini reddeder (ERR_NOW_MISMATCH).
  • İstek başına sınırlar (her hesaplamadan önce devreye girer):
SınırDeğerKod (422)
Konaklama başına gece1–30ERR_EMPTY_STAY, ERR_STAY_TOO_LONG
En erken girişbugün (referans tarihi)ERR_STAY_IN_PAST
En geç girişreferans tarihi + 732 günERR_STAY_TOO_FAR
İstek başına yolcu1–20ERR_NO_TRAVELLERS, ERR_TOO_MANY_TRAVELLERS
Yaş0–120ERR_INVALID_AGE

1.4 Para

  • Tüm tutarlar tam sent cinsindendir (…Cents, tam sayı). 18000 = 180,00.
  • Yuvarlama: ticari yuvarlama ile 2 basamağa, yolcu başına bir kez (her fiyat yanıtında rounding: {"mode": "Commercial", "decimalPlaces": 2, "scope": "Person"} – otelin EDF'sinin beyan ettiği kuralın aynısı). Bir yolcunun kalemleri tam olarak (yuvarlanmadan) toplanır, yalnızca toplam yuvarlanır: bu perTravellerCents[i] değeridir, totalCents bunların toplamıdır. „89,90 üzerinden −%15“ gibi bir kalem −13,485'tir ve toplama kadar öyle kalır. Kalem başına tam tutarı breakdown[].amountExact gösterir (Bölüm 3.1).
  • Para birimi otelin sözleşme para birimidir (currency, ISO 4217). TourAPI döviz çevirmesi yapmaz. İstekteki currency bir tercihtir: farklıysa yanıt sözleşme para biriminde, warnings içinde bir uyarıyla gelir. Sözleşmede para birimi tanımlı değilse currency boş kalır, yine bir uyarıyla – „EUR“ tahmin edilmez.
  • priceCheck ile /v1/book çağrısında ise para birimi çelişkisi bir hatadır (ERR_CURRENCY_NOT_AVAILABLE): ortak birimi olmayan tutarlar karşılaştırılmaz.

1.5 Konaklama düzeni

occupancy.travellers, bir odanın yolcularının listesidir; her yolcu için giriş günündeki age. name ve type gönderilebilir, ancak fiyata etkisi yoktur. Çocuk fiyatları, tam ücret ödeyenler ve minimum doluluk sözleşmeden çıkar:

  • Birinin çocuk mu yetişkin mi olduğuna sabit bir sınır değil, odanın çocuk yaş aralığı karar verir (ör. 2–11 yaş: 14 yaşındaki biri yetişkin fiyatı öder).
  • Bir oda belirli sayıda tam ücret ödeyen isteyebilir (ör. çift kişilik odada 2). Daha az yetişkin seyahat ederse, boş kalan tam ücret yerini bir çocuk doldurur ve tam temel fiyatı öder (pansiyon tipi çocuk tarifesiyle); çocuk indirimleri ancak bunun ötesindeki çocuklar için geçerlidir.
  • „1. çocuk / 2. çocuk“ gibi kademeler, çocukları otelin belirlediği sırayla sayar (önce en büyük veya önce en küçük). Aynı sıra, hangi çocuğun boş bir tam ücret yerini dolduracağını da belirler.
  • Çocuk yaş aralığından küçük olan bebektir: kişi başı fiyatta temel fiyat olmadan, asla bir tam ücret yerinde değil; pansiyon tipi veya ayrı bir bebek fiyatı yalnızca sözleşme bunları belirtiyorsa. „3 kişiden itibaren“ tekliflerinde bir bebek, yalnızca oda onu doluluğa sayıyorsa sayılır.
  • Hiçbir yolcu 0'dan az ödemez. Yüzde indirimleri, yolcunun çocuk veya kişi indiriminden sonra borçlu olduğu tutar üzerinden hesaplanır (ücretsiz bir çocuk ücretsiz kalır, −%50 çocuk fiyatında −%20'lik bir erken rezervasyon indirimi yarısına uygulanır).

1.6 Yanıtlar, hatalar, uyarılar

  • Başarı: sonuçla birlikte 200. Hata: 4xx/5xx ile
    {"errorCode": "ERR_…", "message": "…", "warnings": ["…"]}

    errorCode kararlıdır ve programlar için tasarlanmıştır, message insanlar için Almanca bir metindir ve değişebilir. Tam liste: Bölüm 7.

  • warnings (isteğe bağlı, başarılı yanıtlarda da): API'nin istekte fark ettiği, ancak reddetmediği şeyler – yok sayılan alanlar, farklı para birimi, yok sayılan now. Lütfen loglayın. Birçok entegrasyon hatası tam olarak orada görünür.
  • İşlem süresi: Bir uç noktanın her yanıtı – hata yanıtları ve /v1/health dahil – Server-Timing: tourapi;dur=<ms> başlığını taşır (W3C Server Timing), örn. tourapi;dur=12.4: isteğin uç noktaya ulaşmasından yanıtın başlamasına kadar geçen milisaniye; istek gövdesinin okunması ile hız sınırı ve arama kapısında bekleme (Bölüm 8) dahil, yanıtın aktarımı hariç. Kendi ölçtüğünüz süreyle arasındaki fark ağ ve aktarımdır. Değer bilgi amaçlıdır, bir taahhüt değildir; ihtiyacınız yoksa başlığı yok sayın.
  • Bilinmeyen alanlar: Sorgu isteklerinde (/v1/price, /v1/prices, /v1/search) bilinmeyen bir alan yok sayılır ve warnings içinde belirtilir. occupancy içinde ise reddedilir (422 ERR_UNKNOWN_FIELD), çünkü orada bir yazım hatası fiyatı değiştirir. /v1/book ve /v1/cancel bilinmeyen her alanı reddeder.

1.7 Örneklerin test dünyası

Örnekler işletmecinin test dünyasına (Golden Seed) karşı çalışır: tur operatörü TEST-TENANT-A, oteller TEST-HOTEL-…, odalar DZ/EZ, pansiyon tipleri RO/BB/HB. Yer tutucular:

Yer tutucu
{{baseUrl}}
Anlamı
Temel URL
Yer tutucu
{{apiKey}}
Anlamı
Müşteri grubu olmayan anahtar (temel sözleşme).
Yer tutucu
{{rabattKey}}
Anlamı
TEST-PARTNER-DISCOUNT müşteri grubunun anahtarı (TEST-HOTEL-DISCOUNT üzerinde −%20, fiyat grubu)
Yer tutucu
{{partnerKey}}
Anlamı
TEST-PARTNER-BASE kontenjan grubunun anahtarı
Yer tutucu
{{poolKey}}
Anlamı
TEST-PARTNER-POOL kontenjan grubunun anahtarı, dışa aktarım yetkisi olmadan
Yer tutucu
{{gesperrterKey}}
Anlamı
iptal edilmiş anahtar
Yer tutucu
{{exportEpoch}}, {{exportSeq}}
Anlamı
export-voll örneğinin manifestindeki epoch ve to_seq
Yer tutucu
{{ohnePreisRef}}, {{zweiteRef}}
Anlamı
Sırasıyla buchen-ohne-preispruefung ve buchen-gleiche-kundenreferenz örneklerinden rezervasyon referansları
Yer tutucu
{{D0}}, {{D2}}, …
Anlamı
Test dünyasının giriş günü D0 artı n gün. D0 = test verilerinin ilk kurulduğu gün + 30 gün. D0 yalnızca test verilerinin sabit bir takvim günüdür, 1.3'teki referans tarihi değildir (o her zaman sunucu tarihidir)
Yer tutucu
{{heute}}, {{gestern}}
Anlamı
Sunucu tarihi (= referans tarihi), önceki gün
Yer tutucu
{{buchungsRef}}
Anlamı
buchen örneğinden rezervasyon referansı
Yer tutucu
{{feedToken}}, {{inhaltCursor}}
Anlamı
inhalt-verzeichnis örneğinden feedToken ve nextCursor
Yer tutucu
{{inhaltEtag}}
Anlamı
inhalt-hotel örneğinden ETag
Yer tutucu
{{*}}
Anlamı
(yalnızca yanıtlarda) herhangi bir değer, ör. zaman damgası

Test dünyasının fiyatları (DZ, 2 yetişkin, yalnızca oda): ilk gece 100,00, sonraki her gece oda başına 80,00; kahvaltı +20,00, yarım pansiyon kişi ve gece başına +35,00. İstisna TEST-HOTEL-KIND (hedef ACE): kişi ve gece başına fiyat (DZ 89,90 / 79,90), 2 ile 11 yaş arası çocuklar −%15.

1.8 Sağlık: GET /v1/health

Load balancer'lar ve izleme için, anahtarsız. 200 {"status": "ok"} şu anlama gelir: Düğüm stoku yüklemiştir ve veritabanıyla son eşitlemesi tazedir (varsayılan: 60 sn'den yeni). Aksi halde status degraded (eşitleme çok eski, reason: "sync_stale") veya down (stok yüklenmemiş, reason: "view_not_loaded") ile 503, ayrıca metin olarak detail. Yanıt tur operatörü veya stok verisi içermez. Bir gönderici IP'sinden saniyede 10'dan fazla çağrı gecikmeli yanıtlanır (en fazla 1 sn, Bölüm 8).

Düğüm bir okuma replikası üzerinden okuyorsa, yanıt ayrıca onun gecikmesini saniye cinsinden belirtir (replica_lag_s). 5 sn'nin üzerinde durum ok kalır, warnings o zaman "replica_lag" içerir. 503 degraded, eşitleme yaşı artı gecikme sınırı aştığında (reason: "replica_lag") veya gecikme ölçülemediğinde gelir (reason: "replica_lag_unknown").

Her yanıt (503 dahil) ayrıca sürecin çalışma süresini saniye cinsinden (uptime_s) ve derleme durumunu (version, durumu olmayan bir derlemede boş) belirtir. Düğüm veritabanını ölçer ölçmez db yer alır: db_latency_ms ile "ok" (bir gidiş-dönüşün süresi, health çağrısında değil eşitleme döngüsünde ölçülür) veya "error" (son ölçüm başarısız). "error" tek başına durumu değiştirmez — düğüm yüklenmiş stokundan yanıt verir; ok durumunda warnings içinde "db_error" yer alır ve eşitleme gerçekleşmezse, sınırdan sonra 503 degraded (sync_stale) gelir.

warnings içindeki "capacity_overlap" (durum ok kalır) şu anlama gelir: Eşitleme döngüsündeki stok kontrolü, oda tipi başına çakışan kapasite dönemleri bulmuştur — bir işletmeci bulgusu (stok, yazma yolu dışından değiştirilmiş), isteğin hatası değil.

### gesundheit
GET {{baseUrl}}/v1/health
{
  "status": "ok",
  "db": "ok",
  "db_latency_ms": "{{*}}",
  "uptime_s": "{{*}}",
  "version": "{{*}}"
}

1.9 /v1 için uyumluluk taahhüdü

/v1 içinde API yalnızca eklemeli olarak değişir:

/v1 içinde gelebilir/v1 içinde asla gelmez (bu /v2 olurdu)
yeni isteğe bağlı istek alanlarıistekte yeni zorunlu alanlar
yeni yanıt alanlarıalanları kaldırmak veya yeniden adlandırmak
yeni endpoint'lerbir alanın tipini veya anlamını değiştirmek
yeni ERR_* kodları, her biri hata kataloğunda belgelenmiş ele alma yöntemiylemevcut bir ERR_* kodunun anlamını değiştirmek

Alıcının bunun için yapması gerekenler:

  • Bilinmeyen yanıt alanlarını yok sayın, reddetmeyin.
  • Bilinmeyen ERR_* kodlarını HTTP durumuna göre ele alın (4xx: körü körüne tekrarlamayın, 5xx/429: aralıklı tekrarlayın, Bölüm 7).
  • message ve warnings insanlar için metinlerdir ve taahhüdün parçası değildir.

/v1 içinde sabit: zaman damgaları UTC olarak RFC 3339'dur (Z), tutarlar tam sent (1.4), metin uzunlukları yazmadan önce kontrol edilir (alan adıyla 422, asla 500, Ek), /v1/health anahtar gerektirmez (1.8).

1.10 İş ortağı portalına giriş

Portal (dokümantasyon, kendi erişimleriniz ve anahtarlarınız) API'nin parçası değildir; API yalnızca anahtara ihtiyaç duyar. Portala giriş şöyle işler:

  • Portal erişimini tur operatörü oluşturur. Bir kullanıcı adı ve tek kullanımlık şifre (30 gün geçerli) alırsınız.
  • İlk giriş: kullanıcı adı ve tek kullanımlık şifre, ardından ikinci faktörü kurun (RFC 6238 uyumlu doğrulayıcı uygulama: QR kodunu tarayın veya anahtarı yazın, altı haneli kodu girin), sonra kendi şifrenizi belirleyin. Geçerli bir tek kullanımlık şifre olmadan ikinci faktör kurulamaz.
  • Sonraki her giriş: şifre ve güncel kod. Bir kod yalnızca bir kez geçerlidir.
  • Yanlış kodlar: 5 yanlıştan sonra giriş sona erer. 7 gün içinde 10 başarısız deneme erişimi kilitler, doğru kod için de. Kilidi yalnızca tur operatörü açabilir.
  • Kilitlendi veya telefon kayboldu: tur operatörüne başvurun. „Kilitlenmeyi kaldır“ erişimi mevcut ikinci faktörle yeniden açar. „2. faktörü sıfırla“ yeni bir tek kullanımlık şifre verir; ardından ikinci faktörü ve şifrenizi yeniden kurarsınız, eski şifre artık geçerli değildir.
  • Portal ve tur operatörü konsolu ayrıdır: portal erişim bilgileri konsolda geçerli değildir.

2. Bir entegrasyonun akışı

/v1/destinations
  -> /v1/search
  -> /v1/price | /v1/prices
  -> /v1/book        idemKey, priceCheck
  -> /v1/booking
  -> /v1/cancel      idemKey
  • /v1/destinations: anahtarın geçerli destination kodları.
  • /v1/search: başlangıç fiyatı ve müsaitlik bilgisiyle rezerve edilebilir oteller.
  • /v1/price: otel, oda, pansiyon ve kişi dağılımı için fiyat bilgisi (bağlayıcı olan yalnızca /v1/book); /v1/prices: tüm pansiyon tipleri (ve odalar) tek seferde.
  • /v1/book: idemKey ve priceCheck (az önce alınan fiyat) ile rezervasyon yapar.
  • /v1/booking: rezervasyonu bizim referansımız veya kendi referansınız üzerinden okur.
  • /v1/cancel: aynı idemKey üzerinden iptal eder.

Bilinmesi gereken kurallar:

  1. Arama ve fiyat bilgi amaçlıdır, bağlayıcı olan yalnızca /v1/book'tur. Fiyat ile rezervasyon arasında tur operatörü fiyatları veya kontenjanı değiştirebilir. priceCheck ile /v1/book değişmiş bir fiyatı, sessizce yeni fiyattan rezervasyon yapmak yerine reddeder (409 ERR_PRICE_DRIFT).
  2. Her rezervasyonun kendi idemKey değeri vardır (çağıran tarafından verilir, ör. kendi işlem numarası). Aynı anahtarla yapılan tekrarlar asla çift rezervasyon oluşturmaz (Bölüm 9).
  3. Hataları körü körüne tekrarlamayın. Hangi hataların tekrarlanmaya değdiği hata kataloğunda (sütun „Çağıran“) yer alır.

3. Fiyat: /v1/price ve /v1/prices (BA)

3.1 POST /v1/price – tek fiyat

Bir otelin tam olarak bir odasını bir konaklama, bir pansiyon tipi ve bir konaklama düzeni için fiyatlandırır.

Alan
hotel
Zorunlu
evet
Anlamı
Otel kodu
Alan
room
Zorunlu
hayır
Anlamı
Oda kodu. Yoksa API, konaklama düzenine izin veren ve pansiyon tipini sunan en uygun fiyatlı müsait odayı alır; hiçbiri müsait değilse bunların en uygun fiyatlısını (o zaman available=false, 3.3). Sözleşme verileri hesaplama çekirdeği tarafından reddedilen bir oda (7.5, ör. ERR_INVALID_AMOUNT, ERR_NO_SECTION) bu sırada atlanır ve warnings içinde oda ve kodla belirtilir; hiçbir oda hesaplanamazsa hata 422 olarak gelir. room ile bu odanın hatası her zaman 422 olarak gelir.
Alan
board
Zorunlu
evet
Anlamı
Pansiyon tipi kodu, tam olarak sözleşmedeki gibi (RO, ro değil). Eksikse: ERR_BOARD_MISSING; oda (room olmadan: hiçbir oda) bunu sunmuyorsa: 422 ERR_BOARD_NOT_OFFERED
Alan
checkIn, checkOut
Zorunlu
evet
Anlamı
Konaklama (Bölüm 1.3)
Alan
occupancy.travellers[]
Zorunlu
evet
Anlamı
age ile yolcular
Alan
currency
Zorunlu
hayır
Anlamı
Tercih edilen para birimi, yalnızca uyarı (1.4)
Alan
now
Zorunlu
hayır
Anlamı
tolere edilir, yok sayılır (1.3)

Yanıt:

Alan
room
Anlamı
fiyatlandırılan oda (istekte room yoksa: en uygun fiyatlı müsait oda); /v1/book'a bu şekilde aktarın
Alan
currency
Anlamı
Sözleşme para birimi, boş = sözleşmede tanımlı değil
Alan
totalCents
Anlamı
Odanın konaklama için toplam fiyatı
Alan
rounding
Anlamı
Yanıtın yuvarlama kuralı (Bölüm 1.4)
Alan
perTravellerCents[]
Anlamı
Yolcu başına fiyat, yaşa göre azalan sırada (önce en büyük, aynı yaşta istek sırası): kalemlerinin tam toplamı, bir kez yuvarlanmış. Toplam = totalCents. Oda fiyatlarında (obje fiyatı) oda kalemlerini ilk yolcu taşır, pansiyon tipi ilgili yolcuda yer alır.
Alan
breakdown[]
Anlamı
Tekil kalemler: chargeType (BaseCharge, PhantomBaseCharge, GuestCharge, BoardCharge, Extra), code, traveller (perTravellerCents içindeki indeks), night (0'dan başlayan indeks, -1 = konaklama başına), amountExact (ondalık sayı olarak tam tutar, ör. "-13.485"), amountCents, ekstralarda applianceCode, bir ekstra ailesinin ekstralarında (aynı applianceCode değerine sahip birden fazla ekstra) ayrıca variant (varyant; ekstrayı ancak ikisi birlikte belirtir), ücretsiz gece kalemlerinde ayrıca freeNight: true (bu gece tamamen veya kısmen bağışlanmıştır, ör. „7=6“: yedinci gece yolcu başına temel fiyatı ve – teklife göre – pansiyon tipini sıfırlayan bir kalem taşır). Sıra: gece, onun içinde yolcu, onun içinde hesaplama adımı (temel fiyat, kişi indirimi/ek ücreti, kendi kişi indirimiyle birlikte pansiyon tipi, ekstralar); konaklama başına kalemler en sonda.
Alan
separateExtras[]
Anlamı
yalnızca varsa: ayrı gösterilen ekstralar. inTotal=true: zorunlu ekstra, toplam fiyata dahil. inTotal=false: isteğe bağlı ekstra, toplam fiyata dahil değil. code ekstrayı belirtir, bir ekstra ailesinde variant ile birlikte. amountCents ekstranın tam toplamıdır, bir kez yuvarlanmış; inTotal=true durumunda kendi breakdown satırlarının toplamından birkaç sent sapabilir – belirleyici olan breakdown'dur.
Alan
availability
Anlamı
configured (kontenjan tanımlı), available (her gece açık), minFree (geceler boyunca en küçük boş sayı, 99'a kadar tam; -1 = her gece 99'dan fazla boş veya serbest satış; 0 = en az bir gece rezervasyona kapalı)
Alan
warnings[]
Anlamı
Uyarılar (1.6); room olmadan ayrıca sözleşme hatası nedeniyle atlanan her oda için: zimmer 'EZ' ausgelassen: ERR_INVALID_AMOUNT (…)
### preis-einzeln
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "currency": "EUR",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{
  "room": "DZ",
  "currency": "EUR",
  "rounding": {"mode": "Commercial", "decimalPlaces": 2, "scope": "Person"},
  "totalCents": 18000,
  "perTravellerCents": [18000, 0],
  "breakdown": [
    {"chargeType": "BaseCharge", "code": "Base", "traveller": 0, "night": 0, "amountExact": "100.00", "amountCents": 10000},
    {"chargeType": "BaseCharge", "code": "ExtraDay", "traveller": 0, "night": 1, "amountExact": "80.00", "amountCents": 8000}
  ],
  "availability": {"configured": true, "available": true, "minFree": 5}
}

room olmadan API en uygun fiyatlı uygun odayı arar – burada bir kişi için EZ; yanıttaki room onu belirtir.

### preis-guenstigstes-zimmer
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
{
  "room": "EZ",
  "currency": "EUR",
  "rounding": {"mode": "Commercial", "decimalPlaces": 2, "scope": "Person"},
  "totalCents": 12500,
  "perTravellerCents": [12500],
  "breakdown": [
    {"chargeType": "BaseCharge", "code": "Base", "traveller": 0, "night": 0, "amountExact": "70.00", "amountCents": 7000},
    {"chargeType": "BaseCharge", "code": "ExtraDay", "traveller": 0, "night": 1, "amountExact": "55.00", "amountCents": 5500}
  ],
  "availability": {"configured": true, "available": true, "minFree": 5}
}

Dolu, kapatılmış, kontenjan tanımlanmamış veya – bir müşteri grubunun anahtarında – tahsis edilmemiş odaları room olmadan yapılan seçim, daha ucuz olsalar bile atlar: Aşağıdaki otelde EZ'nin (70 EUR) kontenjanı yoktur ve rezervasyona kapalıdır, API daha pahalı olan, rezerve edilebilir DZ'yi alır.

### preis-guenstigstes-verfuegbares
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-STOP",
  "board": "RO",
  "checkIn": "{{D10}}",
  "checkOut": "{{D11}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
{
  "room": "DZ",
  "currency": "EUR",
  "rounding": {"mode": "Commercial", "decimalPlaces": 2, "scope": "Person"},
  "totalCents": 10000,
  "perTravellerCents": [10000],
  "breakdown": [
    {"chargeType": "BaseCharge", "code": "Base", "traveller": 0, "night": 0, "amountExact": "100.00", "amountCents": 10000}
  ],
  "availability": {"configured": true, "available": true, "minFree": 5}
}

Oda fiyatlarında kişi başı fiyat. Birçok sözleşme kişiyi değil odayı fiyatlandırır (obje fiyatı). Bu durumda tüm oda kalemleri – temel fiyat, oda veya işlem başına ekstralar – perTravellerCents içinde ilk yolcunun kalemine yazılır, diğerleri bunun karşılığında 0 taşır (preis-einzeln örneğinde: [18000, 0]). Pansiyon tipi, obje fiyatında da her zaman kişi ve gece başına bir fiyattır: ilgili yolcuda yer alır (kahvaltı ile [22000, 4000], bkz. preis-alle-verpflegungen); tek başına seyahat eden kişi tam oda fiyatını, ancak yalnızca bir pansiyon tipini öder. Kişi başına sabit bir indirim (ör. kişi başına −20 € erken rezervasyon) obje fiyatında oda fiyatını düşürür ve bu nedenle yine ilk kalemde yer alır. Bu kasıtlıdır ve böyle kalır (EDF 5.1.6: „objektbasierte Zusatzleistungen werden der 1. Person zugerechnet“; her kalem bir kez yuvarlanır, toplam her zaman tam olarak totalCents olur). İlk kalem en büyük yolcuya aittir – perTravellerCents ve breakdown[].traveller isteğin sırasına göre değil, yaşa göre azalan sırada dizilir. Kişiye bağlı kalemler (ör. çocuk fiyatları) ilgili yolcuda yer alır. Bu nedenle perTravellerCents „kişi başı“ gösterim için tasarlanmamıştır: totalCents değerini yolcu sayısına bölün ve kendiniz yuvarlayın.

Fiyat anahtara bağlıdır: Aynı istek bir müşteri grubunun anahtarıyla o grubun özel fiyatını verir (burada −%20). TEST-PARTNER-DISCOUNT bir fiyat grubudur: genel stoktan rezervasyon yapar, availability grubu olmayan anahtardakiyle aynıdır (1.1).

### preis-kundengruppe
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{rabattKey}}

{
  "hotel": "TEST-HOTEL-DISCOUNT",
  "room": "DZ",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{
  "room": "DZ",
  "currency": "EUR",
  "rounding": {"mode": "Commercial", "decimalPlaces": 2, "scope": "Person"},
  "totalCents": 14400,
  "perTravellerCents": [14400, 0],
  "breakdown": [
    {"chargeType": "BaseCharge", "code": "Base", "traveller": 0, "night": 0, "amountExact": "80.00", "amountCents": 8000},
    {"chargeType": "BaseCharge", "code": "ExtraDay", "traveller": 0, "night": 1, "amountExact": "64.00", "amountCents": 6400}
  ],
  "availability": {"configured": true, "available": true, "minFree": 5}
}

Çocuk indirimli kişi başı fiyat: 89,90 üzerinden −%15, −13,485 eder – bir sentin kesri. Kalem tam kalır (amountExact); yalnızca çocuğun toplamı bir kez yuvarlanır: 89,90 − 13,485 + 79,90 − 11,985 = 144,33 → perTravellerCents[2] = 14433. Bir yolcunun amountCents değerleri, tam olarak onun fiyatını verecek şekilde dağıtılır: her satır, yolcunun o satıra kadarki yuvarlanmış kümülatif toplamı eksi bir önceki satıra kadarki toplamdır (8990, −1348, 7990, −1199 = 14433). Kalemleri tek tek yuvarlayıp toplayan (−13,49, −11,99) 144,32'ye ulaşır – bu fiyat değildir.

### preis-kind-bruchcent
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-KIND",
  "room": "DZ",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}, {"age": 8}]}
}
{
  "room": "DZ",
  "currency": "EUR",
  "rounding": {"mode": "Commercial", "decimalPlaces": 2, "scope": "Person"},
  "totalCents": 48393,
  "perTravellerCents": [16980, 16980, 14433],
  "breakdown": [
    {"chargeType": "BaseCharge", "code": "Base", "traveller": 0, "night": 0, "amountExact": "89.90", "amountCents": 8990},
    {"chargeType": "BaseCharge", "code": "Base", "traveller": 1, "night": 0, "amountExact": "89.90", "amountCents": 8990},
    {"chargeType": "BaseCharge", "code": "Base", "traveller": 2, "night": 0, "amountExact": "89.90", "amountCents": 8990},
    {"chargeType": "GuestCharge", "code": "Base", "traveller": 2, "night": 0, "amountExact": "-13.485", "amountCents": -1348},
    {"chargeType": "BaseCharge", "code": "ExtraDay", "traveller": 0, "night": 1, "amountExact": "79.90", "amountCents": 7990},
    {"chargeType": "BaseCharge", "code": "ExtraDay", "traveller": 1, "night": 1, "amountExact": "79.90", "amountCents": 7990},
    {"chargeType": "BaseCharge", "code": "ExtraDay", "traveller": 2, "night": 1, "amountExact": "79.90", "amountCents": 7990},
    {"chargeType": "GuestCharge", "code": "ExtraDay", "traveller": 2, "night": 1, "amountExact": "-11.985", "amountCents": -1199}
  ],
  "availability": {"configured": true, "available": true, "minFree": 5}
}

Ücretsiz geceler („7=6“). Bir geceyi bağışlayan kalem, teklifin applianceCode değeri ve freeNight: true ile bir Extra'dır; toplam fiyata dahildir ve bağışlanan gecede her yolcu için doğrudan onun temel fiyatının arkasında yer alır. Test dünyasında (TEST-HOTEL-FREINACHT, DZ, yalnızca oda, kişi başı fiyat: ilk gece 100,00, sonraki her gece 80,00, checkIn = {{D0}}) 2 yetişkin 7 gece için 1160,00 yerine 1000,00 öder: gece 6 (yedinci gece) her yolcu için {"chargeType": "Extra", "code": "PN", "night": 6, "amountExact": "-80.00", "applianceCode": "FSO1", "freeNight": true} taşır. Yarım pansiyonla aynı kalem pansiyon tipini de bağışlar (-115.00). 14 gece iki ücretsiz gece verir (gece 12 ve 13), 6 gece hiç vermez.

3.2 POST /v1/prices – tüm pansiyon tipleri tek seferde

/v1/price gibi, ancak board yerine isteğe bağlı boards[] (yoksa: odanın tüm pansiyon tipleri) ve isteğe bağlı room (yoksa: tüm odalar). Konaklama düzenine izin vermeyen odalar düşer; hiçbiri izin vermiyorsa neden hata olarak gelir (ERR_OCCUPANCY_NOT_ALLOWED). Hiçbir odanın sunmadığı, istenen bir pansiyon tipi: 422 ERR_BOARD_NOT_OFFERED. board alanı burada filtreleme yapmaz: Onu gönderen yine de tüm pansiyon tiplerini ve warnings içinde bir uyarı alır (aşağıdaki örnek); filtreleme yalnızca boards[] ile yapılır.

Yanıt: currency, rounding, room ve boards[] içeren rooms[] (her pansiyon tipi için board, globalType (EDF dışa aktarımındaki ve açık aramadaki pansiyon türü: RO için AO, aksi halde tur operatörünün eşlemesi, aksi halde bir pansiyon türüyse kodun kendisi, aksi halde XX = eşlenemez), totalCents, perTravellerCents, separateExtras, availability), oda başına fiyata göre artan sırada; warnings. breakdown yok.

Bir odanın bir pansiyon tipi bu konaklama düzeni için hesaplanamıyorsa, çünkü sözleşme orada hatalıdır (ERR_NEGATIVE_TRAVELLER_PRICE, ERR_NEGATIVE_PERCENT_BASE, Bölüm 7.5) veya bu yolcu grubuna satılmıyordur (ERR_BOARD_NOT_AVAILABLE, 3.5), yalnızca bu kombinasyon düşer: fiyatsız olarak rooms[].errors[] içinde (board, globalType, errorCode, message) ve warnings içinde yer alır; matrisin geri kalanı fiyatlanmış kalır, odanın boards[] listesi o zaman boş olabilir. Bir odanın diğer her sözleşme hatasında da aynısı geçerlidir (Bölüm 7.5, ör. ERR_NO_SECTION: bir gece için sözleşmede sezon yok): etkilenen pansiyon tipleri kodla birlikte rooms[].errors[] ve warnings içinde yer alır, diğer tüm odalar room ve board ile /v1/price ile aynı fiyatlarla fiyatlanmış kalır. Tek bir kombinasyon bile hesaplanamıyorsa kod hata olarak gelir (422). İsteğin kendisindeki hatalar isteği yine tamamen reddeder.

Süre sınırı: /v1/prices ve room olmadan /v1/price birden fazla odayı veya pansiyon tipini hesaplar; bu 2 sn'yi aşarsa (normal işletimde birkaç milisaniye sürer) 503 ERR_PRICE_TIMEOUT gelir – asla yarım bir matris veya odaların bir kısmından seçilmiş „en uygun fiyatlı“ bir oda gelmez. Çözüm: room veya boards belirtin. Süre sınırı /v1/prices için her zaman geçerlidir, room ve tek bir pansiyon tipiyle de; yalnızca room ile /v1/price çağrısının kendi süre sınırı yoktur ve bu nedenle güvenli çıkış yoludur. 503, Retry-After taşımaz: önce daraltın, sonra tekrarlayın. Çağıran bağlantıyı kapatırsa hesaplama iptal edilir ve yanıt verilmez.

### preis-alle-verpflegungen
POST {{baseUrl}}/v1/prices
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{
  "currency": "EUR",
  "rounding": {"mode": "Commercial", "decimalPlaces": 2, "scope": "Person"},
  "rooms": [
    {
      "room": "DZ",
      "boards": [
        {"board": "RO", "globalType": "AO", "totalCents": 18000, "perTravellerCents": [18000, 0],
         "availability": {"configured": true, "available": true, "minFree": 5}},
        {"board": "BB", "globalType": "BB", "totalCents": 26000, "perTravellerCents": [22000, 4000],
         "availability": {"configured": true, "available": true, "minFree": 5}},
        {"board": "HB", "globalType": "HB", "totalCents": 32000, "perTravellerCents": [25000, 7000],
         "availability": {"configured": true, "available": true, "minFree": 5}}
      ]
    }
  ]
}

boards[] yerine board ile, onsuz halle aynı matris gelir, ayrıca şu uyarı board: 'HB' wird bei /v1/prices ignoriert — Verpflegungen ueber boards waehlen (fehlt boards: alle des Zimmers):

### preis-alle-verpflegungen-board-ignoriert
POST {{baseUrl}}/v1/prices
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "board": "HB",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}

3.3 Fiyat yanıtlarında müsaitlik

availability her zaman doludur ve fiyatlandırılan odayı tüm geceler boyunca ifade eder. available=false olan bir fiyat bir fiyat bilgisidir, teklif değildir: rezervasyon başarısız olur (Bölüm 5.3). Tam olarak /v1/book'un karşısında satış yaptığı şey sayılır: rezervasyonlar ve günlük durumdan sonra gece başına boş kapasite ve bir kontenjan grubunun anahtarında ek olarak onun tahsisi (grubun zaten rezerve ettiği düşülmüştür; oda ve gece için tahsis yoksa hiçbir şey boş değildir). Bir fiyat grubu, grubu olmayan bir anahtar gibi sayılır (1.1). minFree bunların geceler boyunca en küçüğüdür.

TourAPI gece başına 99'a kadar tam sayar. Hangi gecenin minFree'ye neyle katkıda bulunduğu:

GeceminFree'ye katkıAllotment dosyası (Bölüm 10)
1 ile 99 arası birim boşsayının kendisi01–99
99'dan fazla boş veya serbest satışminFree'yi düşürmez**
Satış durdurma0SS
talep üzerine0RR
dolu, kapalı, kapasitesiz, oda ve gece için tahsisi olmayan müşteri grubu000

minFree yalnızca her gecede 99'dan fazla boş yer varsa veya serbest satıştaysa -1'dir; satış durdurma, talep üzerine veya 0 olan tek bir gece minFree'yi 0 yapar (available=false). Bu, önbellek teslimatının EDF allotment'ındaki gösterimle aynıdır, böylece önbellek ve API aynı sayıyı gösterir.

  • configured=true: TourAPI bu oda için bir kontenjan biliyor. Bu henüz her bir gece hakkında bir şey söylemez: Kapasite girilmemiş bir gece odayı available=false yapar (minFree 0), ancak oda configured=true kalır.
  • configured=false: bu oda için hiç kontenjan tanımlı değildir; rezervasyon yapılamaz.

Yalnızca tek tek gecelerde kapasite eksikse, arama ve /v1/book aynı nedeni belirtir: ERR_NO_INVENTORY (arama 4.2, rezervasyon 5.3). „Rezerve edilebilir“ için belirleyici olan configured değil, available'dır.

availability bir anlık durumdur, taahhüt değildir; bağlayıcı kontrolü yalnızca /v1/book yapar. Bir rezervasyonu veya iptali, onu gerçekleştiren sunucu düğümü bir sonraki istekte gösterir (minFree düşer veya yükselir). Diğer düğümler, diğer kanalların rezervasyonları ve sözleşme değişiklikleri birkaç saniye içinde yansır – aynı şekilde kendi düğümü de, yeni durumu hemen yükleyemediği nadir durumda (rezervasyonun kendisi yine de geçerlidir).

3.4 Odanın satış kuralları: minimum konaklama, giriş günleri, satış penceresi, release

Bir otel sözleşmesi, otelin hangi konaklamaları kabul ettiğini belirleyebilir, örneğin „yüksek sezonda en az 7 gece, giriş yalnızca cumartesi“, „yarım pansiyon yalnızca 31.10.'a kadar girişte“ veya „release 14 gün“ (rezervasyon en geç girişten 15 gün önce). Bu tür kurallar fiyatı değiştirmez; bir odanın istenen konaklama için sunulup sunulmayacağına karar verir. Bir kuralın hariç tuttuğu konaklama fiyat almaz:

KodKural şunu gerektirir …Çağıran
ERR_STAY_LENGTH_NOT_ALLOWEDfarklı bir konaklama süresi (minimum veya maksimum gece)süreyi değiştirin
ERR_ARRIVAL_DAY_NOT_ALLOWEDfarklı bir giriş veya çıkış haftanın günüseyahat günlerini kaydırın
ERR_TRAVEL_DATES_NOT_ALLOWEDbelirli bir dönem içinde seyahat tarihleri (satış penceresi)farklı dönem
ERR_BOARD_NOT_ALLOWEDbu konaklama için farklı bir pansiyon tipifarklı pansiyon tipi
ERR_LEAD_TIME_NOT_ALLOWEDdaha uzun ön süre: giriş release süresi içindedaha geç giriş

message odayı, kuralı ve gerekeni belirtir, örneğin „Zimmer DZ, Verkaufsregel 1: wenn Anreise 2026-07-01 bis 2026-08-31, verlangt mindestens 7 Nächte“. Bir konaklama bir kuralın birden fazla kısmını ihlal ederse şu sıra geçerlidir: süre, haftanın günü, dönem, pansiyon tipi, release.

Release (ön süre). n günlük release içeren bir kural, referans tarihi (sunucu tarihi, 1.3) ile giriş arasında n günden fazla takvim günü gerektirir: release 3 ve referans tarihi 10.05. ise 13.05. girişi hâlâ kapalıdır, 14.05.'ten itibaren rezerve edilebilir; release 0 ise yalnızca referans tarihindeki giriş kapalıdır. Release bir döneme bağlı olabilir (örneğin „eylülden itibaren 14 gün“): konaklamanın bir gecesi o dönemdeyse geçerlidir, girişe kadar ölçülür. Kural çıkışa bağlıysa (ApplyTo Departure), süre çıkışa kadar ölçülür: bu durumda referans tarihi ile çıkış arasında n günden fazla takvim günü gerekir. message referans tarihini, girişi ve ön süreyi belirtir. Bugün kapalı olan bir konaklama yarın açılmaz; tekrar denemek işe yaramaz.

  • /v1/price: room olmadan, kuralı konaklamayı hariç tutan bir oda seçimden düşer; hiçbir oda konaklamayı kabul etmezse neden 422 olarak gelir. Bu neden, başka bir odanın ERR_OCCUPANCY_NOT_ALLOWED hatasına göre önceliklidir, çünkü neyin değiştirilmesi gerektiğini daha kesin söyler.
  • /v1/prices: kurallar pansiyon tipi başına geçerli olabilir; yalnızca ilgili pansiyon tipi düşer. Geriye bir şey kalmazsa neden 422 olarak gelir.
  • /v1/search: otel düşer, kod diagnostics.reasons içinde yer alır.
  • /v1/book: priceCheck ile kural istenen pansiyon tipi için geçerlidir; priceCheck olmadan (pansiyon tipi bilinmiyor), odanın bir pansiyon tipi konaklamayı kabul eder etmez satış yapılır.

ERR_ROOM_RESTRICTION_INVALID (422) şu anlama gelir: sözleşmedeki bir kural değerlendirilemiyor. Bu bir sözleşme hatasıdır – bildirin.

3.5 Yalnızca belirli yolcu grupları için pansiyon tipi

Bir pansiyon tipi ek ücreti yolcu grubunun bileşimine bağlı olabilir, örneğin „yarım pansiyon ek ücreti en az 2 yetişkinli gruplar için geçerlidir“. İstenen konaklama düzeni buna uymuyorsa, bu pansiyon tipi bu konaklama düzeni için rezerve edilemez: 422 ERR_BOARD_NOT_AVAILABLE. TourAPI hiçbir zaman bir pansiyon tipi için pansiyon tipi olmadan fiyat hesaplamaz. message pansiyon tipini, yolcuyu, geceyi ve gereken grubu belirtir. Bu bir sözleşme hatası değildir; farklı bir pansiyon tipi veya konaklama düzeni hesaplanabilir (Çağıran: farklı pansiyon tipi veya konaklama düzeni).

  • /v1/price: room olmadan oda bu pansiyon tipi için seçimden düşer (bir satış kuralında olduğu gibi, 3.4); hiçbir oda hesaplanamazsa neden 422 olarak gelir.
  • /v1/prices: odanın yalnızca bu pansiyon tipi düşer, errorCode ile rooms[].errors[] içinde bir kayıt olarak (3.2); geri kalanı fiyatlanmış kalır.
  • /v1/search: otel düşer, kod diagnostics.reasons içinde yer alır.
  • priceCheck ile /v1/book: 422, hiçbir şey rezerve edilmez.

4. Arama: /v1/search (BA)

4.1 İstek ve yanıt

Tur operatörünün bir konaklama, bir pansiyon tipi ve bir konaklama düzeni için rezerve edilebilir otellerini bulur, her otel için en uygun fiyatlı müsait odayla (room olmadan /v1/price gibi): Dolu olan daha ucuz bir oda, başka bir oda rezerve edilebilir olduğu sürece oteli listeden düşürmez.

Alan
destination
Zorunlu
hayır
Anlamı
Otelin bölge veya havalimanı kodu (aşağıda); boş = tüm oteller
Alan
board, checkIn, checkOut, occupancy, currency, now
Zorunlu
/v1/price gibi
Alan
includeUnavailable
Zorunlu
hayır
Anlamı
true: rezerve edilemeyen oteller işaretlenmiş olarak gelir (varsayılan false)
Alan
pageSize, cursor
Zorunlu
hayır
Anlamı
Sayfalar, Bölüm 4.3

Yanıt:

Alan
results[]
Anlamı
Sayfanın sonuçları, fromTotalCents'e göre artan, eşitlikte otel koduna göre
Alan
results[].hotel, name, room
Anlamı
Otel ve fiyatın ilişkili olduğu oda
Alan
results[].fromTotalCents, currency
Anlamı
Otelin sözleşme para biriminde başlangıç fiyatı (en uygun fiyatlı müsait oda; bookable=false durumunda genel olarak en uygun fiyatlı oda)
Alan
results[].availability
Anlamı
/v1/price'taki gibi
Alan
results[].bookable
Anlamı
true = tüm geceler müsait
Alan
results[].reason, priceInformational
Anlamı
yalnızca bookable=false durumunda (yalnızca includeUnavailable ile): neden ve „yalnızca fiyat bilgisi“ işareti
Alan
diagnostics
Anlamı
sayfa başına: considered = kontrol edilen oteller = returned + skipped; reasons[] = otellerin neden düştüğü, her neden için sayıyla; timeBudgetExhausted = sayfa zaman bütçesi nedeniyle kesildi (4.3); roomErrors[] = kontrol edilen otellerin bir sözleşme hatası (7.5) nedeniyle atlanan odaları, her kod için sayıyla – otel başka bir odayla eşleşse bile
Alan
nextCursor
Anlamı
kontrol edilecek başka oteller olduğu sürece dolu (4.3)
Alan
warnings[]
Anlamı
Uyarılar; karışık sözleşme para birimlerinde toplu bir uyarı

Arama, düşen otellerin otel kodlarını belirtmez, yalnızca nedenleri ve sayıları. Boş sonuç listesi bu nedenle „stok yok“ anlamına gelmez: diagnostics.reasons ör. sezonun uymadığını (ERR_NO_SECTION), her şeyin dolu olduğunu (ERR_NOT_AVAILABLE), bir gece için kontenjan tanımlanmadığını (ERR_NO_INVENTORY) veya otelin hiçbir odasının istenen pansiyon tipini sunmadığını (ERR_BOARD_NOT_OFFERED) söyler.

destination kodlarının nereden geldiği: Her otelin, tur operatörünün sözleşmesinde bir bölge kodu (ör. PMI) ve isteğe bağlı havalimanı kodları vardır; destination bunlardan biriyle tam olarak aynıysa otel eşleşir (büyük/küçük harf önemlidir). Bölge kodları yalnızca A-Z a-z 0-9 . _ - karakterlerinden oluşur (1 ile 64 karakter, serbest metin yok); havalimanı kodları üç büyük harfli IATA kodlarıdır. Anahtarın geçerli kodlarını GET /v1/destinations verir (4.5). Bu anahtar için hiç oteli olmayan bir kod bir hatadır: 422 ERR_UNKNOWN_DESTINATION (örnek suche-unbekanntes-ziel), asla sessizce boş bir liste değil. İlk sayfa (cursor olmadan) kontrol edilir; hedef sayfalama sırasında kaybolursa arama boş bir son sayfayla biter (4.3).

### suche-ziel
POST {{baseUrl}}/v1/search
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "destination": "PMI",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{
  "results": [
    {"hotel": "TEST-HOTEL-BASE", "name": "TEST Basis Palma", "room": "DZ", "currency": "EUR",
     "fromTotalCents": 18000, "availability": {"configured": true, "available": true, "minFree": 5},
     "bookable": true},
    {"hotel": "TEST-HOTEL-DISCOUNT", "name": "TEST Rabatt Palma", "room": "DZ", "currency": "EUR",
     "fromTotalCents": 18000, "availability": {"configured": true, "available": true, "minFree": 5},
     "bookable": true},
    {"hotel": "TEST-HOTEL-TWIN", "name": "TEST Zwilling A Palma", "room": "DZ", "currency": "EUR",
     "fromTotalCents": 18000, "availability": {"configured": true, "available": true, "minFree": 5},
     "bookable": true}
  ],
  "diagnostics": {"considered": 3, "returned": 3, "skipped": 0}
}

Aynı kod küçük harfle yazıldığında API için farklı, bilinmeyen bir hedeftir:

### suche-unbekanntes-ziel
POST {{baseUrl}}/v1/search
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "destination": "pmi",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{"errorCode": "ERR_UNKNOWN_DESTINATION",
 "message": "destination: kein Hotel mit diesem Ziel-Code fuer diesen Key (gueltige Codes: GET /v1/destinations; Gross-/Kleinschreibung zaehlt)"}

4.2 Rezerve edilemeyen oteller

Varsayılan olarak arama yalnızca rezerve edilebilir sonuçları verir ve geri kalanını diagnostics içinde sayar. includeUnavailable=true ile rezerve edilemeyen oteller bookable=false, reason ve priceInformational=true ile gelir. Nedenler /v1/book'taki ile aynı anlama gelir:

  • ERR_NO_INVENTORY: en az bir gece için hiç kontenjan tanımlı değildir – oda için hiç yoktur (configured=false) veya yalnızca bu gece için yoktur. /v1/book aynı konaklamayı ERR_NO_INVENTORY ile reddeder.
  • ERR_NOT_AVAILABLE: her gecenin kontenjanı vardır, ancak her biri açık değildir (dolu, satış durdurma, kapalı, talep üzerine, müşteri grubunun tahsisi tükenmiş veya mevcut değil). Tam olarak hangi durum olduğunu /v1/book belirtir (ERR_SOLD_OUT, ERR_STOP_SALE, ERR_INVENTORY_CLOSED, ERR_GROUP_LIMIT, Bölüm 5.3).

Örnekte TEST-HOTEL-SOLD'un o gece için kapasitesi yoktur, TEST-HOTEL-STOP satış durdurma durumundadır.

### suche-nicht-buchbare
POST {{baseUrl}}/v1/search
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "destination": "IBZ",
  "includeUnavailable": true,
  "board": "RO",
  "checkIn": "{{D11}}",
  "checkOut": "{{D12}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{
  "results": [
    {"hotel": "TEST-HOTEL-SOLD", "name": "TEST Ausgebucht Ibiza", "room": "DZ", "currency": "EUR",
     "fromTotalCents": 10000, "availability": {"configured": true, "available": false, "minFree": 0},
     "bookable": false, "reason": "ERR_NO_INVENTORY", "priceInformational": true},
    {"hotel": "TEST-HOTEL-STOP", "name": "TEST Stop-Sale Ibiza", "room": "DZ", "currency": "EUR",
     "fromTotalCents": 10000, "availability": {"configured": true, "available": false, "minFree": 0},
     "bookable": false, "reason": "ERR_NOT_AVAILABLE", "priceInformational": true}
  ],
  "diagnostics": {"considered": 2, "returned": 2, "skipped": 0}
}

4.3 Sayfalar ve cursor

  • Bir istek, otel kodu sırasıyla en fazla pageSize otelden oluşan bir sayfayı kontrol eder (varsayılan 50, en fazla 100; 1–100 dışında: 422 ERR_BAD_PAGE_SIZE).
  • Başka oteller varsa yanıtta nextCursor yer alır. Sonraki sayfa: aynı istek artı "cursor": "<nextCursor>". Son sayfada nextCursor yoktur.
  • Fiyat sıralaması sayfa başına geçerlidir. Fiyata göre tam bir listeye ihtiyaç duyan sayfaları gezer ve kendisi sıralar. diagnostics sayfa başına geçerlidir.
  • Cursor opaktır ve isteğe ve anahtara bağlıdır. Farklı istek (hedef, dönem, konaklama düzeni, pansiyon tipi, includeUnavailable) veya farklı anahtar: 422 ERR_CURSOR_MISMATCH. Okunamayan veya süresi dolmuş cursor (ör. sunucunun yeniden başlatılmasından sonra): 422 ERR_BAD_CURSOR – o zaman cursor olmadan yeniden başlayın. pageSize ve currency sayfalar arasında değişebilir. Cursor'ın uzunluğu sabittir ve otel hakkında hiçbir şey söylemez.
  • Kesilmiş sayfa: Yük altında bir sayfa 60 ms'den sonra yeni bir otele başlamaz. Bu durumda pageSize'dan kısadır, diagnostics.timeBudgetExhausted=true olur ve nextCursor devam ettirir. Bu bir hata değildir: sayfalamaya devam edin.
  • Ne pageSize ne de cursor gönderilirse ve daha fazla otel varsa, ek olarak warnings içinde bir uyarı yer alır – liste o zaman yalnızca ilk sayfadır.
### suche-seitenweise
POST {{baseUrl}}/v1/search
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "pageSize": 3,
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{
  "results": [
    {"hotel": "TEST-HOTEL-ALTAKTION", "name": "TEST Altaktion Teneriffa", "room": "DZ", "currency": "EUR",
     "fromTotalCents": 18000, "availability": {"configured": true, "available": true, "minFree": 5},
     "bookable": true},
    {"hotel": "TEST-HOTEL-BASE", "name": "TEST Basis Palma", "room": "DZ", "currency": "EUR",
     "fromTotalCents": 18000, "availability": {"configured": true, "available": true, "minFree": 5},
     "bookable": true}
  ],
  "diagnostics": {"considered": 3, "returned": 2, "skipped": 1,
                  "reasons": [{"reason": "ERR_NO_INVENTORY", "count": 1}]},
  "nextCursor": "{{*}}"
}

4.4 Aramanın yükü ve süre sınırları

Arama en maliyetli istektir. Tur operatörü başına aynı anda en fazla 8 arama çalışır; bunlar hesaplama süresini paylaşır (tur operatörü başına sunucu çekirdeklerinin dörtte biri), böylece yoğun bir tur operatörü diğerlerini yavaşlatmaz. Kota doluysa hemen Retry-After: 1 ile 429 ERR_SEARCH_BUSY gelir. 10 sn'den uzun hesaplama yapan bir arama 503 ERR_SEARCH_TIMEOUT ile iptal edilir – asla sessizce kısaltılmış bir listeyle değil (yük altında kesilmiş bir sayfada ise her zaman nextCursor vardır, 4.3). Çözüm: destination belirtin veya daha küçük pageSize. Çağıran bağlantıyı kapatırsa arama hesaplamasını iptal eder ve artık yanıt vermez.

4.5 GET /v1/destinations – geçerli hedef kodları

Bu anahtarın aramasında destination'ın filtreleyebileceği tüm kodları verir: tur operatörünün otellerinin bölge ve havalimanı kodları, artan sırada, her kod bir kez. Yalnızca kendi kodları – bir anahtar asla başka bir tur operatörünün hedeflerini görmez, bir kontenjan grubunun anahtarı yalnızca tahsisi olan otellerinin hedeflerini (1.1; tahsis yoksa liste boştur). Parametresizdir; liste, tur operatörü otel eklediğinde veya kaldırdığında değişir – bir kontenjan grubunun anahtarında ayrıca tur operatörü onun tahsislerini değiştirdiğinde ve bir müşteri grubunun her anahtarında tur operatörü grubun türünü değiştirdiğinde.

AlanAnlamı
destinations[].codeKod, destination içine tam olarak böyle aktarılır
destinations[].nameGösterim için açık metin; TourAPI kodu tanımıyorsa yoktur
warnings[]Uyarılar, ör. gönderilen parametrelerle ilgili (yok sayılırlar)
### ziele
GET {{baseUrl}}/v1/destinations
X-Api-Key: {{apiKey}}
{
  "destinations": [
    {"code": "ACE", "name": "Lanzarote"},
    {"code": "AGP", "name": "Malaga / Costa del Sol"},
    {"code": "ALC", "name": "Alicante / Costa Blanca"},
    {"code": "BCN", "name": "Barcelona"},
    {"code": "FAO", "name": "Faro / Algarve"},
    {"code": "FUE", "name": "Fuerteventura"},
    {"code": "IBZ", "name": "Ibiza"},
    {"code": "LPA", "name": "Gran Canaria"},
    {"code": "MAH", "name": "Menorca"},
    {"code": "PMI", "name": "Palma de Mallorca"},
    {"code": "RHO", "name": "Rhodos"},
    {"code": "TFS", "name": "Teneriffa Sued"}
  ]
}

4a. Açık arama: POST /v1/search/open (BA)

4a.1 Ne için

„1 ile 30 Ekim arasında 5 ila 7 gece için en ucuz nerede?“ – otel kodu olmadan ve sabit tarih olmadan. Açık arama her otel için pencerenin tüm varış günleri, aralığın tüm konaklama süreleri, tüm odalar ve pansiyon tipleri üzerinden en iyi teklifi verir ve bunları tüm sayfalar boyunca genel olarak sıralar. İki taahhüt:

  • Fiyat kesindir: best.totalCents ve best.perTravellerCents, aynı otel, oda, pansiyon tipi, varış ve ayrılış ve aynı konaklama düzeniyle /v1/price ile bit bit aynıdır. Bir önbellekten başlangıç fiyatları yok, örnekleme yok.
  • Sıralama kanıtlanmıştır: gösterilmeyen hiçbir otelin en iyi teklifi son sonuçtan daha iyi değildir. Yük altında yalnızca sayfa kısalır (4a.4); fiyat asla daha az kesin olmaz, sıralama asla tahmin edilmez.

Bir web sitesinin akışı: açık arama (liste) → tarih matrisi ile bir otelin „tarihler ve fiyatlar“ı (4b, aynı pencere) → seçilen teklifin /v1/price ile ayrıntısı (best veya hücre içinden hotel, room, board, checkIn, checkOut, aynı konaklama düzeni: dökümüyle birlikte aynı fiyat; bu tarihlerin diğer pansiyon tipleri, her pansiyon tipi için room olmadan /v1/price ile) → priceCheck.expectedCents = gösterilen fiyat ile /v1/book. Bir teklif token'ı yoktur: rezervasyon fiyatı zaten kontrol eder.

Yetki: Açık arama anahtar başına açılır (yeni anahtarlarda kapalı). Tur operatörü yöneticisi anahtarda arama profilini belirler (pencere, süreler, hedefler, sayfa boyutu, zaman bütçesi, istek hızı); en fazla işletmecinin tur operatörüne verdiği kadarı geçerlidir. Yetki yoksa: 403 ERR_OPEN_SEARCH_NOT_ALLOWED. API profilin sınırlarını alan bazında kontrol eder ve hata mesajında belirtir (4a.5); GET /v1/limits bunları makine tarafından okunabilir biçimde verir (4c).

4a.2 İstek

Alan
destinations
Zorunlu
hayır
Anlamı
GET /v1/destinations'daki gibi hedef kodları (4.5), birleşim; en fazla profilin izin verdiği kadar
Alan
hotels
Zorunlu
hayır
Anlamı
Anahtarın otel kodları; destinations ile birlikte değil. İkisi de yoksa: anahtarın tüm stoku – yalnızca profil tüm hedeflere izin veriyorsa
Alan
arrivalFrom, arrivalTo
Zorunlu
evet
Anlamı
Varış penceresi, her iki gün dahil; arrivalFrom ≥ referans tarihi, arrivalTo ≤ referans tarihi + 732
Alan
nightsMin, nightsMax
Zorunlu
evet
Anlamı
Konaklama süresi en az–en çok (1–30 ve profil içinde); aradaki her süre sayılır
Alan
occupancy
Zorunlu
evet
Anlamı
tek oda, /v1/price'taki gibi
Alan
boards
Zorunlu
hayır
Anlamı
Pansiyon tipi kodları, tam olarak sözleşmedeki gibi; yoksa = tümü
Alan
boardTypes
Zorunlu
hayır
Anlamı
EDF dışa aktarımındaki gibi pansiyon türü (AO, BB, HB, HB+, FB, FB+, SC, AI, AI+, XX); boards ile birlikte: ikisi de uymalı
Alan
minTotalCents, maxTotalCents
Zorunlu
hayır
Anlamı
Toplam fiyata filtre, sınırlar dahil
Alan
currency
Zorunlu
koşullu
Anlamı
Sözleşme para birimine filtre, dönüştürme yok. Aramanın otelleri birden fazla para biriminde fiyatlandırıyorsa zorunlu (422 ERR_CURRENCY_REQUIRED)
Alan
category
Zorunlu
hayır
Anlamı
resmi kategori (otel ana verilerindeki ulusal sınıflandırma): scheme stars veya keys (zorunlu), min/max 1 ile 5 arası yarım adımlarla sayı olarak seviye (örn. 3.5, eşdeğer 3.50), sınırlar dahil (yoksa: 1 veya 5). "4 Superior" 4 sayılır
Alan
regions
Zorunlu
hayır
Anlamı
otel ana verilerindeki (adres) bölgeler, bunlardan biri; girildiği gibi tam karşılaştırılır (büyük/küçük harf önemlidir); en fazla 50
Alan
geo
Zorunlu
hayır
Anlamı
yarıçap: lat, lon (sayı olarak ondalık derece, en fazla 6 ondalık basamak) ve radiusKm (0,001 ile 500 arası, en fazla 3 ondalık basamak). Küre üzerinde mesafe (haversine, dünya yarıçapı 6371 km), tam metreye yuvarlanır; sınır içeride sayılır. Tarih çizgisi üzerinden ve kutuplarda doğrudur
Alan
sort
Zorunlu
hayır
Anlamı
price (varsayılan: toplam fiyat), pricePerNight (gece başına fiyat, yuvarlamadan kesir olarak tam karşılaştırılır), hotel (otel kodu)
Alan
pageSize
Zorunlu
hayır
Anlamı
Sayfa başına sonuç, 1'den profile kadar; belirtilmezse 20 (profil daha azına izin veriyorsa daha az)
Alan
cursor
Zorunlu
hayır
Anlamı
Önceki sayfanın nextCursor'ı, değiştirilmeden (4a.4)

Diğer sorgu endpoint'lerinin aksine açık arama her bilinmeyen alanı reddeder (422 ERR_UNKNOWN_FIELD) – aksi halde bir filtredeki yazım hatası sonuç listesini sessizce değiştirirdi. now yoktur; sunucunun referans tarihi geçerlidir (1.3).

Otel ana verilerine göre filtreler (category, regions, geo; birlikte VE geçerlidir) hesaplamadan önce uygulanır: dışarıda kalan bir otel arama kapsamına ait değildir – ne hotelsInScope içinde sayılır ne de arama profilinin istek başına otel sınırına (ERR_SEARCH_TOO_BROAD) karşı; böylece tüm stok üzerinde bir yarıçap da mümkündür. Bir otelde bir filtrenin ihtiyaç duyduğu değer eksikse (resmi kategori, bölge veya koordinat yok) ve mevcut hiçbir değer onu dışlamıyorsa, arama kapsamında ERR_NO_CATEGORY, ERR_NO_REGION veya ERR_NO_GEO nedeniyle sayılır (bu sıradaki ilk eksik değer) – otel ana verilerindeki boşluklar görünür kalır. Değerleri tur operatörü otel ana verilerinde girer (konsol: otel → İçerik → Ana veri ve konum); bir değişiklik bir sonraki eşitlemeden sonra (saniyeler) geçerli olur ve stand değişir (4a.4).

4a.3 Yanıt

Alan
results[].hotel
Anlamı
Otel kodu; sort ölçütüne göre sıralı, eşitlikte otel koduna göre
Alan
results[].best
Anlamı
en iyi teklif: checkIn, checkOut, nights, room, board, boardType (EDF dışa aktarımındaki gibi pansiyon türü), currency, totalCents, perTravellerCents, availability (/v1/price'taki gibi, her zaman available: true)
Alan
results[].alternatives
Anlamı
Fiyatsız ön kontrol: dates = müsaitlik, satış kuralları, sezon ve konaklama düzenine göre en az bir teklifi olan tarihler (varış × süre), boards/boardTypes/rooms buna göre. Bir üst sınırdır: fiyat hesaplaması bunlardan bazılarını yine de dışlayabilir. Asla fiyat veya sonuç ifadesi olarak okumayın
Alan
coverage.complete
Anlamı
true: sayfa dolu veya liste bitti. false: zaman bütçesi sayfayı kısalttı (4a.4)
Alan
coverage.timeBudgetExhausted
Anlamı
Sayfa zaman bütçesi nedeniyle kısaltıldı (= complete: false)
Alan
coverage.standChanged
Anlamı
veriler önceki sayfadan bu yana değişti (4a.4)
Alan
coverage.hotelsInScope
Anlamı
Arama kapsamındaki oteller (hedefler veya hotels, anahtarın stoku, otel ana verilerine göre filtreler; filtrelenen değeri olmayan oteller de sayılır, 4a.2)
Alan
coverage.hotelsFeasible
Anlamı
bunlardan ön kontrole göre en az bir tarihi olanlar – sonuç sayısının üst sınırı („N otele kadar“), sonuç sayısı değil
Alan
coverage.hotelsPriced
Anlamı
bu sayfada tam olarak hesaplanan oteller (sunucunun paralel olarak kaç otel hesapladığına da bağlıdır; bu yüzden örneklerde açık bırakılmıştır)
Alan
coverage.undecided
Anlamı
Zaman bütçesi bittiğinde yeri henüz belirsiz olan oteller (complete durumunda 0)
Alan
coverage.reasons[]
Anlamı
Tek bir tarihi bile olmayan oteller, neden başına – tüm arama kapsamı için, her sayfada aynı. Hiçbir oda uygun bir pansiyon tipi sunmuyorsa: ERR_BOARD_NOT_OFFERED. /v1/search'te olduğu gibi sonra müsaitlik gelir: hiçbir odanın tek bir boş tarihi bile yoksa neden odur (ERR_NO_INVENTORY, ERR_NOT_AVAILABLE). Aksi halde /v1/price'ın ilk boş tarih için (oda, pansiyon tipi, varış, süre bu sırayla) verdiği yanıt geçerlidir, ör. ERR_OCCUPANCY_NOT_ALLOWED, ERR_NO_SECTION, ERR_STAY_LENGTH_NOT_ALLOWED veya fiyatı filtrenin dışındaysa ERR_OUTSIDE_PRICE_FILTER; ayrıca ERR_CURRENCY_NOT_AVAILABLE (otel başka veya bilinmeyen bir para biriminde fiyatlandırıyor), ERR_HOTEL_NOT_FOUND (müşteri grubu için teklif yok) ve ERR_NO_CATEGORY, ERR_NO_REGION, ERR_NO_GEO (otelde otel ana verilerine göre bir filtrenin değeri eksik, 4a.2)
Alan
coverage.priceReasons[]
Anlamı
Ancak bu sayfadaki tam hesaplamada elenen oteller, neden başına (ör. ERR_OUTSIDE_PRICE_FILTER)
Alan
coverage.roomErrors[]
Anlamı
Sözleşme hatası (7.5) olan odalar, kod başına – asla sessizce atlanmaz
Alan
stand
Anlamı
Bu sayfanın veri durumunun tanımlayıcısı (opak)
Alan
nextCursor
Anlamı
başka sonuçlar gelebildiği sürece dolu
Alan
warnings[]
Anlamı
Uyarılar, ör. veri durumunun değişmesi hakkında

Bir otelin en iyi teklifi: filtrenin tüm tarihler × odalar × pansiyon tipleri üzerinden ölçütün (toplam fiyat veya gece başına fiyat) minimumu, yalnızca rezerve edilebilir teklifler. Eşitlikte önce erken varış, sonra kısa süre, sonra sözleşmedeki sırayla oda ve pansiyon tipi kazanır. sort=hotel ile ölçüt toplam fiyattır.

### suche-offen
POST {{baseUrl}}/v1/search/open
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "destinations": ["PMI"],
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D6}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
  "pageSize": 2
}
{
  "results": [
    {"hotel": "TEST-HOTEL-BASE",
     "best": {"checkIn": "{{D0}}", "checkOut": "{{D2}}", "nights": 2, "room": "DZ", "board": "RO",
              "boardType": "AO", "currency": "EUR", "totalCents": 18000, "perTravellerCents": [18000, 0],
              "availability": {"configured": true, "available": true, "minFree": 5}},
     "alternatives": {"dates": 14, "boards": ["BB", "HB", "RO"], "boardTypes": ["AO", "BB", "HB"], "rooms": 1}},
    {"hotel": "TEST-HOTEL-DISCOUNT",
     "best": {"checkIn": "{{D0}}", "checkOut": "{{D2}}", "nights": 2, "room": "DZ", "board": "RO",
              "boardType": "AO", "currency": "EUR", "totalCents": 18000, "perTravellerCents": [18000, 0],
              "availability": {"configured": true, "available": true, "minFree": 5}},
     "alternatives": {"dates": 14, "boards": ["BB", "HB", "RO"], "boardTypes": ["AO", "BB", "HB"], "rooms": 1}}
  ],
  "coverage": {"complete": true, "timeBudgetExhausted": false, "standChanged": false,
               "hotelsInScope": 3, "hotelsFeasible": 3, "hotelsPriced": "{{*}}", "undecided": 0,
               "reasons": [], "priceReasons": [], "roomErrors": []},
  "stand": "{{*}}",
  "nextCursor": "{{*}}"
}

Sonraki sayfa, cursor eklenmiş aynı istektir:

### suche-offen-weiter
POST {{baseUrl}}/v1/search/open
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "destinations": ["PMI"],
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D6}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
  "pageSize": 2,
  "cursor": "{{offenCursor}}"
}
{
  "results": [
    {"hotel": "TEST-HOTEL-TWIN",
     "best": {"checkIn": "{{D0}}", "checkOut": "{{D2}}", "nights": 2, "room": "DZ", "board": "RO",
              "boardType": "AO", "currency": "EUR", "totalCents": 18000, "perTravellerCents": [18000, 0],
              "availability": {"configured": true, "available": true, "minFree": 5}},
     "alternatives": {"dates": 14, "boards": ["BB", "HB", "RO"], "boardTypes": ["AO", "BB", "HB"], "rooms": 1}}
  ],
  "coverage": {"complete": true, "timeBudgetExhausted": false, "standChanged": false,
               "hotelsInScope": 3, "hotelsFeasible": 3, "hotelsPriced": "{{*}}", "undecided": 0,
               "reasons": [], "priceReasons": [], "roomErrors": []},
  "stand": "{{*}}"
}

Tüm stok üzerinden, gece başına fiyata göre, yalnızca kahvaltı veya yarım pansiyon. Pencerede kontenjanı olmayan oteller coverage.reasons içinde neden olarak yer alır:

### suche-offen-je-nacht
POST {{baseUrl}}/v1/search/open
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D13}}",
  "nightsMin": 3,
  "nightsMax": 5,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
  "boardTypes": ["BB", "HB"],
  "sort": "pricePerNight",
  "pageSize": 3
}
{
  "results": [
    {"hotel": "TEST-HOTEL-ALTAKTION",
     "best": {"checkIn": "{{D0}}", "checkOut": "{{D5}}", "nights": 5, "room": "DZ", "board": "BB",
              "boardType": "BB", "currency": "EUR", "totalCents": 62000, "perTravellerCents": [52000, 10000],
              "availability": {"configured": true, "available": true, "minFree": 5}},
     "alternatives": {"dates": 42, "boards": ["BB", "HB"], "boardTypes": ["BB", "HB"], "rooms": 1}},
    {"hotel": "TEST-HOTEL-BASE",
     "best": {"checkIn": "{{D0}}", "checkOut": "{{D5}}", "nights": 5, "room": "DZ", "board": "BB",
              "boardType": "BB", "currency": "EUR", "totalCents": 62000, "perTravellerCents": [52000, 10000],
              "availability": {"configured": true, "available": true, "minFree": 4}},
     "alternatives": {"dates": 42, "boards": ["BB", "HB"], "boardTypes": ["BB", "HB"], "rooms": 1}},
    {"hotel": "TEST-HOTEL-DISCOUNT",
     "best": {"checkIn": "{{D0}}", "checkOut": "{{D5}}", "nights": 5, "room": "DZ", "board": "BB",
              "boardType": "BB", "currency": "EUR", "totalCents": 62000, "perTravellerCents": [52000, 10000],
              "availability": {"configured": true, "available": true, "minFree": 5}},
     "alternatives": {"dates": 42, "boards": ["BB", "HB"], "boardTypes": ["BB", "HB"], "rooms": 1}}
  ],
  "coverage": {"complete": true, "timeBudgetExhausted": false, "standChanged": false,
               "hotelsInScope": 19, "hotelsFeasible": 13, "hotelsPriced": "{{*}}", "undecided": 0,
               "reasons": [{"reason": "ERR_NO_INVENTORY", "count": 6}], "priceReasons": [], "roomErrors": []},
  "stand": "{{*}}",
  "nextCursor": "{{*}}"
}

Palma ve Menorca genelinde otel ana verilerine göre filtrelerle: en az 3,5 yıldız, Mallorca bölgesi, Palma çevresinde 25 km. Alcúdia'daki 3 yıldızlı otel dışarıda kalır, ana verileri girilmemiş Menorca'daki iki otel neden olarak görünür:

### suche-offen-stamm
POST {{baseUrl}}/v1/search/open
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "destinations": ["PMI", "MAH"],
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D6}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
  "category": {"scheme": "stars", "min": 3.5},
  "regions": ["Mallorca"],
  "geo": {"lat": 39.57, "lon": 2.65, "radiusKm": 25}
}
{
  "results": [
    {"hotel": "TEST-HOTEL-BASE",
     "best": {"checkIn": "{{D0}}", "checkOut": "{{D2}}", "nights": 2, "room": "DZ", "board": "RO",
              "boardType": "AO", "currency": "EUR", "totalCents": 18000, "perTravellerCents": [18000, 0],
              "availability": {"configured": true, "available": true, "minFree": 5}},
     "alternatives": {"dates": 14, "boards": ["BB", "HB", "RO"], "boardTypes": ["AO", "BB", "HB"], "rooms": 1}},
    {"hotel": "TEST-HOTEL-DISCOUNT",
     "best": {"checkIn": "{{D0}}", "checkOut": "{{D2}}", "nights": 2, "room": "DZ", "board": "RO",
              "boardType": "AO", "currency": "EUR", "totalCents": 18000, "perTravellerCents": [18000, 0],
              "availability": {"configured": true, "available": true, "minFree": 5}},
     "alternatives": {"dates": 14, "boards": ["BB", "HB", "RO"], "boardTypes": ["AO", "BB", "HB"], "rooms": 1}}
  ],
  "coverage": {"complete": true, "timeBudgetExhausted": false, "standChanged": false,
               "hotelsInScope": 4, "hotelsFeasible": 2, "hotelsPriced": "{{*}}", "undecided": 0,
               "reasons": [{"reason": "ERR_NO_CATEGORY", "count": 2}], "priceReasons": [], "roomErrors": []},
  "stand": "{{*}}"
}

4a.4 Sayfalar, cursor, veri durumu, zaman bütçesi

  • Sayfalar: nextCursor sonraki sayfaya götürür: aynı istek artı "cursor": "<nextCursor>"; pageSize değişebilir. Sonraki sayfa son sonucun arkasından devam eder (ölçüt, sonra otel kodu) – tüm sayfalar birlikte tek bir sıralı listedir. Son sayfada nextCursor yoktur; o sayfa boş da olabilir. sort=price ve pricePerNight ile sonraki sayfa, önceki sayfaların otellerini yeniden hesaplamaz: listenin derinlerinde de bir sayfa yaklaşık ilk sayfa kadar sürer.
  • Cursor: opaktır, isteğe ve anahtara bağlıdır, 15 dakika geçerlidir. Farklı istek (pageSize dışında herhangi bir alan) veya farklı anahtar: 422 ERR_CURSOR_MISMATCH. Okunamayan, süresi dolmuş veya sabit cursor anahtarı olmadan sunucu yeniden başlatıldıktan sonra: 422 ERR_BAD_CURSOR – o zaman cursor olmadan yeniden başlayın. /v1/search'ten gelen bir cursor burada geçerli değildir.
  • Veri durumu: stand, tur operatörü sözleşmeleri, promosyonları veya tahsisleri yayınladığında ya da otel ana verilerinde bir otelin bölgesini, koordinatlarını veya resmi kategorisini değiştirdiğinde ve gece yarısı (referans tarihi) değişir; rezervasyonlarda veya diğer içeriklerde (metinler, görseller) değişmez. Veri durumu önceki sayfadan bu yana değiştiyse sonraki sayfa aynı yerden yeni durumda hesaplamaya devam eder; coverage.standChanged: true ve warnings içinde bir uyarıyla. O zaman (iki sayfa arasındaki başkalarının rezervasyonlarında da olduğu gibi) bir otel iki kez görünebilir veya eksik olabilir: hotel'e göre tekilleştirin. Her sayfanın fiyatları kendi veri durumu için geçerlidir; rezervasyonda fiyatı priceCheck güvenceye alır.
  • Zaman bütçesi: Her sayfanın bir zaman bütçesi vardır (arama profili, ör. 150 ms), isteğin gelişinden itibaren sayılır. Bütçe tükendiğinde ve sayfada zaten bir sonuç varsa başka otel hesaplanmaz; yeri zaten kesinleşmiş sonuçlar yine de eklenir. O zaman: coverage.complete: false, timeBudgetExhausted: true, undecided > 0 ve kalanı için nextCursor. Gösterilen sonuçlar yine de kesin ve kanıtlanmış sıradadır – sayfalamaya devam edin. Bir sayfa, hâlâ bir sonuç varken asla sonuçsuz bitmez. Kısaltılmış sayfalar bir hata değildir. Özellikle çok büyük aramalarda (en büyük hedefte geniş konaklama süresi aralığına sahip uzun bir varış penceresi), aynı tur operatörünün birden fazla araması aynı anda çalışırken ortaya çıkar. Tam sayfalara ihtiyacınız varsa daha küçük pencereler veya aralıklar sorgulayın ya da aramaları paralel değil art arda yapın.
  • Yük: Açık arama, tur operatörünün arama yerlerini ve hesaplama süresini /v1/search ile paylaşır (4.4), ancak arama yerlerinin en fazla yarısını kullanır (8'de 4; tur operatörünün tüm anahtarları ve iki uç nokta birlikte) – geri kalanı /v1/search için boş kalır. Ayrıca anahtar başına arama profilinin istek hızı ve eşzamanlı aramaları geçerlidir (429 ERR_RATE_LIMITED veya ERR_SEARCH_BUSY, Retry-After ile); 429 ERR_SEARCH_BUSY ile reddedilen bir arama istek hızından düşmez. Bir arama 10 sn'den uzun hesaplarsa 503 ERR_SEARCH_TIMEOUT ile biter, asla kısmi bir sayfayla değil.
  • Kanıtlanamıyor: Arama bir istek için fiyatı, sıralamayı veya müsaitliği kanıtlayamazsa (isteğin değil, sunucunun bir hatası), muhtemelen yanlış bir sayfa yerine 500 ERR_INTERNAL ile yanıt verir.

4a.5 Açık aramanın hataları

Kontroller bu sırayla çalışır; ilk bulgu bildirilir: anahtar → yetki → body ve bilinmeyen alanlar → alan biçimi → API sınırları (1.3) → arama profilinin sınırları → hedefler, oteller, pansiyon tipi, para birimi → cursor → istek hızı ve arama yerleri → hesaplama. Reddedilen bir istek arama yeri işgal etmez ve arama olarak sayılmaz. Kodlar hata kataloğundadır (7.2, 7.3).

### suche-offen-ohne-recht
POST {{baseUrl}}/v1/search/open
Content-Type: application/json
X-Api-Key: {{poolKey}}

{
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D6}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{"errorCode": "ERR_OPEN_SEARCH_NOT_ALLOWED",
 "message": "dieser API-Key hat kein Recht fuer die offene Suche — der Veranstalter-Admin schaltet es frei (Suchprofil des Keys)"}
### suche-offen-fenster-zu-breit
POST {{baseUrl}}/v1/search/open
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D30}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{"errorCode": "ERR_WINDOW_TOO_WIDE", "message": "arrivalFrom..arrivalTo: 31 Tage, erlaubt hoechstens 14 (Suchprofil des Keys)"}

Müşteri grubunun anahtarı yalnızca bir hedefte arama yapabilir:

### suche-offen-ziel-nicht-erlaubt
POST {{baseUrl}}/v1/search/open
Content-Type: application/json
X-Api-Key: {{partnerKey}}

{
  "destinations": ["PMI"],
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D6}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}
{"errorCode": "ERR_DESTINATION_NOT_ALLOWED", "message": "destinations[0]: Ziel ausserhalb der erlaubten Ziele dieses Keys (Suchprofil des Keys)"}

Yanlış değerli bir filtre alanı belirtir:

### suche-offen-filter-kaputt
POST {{baseUrl}}/v1/search/open
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D6}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
  "geo": {"lat": 39.57, "lon": 2.65, "radiusKm": 600}
}
{"errorCode": "ERR_BAD_FILTER", "message": "geo.radiusKm: 600 ausserhalb 0.001..500"}

4b. Tarih matrisi: POST /v1/search/open/dates (BA)

4b.1 Ne için

Bir otelin „tarihler ve fiyatlar“ı: tek bir otel ve açık aramayla aynı pencere için her tarihte (varış × süre) rezerve edilebilir en ucuz teklif ya da neden teklif olmadığı. Yetki ve sınırlar açık aramadaki aynı arama profilinden gelir (4a.1); profilin istek hızı ve eşzamanlı aramaları iki endpoint için birlikte geçerlidir.

  • Fiyat kesindir: teklif içeren her hücre, aynı otel, oda, pansiyon tipi, giriş ve çıkış ve aynı konaklama düzeniyle /v1/price ile bit düzeyinde aynıdır.
  • Açık aramayla uyumludur: aynı filtre ve aynı stand ile 4a'daki best, en küçük ölçüte (toplam fiyat veya gecelik fiyat) sahip ilk hücredir – aynı eşitlik kuralı (daha erken varış, daha kısa süre, sözleşme sırasına göre oda ve pansiyon tipi). perBoard ile best, en küçük ölçüte sahip ilk tarihte kendi pansiyon tipinin hücresidir.

4b.2 İstek

Alan
hotel
Zorunlu
evet
Anlamı
Anahtarın otel kodu (ör. 4a'dan results[].hotel)
Alan
arrivalFrom, arrivalTo
Zorunlu
evet
Anlamı
4a.2'deki gibi varış penceresi; en fazla matrixMaxWindowDays gün (arama profili)
Alan
nightsMin, nightsMax
Zorunlu
evet
Anlamı
4a.2'deki gibi süre aralığı (profilin izin verdiği süreler)
Alan
occupancy
Zorunlu
evet
Anlamı
bir oda, /v1/price gibi
Alan
boards, boardTypes
Zorunlu
hayır
Anlamı
4a.2'deki gibi; boards içindeki her kod otelde sunulmalıdır (422 ERR_BOARD_NOT_OFFERED)
Alan
rooms
Zorunlu
hayır
Anlamı
Otelin oda kodları; yoksa tümü (bilinmeyen: 404 ERR_ROOM_NOT_FOUND)
Alan
minTotalCents, maxTotalCents
Zorunlu
hayır
Anlamı
4a.2'deki gibi toplam fiyat filtresi
Alan
currency
Zorunlu
hayır
Anlamı
Otelin sözleşme para birimi; otel başka bir para biriminde veya geçerli bir para birimi olmadan fiyatlandırıyorsa: 422 ERR_CURRENCY_NOT_AVAILABLE
Alan
perBoard
Zorunlu
hayır
Anlamı
true: her tarih için pansiyon tipi başına bir hücre (varsayılan false: tarih başına bir hücre)
Alan
category, regions, geo
Zorunlu
hayır
Anlamı
4a.2'deki gibi otel ana verilerine göre filtreler, aynı etki: otel dışarıda kalırsa arama alanına ait değildir ve matriste hiç hücre yoktur (cells boş); değer eksikse her hücre ERR_NO_CATEGORY, ERR_NO_REGION veya ERR_NO_GEO ile none olur

Hücreler = varış günleri × süreler (perBoard ile × pansiyon tipleri), en fazla matrixMaxCells (arama profili, aksi halde 422 ERR_SEARCH_TOO_BROAD). Cursor yoktur. Her bilinmeyen alan: 422 ERR_UNKNOWN_FIELD. Kontrol sırası açık aramanınkidir (4a.5), cursor olmadan.

4b.3 Yanıt

Alan
hotel, currency
Anlamı
Otel ve sözleşme para birimi
Alan
stand
Anlamı
4a'daki gibi veri durumu (aynı stand = aynı veri)
Alan
boards
Anlamı
yalnızca perBoard ile: tarih başına pansiyon tipleri sözleşme sırasıyla (odalar üzerinden ilk geçiş, önce RO, /v1/prices gibi), hücrelerin sırasıyla
Alan
cells[]
Anlamı
checkIn, nights sırasıyla boşluksuz (perBoard ile ardından pansiyon tipi); her hücrede checkIn, checkOut, nights, status. Yalnızca otel, otel ana verilerine göre filtrelerin dışında kalırsa boştur
Alan
cells[].status = "offer"
Anlamı
Teklif: room, board, boardType, totalCents, perTravellerCents, availability (/v1/price gibi, her zaman available: true) – tarihin en ucuz odası/pansiyon tipi, eşitlikte sözleşme sırasına göre
Alan
cells[].status = "none"
Anlamı
teklif yok, neden reason içinde: kodlar /v1/price ile aynı, sıra açık aramadaki gibi (4a) – önce müsaitlik (ERR_NO_INVENTORY, ERR_NOT_AVAILABLE), sonra satış kuralları, konaklama düzeni, sezon, fiyat filtresi (ERR_OUTSIDE_PRICE_FILTER) veya bir sözleşme hatası; otel ana verilerine göre filtrelerle ERR_NO_CATEGORY, ERR_NO_REGION, ERR_NO_GEO (otelde değer eksik, her hücre)
Alan
cells[].status = "unchecked"
Anlamı
zaman bütçesi dolduğu için kontrol edilmedi – asla „teklif yok“ olarak okunmamalı
Alan
coverage
Anlamı
complete (hiçbir hücre unchecked değil), timeBudgetExhausted, cells = offers + none + unchecked, roomErrors[] (kod başına sözleşme hatalı odalar, 7.5)
Alan
warnings[]
Anlamı
Uyarılar, ör. kontrol edilmemiş hücreler hakkında

Hücreler matris sırasıyla bloklar halinde hesaplanır; profilin zaman bütçesi dolduğunda kalan hücreler unchecked kalır (ilk blok her zaman kontrol edilir). O zaman pencereyi veya süreleri daraltıp yeniden sorun. Matris 10 sn'den uzun hesaplarsa: 503 ERR_SEARCH_TIMEOUT; kanıtlanamazsa: 500 ERR_INTERNAL (4a.4'teki gibi).

### suche-termine
POST {{baseUrl}}/v1/search/open/dates
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D1}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
  "maxTotalCents": 20000
}
{
  "hotel": "TEST-HOTEL-BASE",
  "currency": "EUR",
  "stand": "{{*}}",
  "cells": [
    {"checkIn": "{{D0}}", "checkOut": "{{D2}}", "nights": 2, "status": "offer", "room": "DZ", "board": "RO",
     "boardType": "AO", "totalCents": 18000, "perTravellerCents": [18000, 0],
     "availability": {"configured": true, "available": true, "minFree": 5}},
    {"checkIn": "{{D0}}", "checkOut": "{{D3}}", "nights": 3, "status": "none", "reason": "ERR_OUTSIDE_PRICE_FILTER"},
    {"checkIn": "{{D1}}", "checkOut": "{{D3}}", "nights": 2, "status": "offer", "room": "DZ", "board": "RO",
     "boardType": "AO", "totalCents": 18000, "perTravellerCents": [18000, 0],
     "availability": {"configured": true, "available": true, "minFree": 5}},
    {"checkIn": "{{D1}}", "checkOut": "{{D4}}", "nights": 3, "status": "none", "reason": "ERR_OUTSIDE_PRICE_FILTER"}
  ],
  "coverage": {"complete": true, "timeBudgetExhausted": false, "cells": 4, "offers": 2, "none": 2, "unchecked": 0,
               "roomErrors": []}
}

Pansiyon tipi başına, yalnızca kahvaltı ve yarım pansiyon:

### suche-termine-je-verpflegung
POST {{baseUrl}}/v1/search/open/dates
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D0}}",
  "nightsMin": 2,
  "nightsMax": 3,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
  "boards": ["BB", "HB"],
  "perBoard": true
}
{
  "hotel": "TEST-HOTEL-BASE",
  "currency": "EUR",
  "stand": "{{*}}",
  "boards": ["BB", "HB"],
  "cells": [
    {"checkIn": "{{D0}}", "checkOut": "{{D2}}", "nights": 2, "status": "offer", "room": "DZ", "board": "BB",
     "boardType": "BB", "totalCents": 26000, "perTravellerCents": [22000, 4000],
     "availability": {"configured": true, "available": true, "minFree": 5}},
    {"checkIn": "{{D0}}", "checkOut": "{{D2}}", "nights": 2, "status": "offer", "room": "DZ", "board": "HB",
     "boardType": "HB", "totalCents": 32000, "perTravellerCents": [25000, 7000],
     "availability": {"configured": true, "available": true, "minFree": 5}},
    {"checkIn": "{{D0}}", "checkOut": "{{D3}}", "nights": 3, "status": "offer", "room": "DZ", "board": "BB",
     "boardType": "BB", "totalCents": 38000, "perTravellerCents": [32000, 6000],
     "availability": {"configured": true, "available": true, "minFree": 5}},
    {"checkIn": "{{D0}}", "checkOut": "{{D3}}", "nights": 3, "status": "offer", "room": "DZ", "board": "HB",
     "boardType": "HB", "totalCents": 47000, "perTravellerCents": [36500, 10500],
     "availability": {"configured": true, "available": true, "minFree": 5}}
  ],
  "coverage": {"complete": true, "timeBudgetExhausted": false, "cells": 4, "offers": 4, "none": 0, "unchecked": 0,
               "roomErrors": []}
}

31 varış günü × 14 süre × 3 pansiyon tipi, profilin izin verdiğinden fazla hücredir:

### suche-termine-zu-viele-zellen
POST {{baseUrl}}/v1/search/open/dates
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "arrivalFrom": "{{D0}}",
  "arrivalTo": "{{D30}}",
  "nightsMin": 1,
  "nightsMax": 14,
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
  "perBoard": true
}
{"errorCode": "ERR_SEARCH_TOO_BROAD",
 "message": "Termin-Matrix: 1302 Zellen (Anreisen x Dauern x 3 Verpflegungen), erlaubt hoechstens 500 (Suchprofil des Keys)"}

4c. Anahtarın sınırları: GET /v1/limits

Kendi anahtarının geçerli sınırları; böylece bir web sitesi sınırları 422 ile yoklamak yerine tarih seçimini, süre seçimini ve hedefleri ayarlar. Parametre yok (herhangi biri: 422 ERR_UNKNOWN_FIELD), arama olarak sayılmaz.

Alan
rate
Anlamı
Anahtarın tüm isteklerinin hızı (4.4): perSecond, burst, scope (key = anahtar ve düğüm başına; testCircle = test anahtarlarının ortak havuzu, 11)
Alan
export.allowed
Anlamı
EDF teslimatı yetkisi (10)
Alan
content.allowed
Anlamı
İçerik API'sine (/v1/content/*) erişim: işletmeci tur operatörü için içerikleri açmış ve anahtar içerik yetkisine sahip – oradakiyle aynı kural; erişim yoksa 403 ERR_CONTENT_NOT_ALLOWED
Alan
openSearch.allowed
Anlamı
Açık arama ve tarih matrisi yetkisi; yetki yoksa yalnızca bu alan bulunur
Alan
openSearch.maxWindowDays, nightsMin, nightsMax, maxNightsSpan
Anlamı
Varış penceresi, izin verilen süreler ve istek başına süre aralığı (4a)
Alan
openSearch.maxDestinations, maxHotels, maxCandidates, maxPageSize
Anlamı
İstek başına hedefler ve oteller, arama kapsamındaki oteller, sayfa başına sonuçlar (4a)
Alan
openSearch.timeBudgetMs, rate, burst, concurrency
Anlamı
Sayfa veya matris başına zaman bütçesi, istek hızı ve eşzamanlı aramalar (iki endpoint birlikte)
Alan
openSearch.matrixMaxWindowDays, matrixMaxCells
Anlamı
Tarih matrisinin penceresi ve hücreleri (4b)
Alan
openSearch.allDestinations, destinations
Anlamı
tüm hedeflere izin var, aksi halde izin verilen hedef kodları – yalnızca anahtarın envanterinde oteli olanlar (GET /v1/destinations gibi)

Değerler tam olarak /v1/search/open ve /v1/search/open/dates uç noktalarının uyguladığı değerlerdir: işletmecinin tur operatörüne verdiği ile anahtarın profilinin en küçüğü. Bir sınırı hangi seviyenin belirlediği yanıtta yer almaz. Profildeki değişiklikler birkaç saniye sonra etkili olur.

### grenzen
GET {{baseUrl}}/v1/limits
X-Api-Key: {{apiKey}}
{
  "rate": {"perSecond": 200, "burst": 400, "scope": "key"},
  "export": {"allowed": true},
  "content": {"allowed": true},
  "openSearch": {"allowed": true, "maxWindowDays": 14, "nightsMin": 1, "nightsMax": 14, "maxNightsSpan": 3,
                 "maxDestinations": 3, "maxHotels": 20, "maxCandidates": 500, "maxPageSize": 20,
                 "timeBudgetMs": 150, "rate": 5, "burst": 20, "concurrency": 1,
                 "matrixMaxWindowDays": 31, "matrixMaxCells": 500, "allDestinations": true}
}

Açık arama yetkisi olmayan bir anahtar:

### grenzen-ohne-recht
GET {{baseUrl}}/v1/limits
X-Api-Key: {{poolKey}}
{
  "rate": {"perSecond": 200, "burst": 400, "scope": "key"},
  "export": {"allowed": false},
  "content": {"allowed": false},
  "openSearch": {"allowed": false}
}

5. Rezervasyon (B) ve rezervasyon bilgisi

5.1 POST /v1/book

Alan
hotel, room
Zorunlu
evet
Anlamı
Otel ve oda kodu (rezervasyon pansiyon tipinden bağımsızdır; pansiyon tipi priceCheck içinde yer alır). room eksikse: 400 ERR_INVALID_BUCKET, priceCheck ile ve olmadan
Alan
checkIn, checkOut
Zorunlu
evet
Anlamı
Konaklama (Bölüm 1.3)
Alan
quantity
Zorunlu
evet
Anlamı
Oda sayısı, 1–1.000.000 (400 ERR_QUANTITY_INVALID)
Alan
idemKey
Zorunlu
evet
Anlamı
işlemin kendi benzersiz anahtarı (Bölüm 9), en fazla 128 karakter; eksikse: 400 ERR_INVALID_IDEM_KEY
Alan
reference
Zorunlu
hayır
Anlamı
kendi rezervasyon referansı, en fazla 128 karakter; rezervasyon daha sonra bununla okunabilir
Alan
leadPaxName
Zorunlu
hayır
Anlamı
Ana yolcunun adı, en fazla 255 karakter
Alan
metadata
Zorunlu
hayır
Anlamı
serbest JSON nesnesi, saklanır ve /v1/booking'de geri döndürülür, değerlendirilmez. metadata.correlationId (64 karaktere kadar) korelasyon kimliği olarak devralınır.
Alan
priceCheck
Zorunlu
hayır, önerilir
Anlamı
Satıştan önce fiyat kontrolü, aşağıya bakın

Çok uzun metinleri (idemKey, reference, leadPaxName, metadata.correlationId) API her satıştan önce 422 ERR_VALIDATION ile reddeder; message alanı, uzunluğu ve sınırı belirtir. Asla kısaltılmaz.

priceCheck: board, occupancy.travellers[], expectedCents (müşterinin gördüğü fiyat), tolerancePercent (% olarak izin verilen sapma, ≥ 0), isteğe bağlı currency ve now. TourAPI fiyatı /v1/price gibi yeniden hesaplar; tolerans değerinden fazla saparsa: message içinde güncel fiyatla 409 ERR_PRICE_DRIFT, hiçbir şey rezerve edilmez. priceCheck ile TourAPI kontrol edilen fiyatı rezervasyonda saklar (/v1/booking içinde totalCents, currency, board). priceCheck olmadan rezervasyon, fiyatsız saf bir kontenjan satışıdır (totalCents: null). Bu durumda da /v1/book yalnızca /v1/price'ın bu anahtar için tanıdığı otelleri satar: Geçerli sözleşme olmadan otel yoktur (404 ERR_HOTEL_NOT_FOUND), kontenjan tanımlı olsa bile.

Yanıt: booked, alreadyBooked (true = tekrar, yeni bir şey satılmadı), reference (bizim rezervasyon referansımız TA-…), correlationId. correlationId, gönderilen metadata.correlationId'dir; yoksa TourAPI bir UUID atar ve bunu kayıtta saklar. Bir tekrar (alreadyBooked=true) her zaman ilk çağrının saklanan correlationId değerini belirtir – tekrar hiç göndermese veya farklı bir tane gönderse bile.

### buchen
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0001",
  "reference": "KUNDE-4711",
  "leadPaxName": "Erika Muster",
  "metadata": {"correlationId": "kette-0815", "vermittler": "Filiale 12"},
  "priceCheck": {
    "board": "RO",
    "currency": "EUR",
    "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
    "expectedCents": 18000,
    "tolerancePercent": 0
  }
}
{"booked": true, "alreadyBooked": false, "reference": "{{buchungsRef}}", "correlationId": "kette-0815"}

5.2 Tekrarlamak güvenlidir

Aynı isteği aynı idemKey ile bir kez daha göndermek – örneğin bir ağ hatasından sonra – yeniden satış yapmaz, mevcut rezervasyonu onaylar (alreadyBooked: true, aynı referans).

### buchen-wiederholt
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0001",
  "reference": "KUNDE-4711",
  "leadPaxName": "Erika Muster",
  "metadata": {"correlationId": "kette-0815", "vermittler": "Filiale 12"},
  "priceCheck": {
    "board": "RO",
    "currency": "EUR",
    "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
    "expectedCents": 18000,
    "tolerancePercent": 0
  }
}
{"booked": true, "alreadyBooked": true, "reference": "{{buchungsRef}}", "correlationId": "kette-0815"}

Farklı rezervasyon verileriyle (otel, oda, konaklama, miktar) aynı idemKey çağıranın hatasıdır:

### buchen-anderer-vorgang
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 2,
  "idemKey": "BEISPIEL-0001"
}
{"errorCode": "ERR_IDEMPOTENCY_MISMATCH", "message": "idemKey bereits mit anderen Buchungsdaten (hotel, room, checkIn, checkOut, quantity) vergeben"}

5.3 Bir rezervasyon neden başarısız olur

Satış bir gecede başarısız olursa message etkilenen ilk geceyi belirtir. Hiçbir şey rezerve edilmez (ya tüm geceler ya hiçbiri).

Kod
ERR_SOLD_OUT
Durum
422
Anlamı
Gece dolu
Kod
ERR_STOP_SALE
Durum
422
Anlamı
Tur operatörü satışı durdurmuş
Kod
ERR_INVENTORY_CLOSED
Durum
422
Anlamı
Gece kapalı veya yalnızca talep üzerine
Kod
ERR_NO_INVENTORY
Durum
422
Anlamı
bir gece için kapasite tanımlı değil
Kod
ERR_GROUP_LIMIT
Durum
422
Anlamı
müşteri grubunun tahsisi tükenmiş (veya oda ve gece için mevcut değil)
Kod
ERR_HOTEL_NOT_FOUND
Durum
404
Anlamı
Otel bu anahtar için yok (ayrıca: geçerli sözleşme yok, priceCheck ile ve olmadan; teklifi olmayan müşteri grubu, 1.1)
Kod
ERR_PRICE_DRIFT
Durum
409
Anlamı
Fiyat priceCheck'ten sapıyor
Kod
ERR_STAY_LENGTH_NOT_ALLOWED, ERR_ARRIVAL_DAY_NOT_ALLOWED, ERR_TRAVEL_DATES_NOT_ALLOWED, ERR_BOARD_NOT_ALLOWED, ERR_LEAD_TIME_NOT_ALLOWED
Durum
422
Anlamı
odanın bir satış kuralı konaklamayı hariç tutuyor (3.4); hiçbir şey rezerve edilmez
Kod
ERR_BOARD_NOT_AVAILABLE
Durum
422
Anlamı
priceCheck'teki pansiyon tipi bu yolcu grubuna satılmıyor (3.5); hiçbir şey rezerve edilmez
### buchen-preis-geaendert
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0002",
  "priceCheck": {
    "board": "RO",
    "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
    "expectedCents": 17000,
    "tolerancePercent": 2
  }
}
{"errorCode": "ERR_PRICE_DRIFT", "message": "Preis hat sich geaendert: aktuell 18000 Cent, erwartet 17000 Cent"}
### buchen-ausgebucht
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-SOLD",
  "room": "DZ",
  "checkIn": "{{D13}}",
  "checkOut": "{{D14}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0003"
}
{"errorCode": "ERR_SOLD_OUT", "message": "Nacht {{D13}} ausgebucht"}
### buchen-stop-sale
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-STOP",
  "room": "DZ",
  "checkIn": "{{D11}}",
  "checkOut": "{{D12}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0004"
}
{"errorCode": "ERR_STOP_SALE", "message": "Nacht {{D11}} stop-sale"}

Tahsisli müşteri grubuna sahip bir anahtar, serbest satıştaki gecelerde de kendi grubunun tahsisine karşı rezervasyon yapar. Burada TEST-PARTNER-BASE'e o gece için 5 tahsis edilmiştir:

### buchen-zuteilung-erschoepft
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{partnerKey}}

{
  "hotel": "TEST-HOTEL-FREESALE",
  "room": "DZ",
  "checkIn": "{{D20}}",
  "checkOut": "{{D21}}",
  "quantity": 6,
  "idemKey": "BEISPIEL-0005"
}
{"errorCode": "ERR_GROUP_LIMIT", "message": "Gruppen-Kontingent fuer {{D20}} erschoepft"}

Bir fiyat grubu, grubu olmayan bir anahtar gibi genel stoktan rezervasyon yapar:

### buchen-preisgruppe
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{rabattKey}}

{
  "hotel": "TEST-HOTEL-DISCOUNT",
  "room": "DZ",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0006"
}
{"booked": true, "alreadyBooked": false, "reference": "{{*}}", "correlationId": "{{*}}"}

5.4 GET /v1/booking?ref=… – rezervasyon bilgisi

ref bizim referansımız (TA-…) veya rezervasyondaki kendi reference değerinizdir; bizim referansımız önceliklidir. Kendi reference değeriniz birden fazla görünür rezervasyonla eşleşirse (iptal edilmişler dahil): 409 ERR_REFERENCE_AMBIGUOUS, message eşleşen TA-… referanslarını belirtir (önce en yeni, en fazla 10) – o zaman rezervasyon yanıtındaki bizim referansımızla okuyun. Yanıt: reference, customerReference, hotel, room, group (yalnızca müşteri grubu rezervasyonlarında; grup kodu A-Z a-z 0-9 . _ - karakterlerinden, 1 ile 64 karakter), checkIn, checkOut, quantity, status (confirmed | released), bookedAt, updatedAt (son değişiklik, iptalde iptal zamanı; ikisi de UTC olarak RFC 3339, ör. 2026-09-26T08:15:03Z), metadata, totalCents, currency, board. Yolcuların kişisel verilerini API döndürmez.

Rezervasyon …rezervasyon bilgisinde
priceCheck iletotalCents = kontrol edilen fiyat, currency = sözleşme para birimi, board = priceCheck'teki pansiyon tipi
priceCheck olmadantotalCents: null, currency: "", board: "" (boş metinler, fiyat kaydedilmedi)
reference olmadancustomerReference yok
metadata olmadanmetadata yok
müşteri grubu olmayan anahtarlagroup yok

Görünürlük: Müşteri grubuna sahip bir anahtar yalnızca kendi grubunun rezervasyonlarını görür. Temel sözleşmedeki bir anahtar tur operatörünün tüm rezervasyonlarını görür, müşteri gruplarınınkiler dahil – ancak yalnızca kendi rezervasyonlarını iptal edebilir (Bölüm 6). Bilinmeyen veya görünür değil: 404 ERR_BOOKING_NOT_FOUND.

### buchung-lesen
GET {{baseUrl}}/v1/booking?ref={{buchungsRef}}
X-Api-Key: {{apiKey}}
{
  "reference": "{{buchungsRef}}",
  "customerReference": "KUNDE-4711",
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "status": "confirmed",
  "bookedAt": "{{*}}",
  "updatedAt": "{{*}}",
  "metadata": {"correlationId": "kette-0815", "vermittler": "Filiale 12"},
  "totalCents": 18000,
  "currency": "EUR",
  "board": "RO"
}

Kendi referansınız üzerinden:

### buchung-lesen-kundenreferenz
GET {{baseUrl}}/v1/booking?ref=KUNDE-4711
X-Api-Key: {{apiKey}}
{
  "reference": "{{buchungsRef}}",
  "customerReference": "KUNDE-4711",
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "status": "confirmed",
  "bookedAt": "{{*}}",
  "updatedAt": "{{*}}",
  "metadata": {"correlationId": "kette-0815", "vermittler": "Filiale 12"},
  "totalCents": 18000,
  "currency": "EUR",
  "board": "RO"
}

priceCheck, reference ve metadata olmayan bir rezervasyon – yanıt sunucu tarafından atanmış bir correlationId taşır, rezervasyon bilgisinde fiyat yoktur:

### buchen-ohne-preispruefung
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D20}}",
  "checkOut": "{{D21}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0010"
}
{"booked": true, "alreadyBooked": false, "reference": "{{ohnePreisRef}}", "correlationId": "{{*}}"}
### buchung-lesen-ohne-preispruefung
GET {{baseUrl}}/v1/booking?ref={{ohnePreisRef}}
X-Api-Key: {{apiKey}}
{
  "reference": "{{ohnePreisRef}}",
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D20}}",
  "checkOut": "{{D21}}",
  "quantity": 1,
  "status": "confirmed",
  "bookedAt": "{{*}}",
  "updatedAt": "{{*}}",
  "totalCents": null,
  "currency": "",
  "board": ""
}

Aynı kendi referansının ikinci bir rezervasyonda kullanılması onu belirsiz yapar:

### buchen-gleiche-kundenreferenz
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D22}}",
  "checkOut": "{{D23}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0011",
  "reference": "KUNDE-4711"
}
{"booked": true, "alreadyBooked": false, "reference": "{{zweiteRef}}", "correlationId": "{{*}}"}
### buchung-lesen-mehrdeutig
GET {{baseUrl}}/v1/booking?ref=KUNDE-4711
X-Api-Key: {{apiKey}}
{"errorCode": "ERR_REFERENCE_AMBIGUOUS", "message": "ref 'KUNDE-4711' passt zu mehreren Buchungen ({{zweiteRef}}, {{buchungsRef}}) — mit der TourAPI-Referenz (TA-…) lesen"}

6. İptal (S): POST /v1/cancel

Tek alan: rezervasyonun idemKey değeri (eksikse: 400 ERR_INVALID_IDEM_KEY; başka her alan: 422 ERR_UNKNOWN_FIELD). Tüm gecelerin kontenjanı iade edilir, durum released olur. Yalnızca aynı anahtar çevresiyle rezerve edilmiş olan iptal edilebilir: Müşteri grubuna sahip bir anahtar kendi grubunun rezervasyonlarını, temel sözleşmedeki bir anahtar temel sözleşmenin rezervasyonlarını iptal eder. Diğer her şey 404 ERR_BOOKING_NOT_FOUND'dur. İptal ücretlerini API hesaplamaz. Her iptal tur operatöründe zaman ve iptal eden anahtarın kimliğiyle (key_id) kaydedilir ve onun rezervasyon görünümünde gösterilir; bir tekrar (alreadyReleased) ikinci bir kayıt oluşturmaz.

Yanıt: released (true), alreadyReleased (true = zaten iptal edilmişti).

### stornieren
POST {{baseUrl}}/v1/cancel
Content-Type: application/json
X-Api-Key: {{apiKey}}

{"idemKey": "BEISPIEL-0001"}
{"released": true, "alreadyReleased": false}

Tekrarlamak güvenlidir:

### stornieren-wiederholt
POST {{baseUrl}}/v1/cancel
Content-Type: application/json
X-Api-Key: {{apiKey}}

{"idemKey": "BEISPIEL-0001"}
{"released": true, "alreadyReleased": true}

Rezervasyon status: released ile okunabilir kalır:

### buchung-lesen-storniert
GET {{baseUrl}}/v1/booking?ref={{buchungsRef}}
X-Api-Key: {{apiKey}}
{
  "reference": "{{buchungsRef}}",
  "customerReference": "KUNDE-4711",
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "status": "released",
  "bookedAt": "{{*}}",
  "updatedAt": "{{*}}",
  "metadata": {"correlationId": "kette-0815", "vermittler": "Filiale 12"},
  "totalCents": 18000,
  "currency": "EUR",
  "board": "RO"
}

İptal edilmiş bir idemKey tüketilmiştir. Yeni bir rezervasyon için yeni bir anahtar kullanın:

### buchen-nach-storno
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0001"
}
{"errorCode": "ERR_IDEM_KEY_RELEASED", "message": "idemKey gehoert zu einer stornierten Buchung — fuer einen neuen Verkauf einen neuen idemKey verwenden"}
### stornieren-unbekannt
POST {{baseUrl}}/v1/cancel
Content-Type: application/json
X-Api-Key: {{apiKey}}

{"idemKey": "GIBT-ES-NICHT"}
{"errorCode": "ERR_BOOKING_NOT_FOUND", "message": "keine Buchung mit diesem idemKey"}

7. Hata kataloğu

Sütun „Çağıran“: İsteği düzeltin = tekrarlamayın, hata istektedir. Tekrarlayın = aynı isteği daha sonra bir kez daha gönderin (Retry-After varsa en erken o kadar saniye sonra). Bildirin = tur operatörüne/işletmeciye bildirin, tekrarlamak işe yaramaz. Bir kod birden fazla endpoint'te görülebilir; durum ve anlam aynı kalır.

7.1 Erişim ve iletim

Kod
ERR_UNAUTHORIZED
Durum
401
Anlamı
Anahtar eksik, bilinmiyor veya iptal edilmiş
Çağıran
Anahtarı kontrol edin, tekrarlamayın (tekrarlar geciktirilir, Bölüm 8)
Kod
ERR_TENANT_SUSPENDED
Durum
403
Anlamı
Anahtar geçerli, tur operatörü askıya alınmış
Çağıran
Tur operatörüne sorun, tekrarlamayın
Kod
ERR_KEY_GROUP_INACTIVE
Durum
403
Anlamı
Anahtarın müşteri grubu devre dışı
Çağıran
Tur operatörüne sorun
Kod
ERR_MODE_MISMATCH
Durum
403
Anlamı
X-TourAPI-Require-Mode anahtarınkinden farklı bir mod istiyor (ör. test ortamında canlı anahtar); hiçbir şey yürütülmedi (Bölüm 11.1)
Çağıran
Anahtarı değiştirin, tekrarlamayın
Kod
ERR_SCENARIO_NOT_ALLOWED
Durum
422
Anlamı
Canlı anahtarla, bilinmeyen bir senaryoyla veya etkisiz olduğu bir endpoint'te X-TourAPI-Sandbox-Scenario (Bölüm 11.3)
Çağıran
İsteği düzeltin
Kod
ERR_METHOD_NOT_ALLOWED
Durum
405
Anlamı
yanlış HTTP metodu
Çağıran
İsteği düzeltin
Kod
ERR_BAD_REQUEST
Durum
400
Anlamı
Body JSON değil, çok büyük (> 1 MiB), ref eksik, priceCheck.tolerancePercent negatif, priceCheck.currency 3 karakterden uzun (daha kısa veya bilinmeyen: 422 ERR_CURRENCY_NOT_AVAILABLE); Content API: since eksik, parametre birden fazla, lang 5'ten fazla veya yinelenen dil içeriyor
Çağıran
İsteği düzeltin
Kod
ERR_UNKNOWN_FIELD
Durum
422
Anlamı
occupancy içinde (sorgu) veya herhangi bir yerde (/v1/search/open, /v1/search/open/dates, /v1/book, /v1/cancel, /v1/export/edf/ack body'si) bilinmeyen alan; /v1/export/edf/* ve /v1/limits üzerinde bilinmeyen query parametresi
Çağıran
İsteği düzeltin
Kod
ERR_OPEN_SEARCH_NOT_ALLOWED
Durum
403
Anlamı
anahtarın açık arama ve tarih matrisi yetkisi yok (4a.1; yeni anahtarlarda kapalı; GET /v1/limits gösterir)
Çağıran
Tur operatörüne sorun
Kod
ERR_DESTINATION_NOT_ALLOWED
Durum
403
Anlamı
açık arama: hedef (destinations[i]) veya otel (hotels[i], tarih matrisi: hotel) arama profilinin izin verilen hedeflerinin dışında
Çağıran
Hedefi arama profilinden alın (GET /v1/limits)
### fehler-ohne-key
POST {{baseUrl}}/v1/price
Content-Type: application/json

{}
{"errorCode": "ERR_UNAUTHORIZED", "message": "fehlender/ungueltiger API-Key"}
### fehler-key-widerrufen
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{gesperrterKey}}

{}
{"errorCode": "ERR_UNAUTHORIZED", "message": "fehlender/ungueltiger API-Key"}
### fehler-methode
GET {{baseUrl}}/v1/price
X-Api-Key: {{apiKey}}
{"errorCode": "ERR_METHOD_NOT_ALLOWED", "message": "nur POST"}
### fehler-tippfehler-belegung
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}, {"alter": 8}]}
}
{"errorCode": "ERR_UNKNOWN_FIELD", "message": "unbekanntes Feld 'occupancy.travellers[1].alter' — abgelehnt (die Belegung bestimmt den Preis, hier wird nicht geraten)"}

7.2 İstek (alanlar ve sınırlar)

Kod
ERR_BAD_DATE
Durum
422
Anlamı
Tarih eksik veya JJJJ-MM-TT biçiminde değil (message alanı belirtir)
Çağıran
İsteği düzeltin
Kod
ERR_EMPTY_STAY
Durum
422
Anlamı
checkOut ≤ checkIn
Çağıran
İsteği düzeltin
Kod
ERR_STAY_TOO_LONG
Durum
422
Anlamı
30 geceden fazla
Çağıran
İsteği düzeltin
Kod
ERR_STAY_IN_PAST
Durum
422
Anlamı
Giriş referans tarihinden önce
Çağıran
İsteği düzeltin
Kod
ERR_STAY_TOO_FAR
Durum
422
Anlamı
Giriş referans tarihinden 732 günden fazla sonra
Çağıran
İsteği düzeltin
Kod
ERR_NO_TRAVELLERS
Durum
422
Anlamı
yolcu yok
Çağıran
İsteği düzeltin
Kod
ERR_TOO_MANY_TRAVELLERS
Durum
422
Anlamı
20'den fazla yolcu
Çağıran
İsteği düzeltin
Kod
ERR_INVALID_AGE
Durum
422
Anlamı
Yaş negatif veya 120'nin üzerinde
Çağıran
İsteği düzeltin
Kod
ERR_BOARD_MISSING
Durum
422
Anlamı
board eksik (/v1/price, /v1/search, priceCheck)
Çağıran
İsteği düzeltin
Kod
ERR_NOW_MISMATCH
Durum
422
Anlamı
priceCheck.now referans tarihi değil
Çağıran
Alanı çıkarın
Kod
ERR_VALIDATION
Durum
422
Anlamı
Metin çok uzun: idemKey, reference (128), leadPaxName (255), metadata.correlationId (64); message alanı ve sınırı belirtir
Çağıran
İsteği düzeltin
Kod
ERR_QUANTITY_INVALID
Durum
400
Anlamı
quantity 1–1.000.000 dışında
Çağıran
İsteği düzeltin
Kod
ERR_INVALID_IDEM_KEY
Durum
400
Anlamı
idemKey eksik (/v1/book, /v1/cancel)
Çağıran
İsteği düzeltin
Kod
ERR_INVALID_BUCKET
Durum
400
Anlamı
room eksik (/v1/book, priceCheck ile ve olmadan)
Çağıran
İsteği düzeltin
Kod
ERR_INVALID_STAY
Durum
400
Anlamı
Konaklama geçersiz (satışta koruma; API bunu önceden ERR_BAD_DATE/ERR_EMPTY_STAY ile kontrol eder)
Çağıran
İsteği düzeltin
Kod
ERR_BAD_PAGE_SIZE
Durum
422
Anlamı
pageSize 1–100 (/v1/search; açık arama: 1'den arama profiline kadar) veya 1–1000 (/v1/content/*) dışında
Çağıran
İsteği düzeltin
Kod
ERR_BAD_CURSOR
Durum
422
Anlamı
Cursor okunamıyor, değiştirilmiş veya süresi dolmuş (ör. sunucu yeniden başlatıldıktan sonra; açık arama: verilişinden 15 dakika sonra); Content API: cursor/since okunamıyor, değiştirilmiş veya başka bir anahtardan
Çağıran
cursor olmadan yeniden başlayın veya dizini yeniden alın
Kod
ERR_CURSOR_MISMATCH
Durum
422
Anlamı
Cursor başka bir isteğe veya başka bir anahtara ait (/v1/search, /v1/search/open)
Çağıran
İsteği düzeltin
Kod
ERR_UNKNOWN_DESTINATION
Durum
422
Anlamı
cursor olmadan /v1/search veya /v1/search/open: destination veya destinations[i] için bu anahtara ait otel yok (büyük/küçük harf önemlidir)
Çağıran
Kodu GET /v1/destinations'dan alın (4.5)
Kod
ERR_BAD_WINDOW
Durum
422
Anlamı
açık arama ve tarih matrisi: arrivalFrom/arrivalTo eksik veya arrivalTo, arrivalFrom'dan önce
Çağıran
İsteği düzeltin
Kod
ERR_BAD_NIGHTS
Durum
422
Anlamı
açık arama ve tarih matrisi: nightsMin/nightsMax eksik, < 1 veya nightsMin > nightsMax
Çağıran
İsteği düzeltin
Kod
ERR_BAD_TARGET
Durum
422
Anlamı
açık arama: destinations ve hotels birlikte, boş liste, boş kod veya arama profili yalnızca tekil hedeflere izin verdiği halde ikisi de yok; tarih matrisi: hotel eksik
Çağıran
İsteği düzeltin
Kod
ERR_BAD_SORT
Durum
422
Anlamı
açık arama: sort bilinmiyor (price, pricePerNight, hotel)
Çağıran
İsteği düzeltin
Kod
ERR_BAD_FILTER
Durum
422
Anlamı
açık arama ve tarih matrisi: boş boards/boardTypes/rooms listesi veya boş giriş, bilinmeyen pansiyon türü, fiyat filtresi negatif veya minTotalCents > maxTotalCents, currency üç büyük harfli bir kod değil; otel ana verilerine göre filtre biçimi dışında (category, regions, geo, 4a.2) – mesaj alanı belirtir
Çağıran
İsteği düzeltin
Kod
ERR_WINDOW_TOO_WIDE
Durum
422
Anlamı
açık arama veya tarih matrisi: varış penceresi arama profilinin izin verdiğinden geniş (maxWindowDays veya matrixMaxWindowDays, message sınırı belirtir)
Çağıran
pencereyi bölün veya daraltın
Kod
ERR_NIGHTS_NOT_ALLOWED
Durum
422
Anlamı
açık arama: süre izin verilen sürelerin dışında veya nightsMax - nightsMin + 1 aralığı çok geniş; tarih matrisi: süre izin verilen sürelerin dışında (message sınırı belirtir)
Çağıran
süreyi ayarlayın
Kod
ERR_SEARCH_TOO_BROAD
Durum
422
Anlamı
açık arama: istekte çok fazla destinations veya hotels ya da arama kapsamında çok fazla otel; tarih matrisi: matrixMaxCells değerinden fazla hücre (message sınırı belirtir)
Çağıran
daraltın
Kod
ERR_CURRENCY_REQUIRED
Durum
422
Anlamı
açık arama: arama kapsamındaki oteller birden fazla sözleşme para biriminde fiyatlandırıyor, currency eksik (message bunları belirtir)
Çağıran
currency belirtin
### fehler-aufenthalt-zu-lang
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D31}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
{"errorCode": "ERR_STAY_TOO_LONG", "message": "checkOut: Aufenthalt laenger als 30 Naechte"}
### fehler-anreise-vergangen
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "board": "RO",
  "checkIn": "{{gestern}}",
  "checkOut": "{{heute}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
{"errorCode": "ERR_STAY_IN_PAST", "message": "checkIn: {{gestern}} liegt vor dem Stichtag {{heute}} (Serverdatum)"}

Çok uzun metinler satıştan önce reddedilir, asla kısaltılmaz:

### fehler-text-zu-lang
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
{"errorCode": "ERR_VALIDATION", "message": "idemKey: zu lang (129 Zeichen, hoechstens 128)"}

Konaklama düzeni dışında yanlış yazılmış bir alan reddedilmez, ancak belirtilir – burada bu yüzden zorunlu alan eksik kalır ve yanıt her ikisini de söyler:

### fehler-tippfehler-feld
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "board": "RO",
  "checke": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
{"errorCode": "ERR_BAD_DATE", "message": "checkIn: fehlt (Pflichtfeld, JJJJ-MM-TT)",
 "warnings": ["unbekanntes Feld 'checke' — ignoriert (Tippfehler?)"]}

Başarılı bir yanıttaki uyarılar:

### hinweise-now-und-waehrung
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "EZ",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "now": "{{gestern}}",
  "currency": "USD",
  "occupancy": {"travellers": [{"age": 40}]}
}
{
  "room": "EZ",
  "currency": "EUR",
  "rounding": {"mode": "Commercial", "decimalPlaces": 2, "scope": "Person"},
  "totalCents": 12500,
  "perTravellerCents": [12500],
  "breakdown": [
    {"chargeType": "BaseCharge", "code": "Base", "traveller": 0, "night": 0, "amountExact": "70.00", "amountCents": 7000},
    {"chargeType": "BaseCharge", "code": "ExtraDay", "traveller": 0, "night": 1, "amountExact": "55.00", "amountCents": 5500}
  ],
  "availability": {"configured": true, "available": true, "minFree": 5},
  "warnings": [
    "now: '{{gestern}}' ignoriert — Stichtag ist das Serverdatum {{heute}}",
    "currency: angefragt 'USD', der Vertrag rechnet in 'EUR' (keine Umrechnung)"
  ]
}

7.3 Stok, fiyat, satış

Kod
ERR_HOTEL_NOT_FOUND
Durum
404
Anlamı
Otel bu anahtar için yok (ayrıca: başka tur operatörü, yayınlanmamış, müşteri grubunun özel fiyatı hesaplanamıyor – bkz. 1.1)
Çağıran
İsteği düzeltin; aksi halde bilinen bir otelde tur operatörünü bilgilendirin
Kod
ERR_ROOM_NOT_FOUND
Durum
404
Anlamı
Oda bu otelde yok (tarih matrisi: rooms[i])
Çağıran
İsteği düzeltin
Kod
ERR_BOOKING_NOT_FOUND
Durum
404
Anlamı
Rezervasyon bilinmiyor veya bu anahtar için görünür değil
Çağıran
Referansı/anahtarı kontrol edin
Kod
ERR_REFERENCE_AMBIGUOUS
Durum
409
Anlamı
kendi reference değeriniz birden fazla rezervasyonla eşleşiyor (/v1/booking); message TA-… referanslarını belirtir (test anahtarı: SB-…)
Çağıran
TourAPI referansıyla okuyun; kendi referanslarınızı benzersiz tutun
Kod
ERR_TENANT_NOT_FOUND
Durum
404
Anlamı
Tur operatörü (artık) aktif değil, yalnızca satış/iptal
Çağıran
Bildirin
Kod
ERR_BOARD_NOT_OFFERED
Durum
422
Anlamı
Pansiyon tipi sunulmuyor (kod tam olarak sözleşmedeki gibi; /v1/price, /v1/prices, priceCheck; açık arama: boards[i] arama kapsamındaki hiçbir otel tarafından sunulmuyor; tarih matrisi: boards[i] otelde yok veya hiçbir pansiyon tipi boards/boardTypes ile uyuşmuyor); aramada diagnostics.reasons veya coverage.reasons içinde neden
Çağıran
İsteği düzeltin
Kod
ERR_OCCUPANCY_NOT_ALLOWED
Durum
422
Anlamı
Konaklama düzeni hiçbir odaya (veya istenen odaya) uymuyor
Çağıran
farklı konaklama düzeni/farklı oda
Kod
ERR_STAY_LENGTH_NOT_ALLOWED
Durum
422
Anlamı
odanın bir satış kuralı farklı bir konaklama süresi gerektiriyor (3.4); aramada diagnostics.reasons içinde neden
Çağıran
Süreyi değiştirin
Kod
ERR_ARRIVAL_DAY_NOT_ALLOWED
Durum
422
Anlamı
bir satış kuralı farklı bir giriş/çıkış haftanın günü gerektiriyor (3.4)
Çağıran
Seyahat günlerini kaydırın
Kod
ERR_TRAVEL_DATES_NOT_ALLOWED
Durum
422
Anlamı
konaklama bir kuralın satış penceresi dışında (3.4)
Çağıran
farklı dönem
Kod
ERR_BOARD_NOT_ALLOWED
Durum
422
Anlamı
pansiyon tipi bu konaklama için satılmıyor (3.4)
Çağıran
farklı pansiyon tipi
Kod
ERR_LEAD_TIME_NOT_ALLOWED
Durum
422
Anlamı
giriş, bir satış kuralının release süresi içinde; referans tarihinden itibaren sayılır (3.4)
Çağıran
daha geç giriş
Kod
ERR_BOARD_NOT_AVAILABLE
Durum
422
Anlamı
pansiyon tipi bu yolcu grubuna satılmıyor, ek ücreti farklı bir bileşim gerektiriyor (3.5); /v1/prices içinde rooms[].errors[] kaydı, aramada diagnostics.reasons içinde neden
Çağıran
farklı pansiyon tipi/konaklama düzeni
Kod
ERR_ROOM_RESTRICTION_INVALID
Durum
422
Anlamı
sözleşmedeki bir satış kuralı değerlendirilemiyor
Çağıran
Bildirin
Kod
ERR_NO_SECTION
Durum
422
Anlamı
bir gece için fiyat yok (sezon sözleşmede yok); başka bir oda hesaplanıyorsa /v1/prices içinde rooms[].errors[] kaydı, room olmadan /v1/price'ta warnings içinde, aramada diagnostics.roomErrors içinde
Çağıran
farklı dönem
Kod
ERR_NO_PRICE
Durum
422
Anlamı
istek için rezerve edilebilir oda yok, daha kesin bir neden olmadan
Çağıran
farklı dönem/farklı konaklama düzeni
Kod
ERR_OCCUPANCY_NIGHT_UNCOVERED
Durum
422
Anlamı
Sözleşmenin konaklama düzeni kuralları bir geceyi kapsamıyor
Çağıran
Bildirin
Kod
ERR_OCCUPANCY_INCONSISTENT_MCA
Durum
422
Anlamı
Minimum doluluk konaklama içinde değişiyor (desteklenmiyor)
Çağıran
daha kısa dönem veya bildirin
Kod
ERR_OCCUPANCY_INCONSISTENT_CHILDREN
Durum
422
Anlamı
bir yolcu konaklama içinde bir kez çocuk, bir kez yetişkin (odanın çocuk yaş aralığı sezona göre değişiyor; desteklenmiyor)
Çağıran
daha kısa dönem veya bildirin
Kod
ERR_OCCUPANCY_INCONSISTENT_INFANTS
Durum
422
Anlamı
bir bebek konaklama içinde bir kez doluluğa sayılıyor, bir kez sayılmıyor (odanın konaklama düzeni kuralı sezona göre değişiyor) ve bir ek ücret veya indirim kişi sayısına bağlı; belirsiz
Çağıran
daha kısa dönem veya bildirin
Kod
ERR_CHILDREN_ORDER_MISSING
Durum
422
Anlamı
sözleşme önce en büyük mü yoksa en küçük çocuğun mu sayılacağını belirlemiyor ve bu çocuklar için bu bir fiyat farkı yaratıyor (sözleşme hatası)
Çağıran
Bildirin
Kod
ERR_INVALID_AMOUNT
Durum
422
Anlamı
sözleşmedeki bir tutar veya yüzde okunamıyor
Çağıran
Bildirin
Kod
ERR_AMOUNT_OVERFLOW
Durum
422
Anlamı
fiyat gösterilebilir sent aralığını aşıyor (sözleşme hatası)
Çağıran
Bildirin
Kod
ERR_CURRENCY_NOT_AVAILABLE
Durum
422
Anlamı
priceCheck.currency sözleşme para birimine uymuyor veya sözleşme para birimi bilinmiyor; açık aramada coverage.reasons içinde neden (otel istenenden başka veya bilinmeyen bir para biriminde fiyatlandırıyor); tarih matrisi: aynısı 422 olarak
Çağıran
İsteği düzeltin veya bildirin
Kod
ERR_PRICE_DRIFT
Durum
409
Anlamı
güncel fiyat priceCheck'ten sapıyor
Çağıran
yeni fiyatı gösterin, yeni expectedCents ile rezervasyon yapın
Kod
ERR_SOLD_OUT
Durum
422
Anlamı
Gece dolu
Çağıran
tekrarlamayın
Kod
ERR_STOP_SALE
Durum
422
Anlamı
Satış durdurma
Çağıran
tekrarlamayın
Kod
ERR_INVENTORY_CLOSED
Durum
422
Anlamı
Gece kapalı veya yalnızca talep üzerine
Çağıran
tekrarlamayın
Kod
ERR_NO_INVENTORY
Durum
422
Anlamı
en az bir gece için kapasite tanımlı değil (rezervasyon ve arama nedeni aynı)
Çağıran
tekrarlamayın
Kod
ERR_NOT_AVAILABLE
Durum
–
Anlamı
yalnızca aramada neden olarak: her gecenin kapasitesi var, ancak her biri açık değil (rezervasyon: ERR_SOLD_OUT, ERR_STOP_SALE, ERR_INVENTORY_CLOSED)
Çağıran
–
Kod
ERR_OUTSIDE_PRICE_FILTER
Durum
–
Anlamı
yalnızca neden olarak: açık aramada otelin her teklifi, tarih matrisinde hücrenin her teklifi minTotalCents/maxTotalCents dışında
Çağıran
–
Kod
ERR_NO_CATEGORY
Durum
–
Anlamı
yalnızca neden olarak (açık arama; tarih matrisinde otelin her hücresi): category filtresi, otelin otel ana verilerinde resmi kategorisi yok
Çağıran
Tur operatörü: kategoriyi girin
Kod
ERR_NO_REGION
Durum
–
Anlamı
yalnızca neden olarak (açık arama; tarih matrisinde otelin her hücresi): regions filtresi, otelin otel ana verilerinde bölgesi yok
Çağıran
Tur operatörü: bölgeyi girin
Kod
ERR_NO_GEO
Durum
–
Anlamı
yalnızca neden olarak (açık arama; tarih matrisinde otelin her hücresi): geo filtresi, otelin otel ana verilerinde koordinatı yok
Çağıran
Tur operatörü: koordinatları girin
Kod
ERR_GROUP_LIMIT
Durum
422
Anlamı
Müşteri grubunun tahsisi tükenmiş veya oda ve gece için mevcut değil
Çağıran
tekrarlamayın
Kod
ERR_IDEMPOTENCY_MISMATCH
Durum
409
Anlamı
idemKey zaten farklı rezervasyon verileriyle kullanılmış
Çağıran
Çağıranın hatası: benzersiz anahtarlar verin
Kod
ERR_IDEM_KEY_RELEASED
Durum
409
Anlamı
idemKey iptal edilmiş bir rezervasyona ait
Çağıran
yeni idemKey kullanın
Kod
ERR_SANDBOX_LIMIT
Durum
422
Anlamı
Test anahtarı: bu erişimin 5.000'den fazla açık test rezervasyonu var (Bölüm 11.2)
Çağıran
Test rezervasyonlarını iptal edin
### fehler-verpflegung-nicht-angeboten
POST {{baseUrl}}/v1/prices
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "boards": ["AI"],
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
{"errorCode": "ERR_BOARD_NOT_OFFERED", "message": "boards: Verpflegung 'AI' wird nicht angeboten"}

Aynısı /v1/price için de geçerlidir – odanın sunmadığı bir pansiyon tipi asla pansiyon tipi olmayan fiyattan hesaplanmaz:

### fehler-verpflegung-preis
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "board": "AI",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
{"errorCode": "ERR_BOARD_NOT_OFFERED", "message": "board: Verpflegung 'AI' wird nicht angeboten"}

7.4 Yük ve işletim

Kod
ERR_RATE_LIMITED
Durum
429
Anlamı
bu anahtardan çok fazla istek (Bölüm 8); açık arama ve tarih matrisi: arama profilinin istek hızı aşıldı (ikisi birlikte)
Çağıran
Retry-After sonrasında tekrarlayın
Kod
ERR_SEARCH_BUSY
Durum
429
Anlamı
tur operatörünün çok fazla eşzamanlı araması (açık arama: arama profiline göre anahtarın da)
Çağıran
Retry-After (1 sn) sonrasında tekrarlayın
Kod
ERR_SEARCH_TIMEOUT
Durum
503
Anlamı
Arama süre sınırını (10 sn) aştı
Çağıran
daraltın (destination/destinations, daha küçük pencere), sonra tekrarlayın
Kod
ERR_PRICE_TIMEOUT
Durum
503
Anlamı
room olmadan /v1/price veya /v1/prices süre sınırını (2 sn) aştı
Çağıran
room ile /v1/price çağrısına geçin (/v1/prices için sınır her zaman geçerlidir, room ve boards ile de), sonra tekrarlayın
Kod
ERR_BOOKING_DISABLED
Durum
503
Anlamı
Satış/iptal bu düğümde etkin değil (test anahtarı: sandbox etkin değil – bir test anahtarı asla canlı rezervasyon yapmaz)
Çağıran
Tekrarlayın, kalıcıysa: bildirin
Kod
ERR_BOOKING_BUSY
Durum
503
Anlamı
Rezervasyon/iptal aynı oteldeki eşzamanlı işlemler nedeniyle gerçekleşmedi, hiçbir şey rezerve veya iptal edilmedi
Çağıran
Retry-After (1 sn) sonrasında aynı idemKey ile tekrarlayın
Kod
ERR_INTERNAL
Durum
500
Anlamı
dahili hata, ör. veritabanına erişilemiyor (hesaplama çekirdeğinde ihlal edilen bir invaryant, sorgu endpoint'lerinde 422 olarak gelir); açık arama: sonuç kanıtlanamıyor (4a.4)
Çağıran
Ara vererek tekrarlayın; /v1/book'ta aynı idemKey ile
Kod
ERR_INVENTORY_DRIFT
Durum
500
Anlamı
Kontenjan invaryantı ihlal edildi, hiçbir şey satılmadı
Çağıran
Bildirin
Kod
ERR_INVENTORY_STATUS_UNKNOWN
Durum
500
Anlamı
kontenjanda bilinmeyen günlük durum, hiçbir şey satılmadı
Çağıran
Bildirin
Kod
ERR_RELEASE_DRIFT
Durum
500
Anlamı
İptal, kontenjan invaryantı nedeniyle engellendi, hiçbir şey iptal edilmedi
Çağıran
Bildirin

7.5 Sözleşme verileri (tur operatöründeki hatalar)

Bu kodlar, otelin sözleşmesi TourAPI'nin hesaplamadığı (veya bu şekilde hesaplamadığı) bir kural içerdiğinde hesaplama çekirdeğinden gelir. Sözleşme önceden kontrol edildiği için yayınlandıktan sonra görülmemeleri gerekir. Durum her zaman 422. Çağıran: Bildirin (otel, dönem ve message ile); aramada diagnostics.reasons içinde neden olarak (otel düşer) veya diagnostics.roomErrors içinde (tek oda atlanır) görünürler. room olmadan /v1/price'ta, başka bir oda hesaplanabildiği sürece etkilenen oda atlanır ve warnings içinde belirtilir; /v1/prices etkilenen pansiyon tiplerini rooms[].errors[] içinde belirtir ve geri kalanını fiyatlar (Bölüm 3.2). ERR_NEGATIVE_TRAVELLER_PRICE ve ERR_NEGATIVE_PERCENT_BASE konaklama düzenine ve pansiyon tipine bağlıdır: Yazma kapısı bunlara karşı uyarır, ancak sözleşmeye izin verir; bu nedenle yayınlandıktan sonra da gelebilirler. /v1/prices o zaman yalnızca etkilenen pansiyon tipini atlar (rooms[].errors[], Bölüm 3.2).

Kod
ERR_NO_BASECHARGE
Anlamı
Temel fiyat eksik
Kod
ERR_SECTION_BAD_DATE, ERR_BOARD_BAD_DATE, ERR_OCCUPANCY_BAD_DATE
Anlamı
sözleşmede geçersiz tarih
Kod
ERR_OCCUPANCY_INCOMPLETE
Anlamı
Konaklama düzeni kuralı eksik
Kod
ERR_AMBIGUOUS_BOARDCHARGE
Anlamı
iki pansiyon tipi ek ücreti aynı gecede aynı kişiye uygulanabiliyor; hangisinin geçerli olduğu belirsiz (yazma kapısı buna izin vermez, yalnızca eski kayıtlar)
Kod
ERR_AMBIGUOUS_BASECHARGE, ERR_AMBIGUOUS_SECTION
Anlamı
bir sezonda aynı tipte iki temel fiyat veya aynı gün için iki sezon; hangi fiyatın geçerli olduğu belirsiz (yazma kapısı buna izin vermez, yalnızca eski kayıtlar)
Kod
ERR_AMBIGUOUS_FREENIGHT
Anlamı
aynı konaklama için iki ücretsiz gece teklifi devreye girebiliyor; ikincisinin hangi geceleri bağışladığı belirlenmemiş (yazma kapısı buna izin vermez, yalnızca eski kayıtlar)
Kod
ERR_UNSUPPORTED_FREENIGHT, ERR_UNSUPPORTED_REDUCTION_MODE
Anlamı
TourAPI'nin hesaplamadığı bir biçimde ücretsiz gece teklifi (ör. yüzde yerine sabit tutar, kişi kısıtlaması, „büyük/küçük“ gece seçimi)
Kod
ERR_NEGATIVE_TRAVELLER_PRICE
Anlamı
tüm ek ücret ve indirimlerden sonra bir yolcu 0'dan az öderdi (ör. hiçbir ücreti olmayan bir çocuğa kişi başı sabit indirim veya %100'ü aşan üst üste indirimler). Bir yolcu asla 0'dan az ödemez; message yolcuyu ve ek ücreti/indirimi belirtir. Yüzde indirimleri, yolcunun çocuk/kişi indiriminden sonra borçlu olduğu tutar üzerinden hesaplanır ve hatayı tek başına tetiklemez
Kod
ERR_NEGATIVE_PERCENT_BASE
Anlamı
bir çocuk/kişi indirimi (sabit tutar), etki ettiği günlük fiyattan veya pansiyon tipinden büyük; bunun üzerindeki bir yüzde indirimi ek ücrete dönüşür, bir ücretsiz gece (ör. „7=6“) konaklamayı pahalılaştırırdı. message ek ücreti/indirimi veya ücretsiz geceyi, yolcuyu ve geceyi belirtir
Kod
ERR_UNSUPPORTED_OCCUPANCY_PRICEBLOCK
Anlamı
Doluluğa göre temel fiyat (harici bir teslimattan, ör. “tam 1 kişi” / “2 kişi ve üzeri”) TourAPI'nin hesaplamadığı bir biçimde — ya da böyle bir odada bebek seyahat ediyor (tedarikçi odayı bebekle satmıyor)
Kod
ERR_UNSUPPORTED_GUESTCHARGE_OBJECT
Anlamı
Bir oda fiyatına (obje fiyatı) kişi indirimi (ör. çocuk indirimi): etki edebileceği kişi başı bir fiyat yok (yazma kapısı buna izin vermez, yalnızca eski kayıtlar)
Kod
ERR_UNSUPPORTED_COMBIGROUP
Anlamı
bir teklif 0 numaralı kombinasyon grubunda münhasır; 0 grubu EDF'de grubu olmayan tüm teklifleri de temsil eder, bir EDF alıcısı farklı hesaplardı (yazma kapısı buna izin vermez, yalnızca eski kayıtlar)
Kod
ERR_COMPATIBLE_WITH_INVALID
Anlamı
bir teklifin “yalnızca şu gruplarla birleştirilebilir” listesi belirsiz: bir grup iki kez, 0..2147483647 dışında ya da “grupta yalnızca biri” ile birlikte (yazma kapısı buna izin vermez, yalnızca eski kayıtlar)
Kod
ERR_CALCMODE_MISSING, ERR_CALCMODE_UNSUPPORTED
Anlamı
Odanın hesaplama türü eksik veya desteklenmiyor
Kod
ERR_BASE_BOARD_INVALID, ERR_BASE_BOARD_CHARGED
Anlamı
Odanın temel pansiyonu (taban fiyata dahil pansiyon) geçersiz veya kendi ek ücretini taşıyor (yazma kapısı buna izin vermez, yalnızca eski veri)
Kod
ERR_MINCHARGEDPERSONS_MISSING, ERR_INVALID_MIN_CHARGED_PERSONS
Anlamı
Minimum ödeme yapan kişi sayısı eksik veya geçersiz
Kod
ERR_INVALID_ENUM, ERR_INVALID_WEEKDAY_MASK
Anlamı
geçersiz numaralandırma değeri veya haftanın günü maskesi
Kod
ERR_MISSING_APPLIANCE_TYPE, ERR_MISSING_AMOUNT_OR_PERCENT, ERR_MISSING_BOARD_CODE, ERR_AMOUNT_PERCENT_CONFLICT, ERR_DUPLICATE_APPLIANCE_CODE, ERR_EXTRA_TYPE_INVALID
Anlamı
Ek ücret veya indirim eksik ya da çelişkili
Kod
ERR_EXTRACALC_UNSUPPORTED, ERR_UNSUPPORTED_APPLIANCE_TYPE, ERR_UNSUPPORTED_APPLYTOBOARD, ERR_UNSUPPORTED_EXTRA_REF, ERR_UNSUPPORTED_GUESTCHARGE_LINK, ERR_UNSUPPORTED_GUESTCHARGE_TYPE, ERR_UNSUPPORTED_INVERT, ERR_UNSUPPORTED_MANDATORY, ERR_UNSUPPORTED_MCA_AGEWINDOW, ERR_UNSUPPORTED_MCA_RANGE, ERR_UNSUPPORTED_RESTRICTION, ERR_UNSUPPORTED_VARMCP_PERSTAY
Anlamı
TourAPI'nin hesaplamadığı sözleşme kuralı

7.6 EDF teslimatı (/v1/export/edf/*, Bölüm 10)

Kod
ERR_EXPORT_BAD_CURSOR
Durum
400
Anlamı
epoch/since eksik veya okunamıyor, until geçerli bir zincir anahtarı değil, max_bytes ≥ 65536 bir sayı değil, parametre iki kez verilmiş, until olmadan bir durumun ortasında sayfa, full'ün epoch/since/until olmadan sonraki sayfası
Çağıran
İsteği düzeltin veya zinciri yeniden başlatın
Kod
ERR_EXPORT_NOT_ALLOWED
Durum
403
Anlamı
Dışa aktarım yetkisi olmayan anahtar
Çağıran
Tur operatörüne sorun, tekrarlamayın
Kod
ERR_EXPORT_EPOCH
Durum
409
Anlamı
epoch uymuyor (feed yeniden oluşturuldu) veya durum (since, until, onayın seq değeri) güncel teslimat durumunun üzerinde (son teslim edilen durumdan büyük)
Çağıran
full çekin
Kod
ERR_EXPORT_CURSOR_EXPIRED
Durum
410
Anlamı
Durum saklama süresinden eski
Çağıran
full çekin
Kod
ERR_EXPORT_NOT_READY
Durum
503
Anlamı
Bu anahtar için teslimat henüz oluşturulmadı, geçici olarak güncel değil (teslimat 2 dakikadan fazla geride) veya düğümde yapılandırılmamış
Çağıran
Retry-After sonrasında tekrarlayın; kalıcıysa: bildirin

Ayrıca /v1/export/edf/* üzerinde: 7.1'deki 401/403 (ERR_TENANT_SUSPENDED, ERR_KEY_GROUP_INACTIVE), 422 ERR_UNKNOWN_FIELD (bilinmeyen parametre veya onay alanı) ve döngü için 429 ERR_RATE_LIMITED (10.3; full'de 3600 sn'ye kadar Retry-After – körü körüne beklemeyin, bir sonraki döngüyü planlayın).

7.7 Otel içerikleri (/v1/content/*, Bölüm 12)

Kod
ERR_CONTENT_NOT_ALLOWED
Durum
403
Anlamı
İçerikler tur operatörü için etkin değil (ayrıca: test ortamına bu kurulumda hizmet verilmiyor) veya anahtarın içerik yetkisi yok
Çağıran
Tur operatörüne sorun, tekrarlamayın
Kod
ERR_LANGUAGE_NOT_OFFERED
Durum
422
Anlamı
lang, tur operatörünün içerik dillerinden olmayan bir dil içeriyor (katalogda: etiket dili değil); sunulanlar warnings içindedir
Çağıran
İsteği düzeltin
Kod
ERR_CONTENT_CURSOR_EXPIRED
Durum
410
Anlamı
since akışın saklama ufkunun (30 gün) öncesinde
Çağıran
Dizini yeniden alın, onun feedToken'ı ile devam edin
Kod
ERR_CONTENT_NOT_READY
Durum
503
Anlamı
İçerikler veya görsel adresleri bu düğümde kurulu değil
Çağıran
Retry-After sonrasında tekrarlayın; kalıcıysa: Bildirin

Ayrıca /v1/content/* üzerinde: 7.1'den 401/403, 404 ERR_HOTEL_NOT_FOUND (otel anahtarın dizininde değil), 400 ERR_BAD_REQUEST, 422 ERR_BAD_PAGE_SIZE, ERR_BAD_CURSOR ve 429 ERR_RATE_LIMITED (anahtar başına hız, Bölüm 8). Bilinmeyen sorgu parametreleri yok sayılır ve warnings içinde belirtilir.

8. Adalet: hız sınırı ve arama kapısı

Kilit
API anahtarı başına istek
Sınır (varsayılan ayar)
saniyede 200, kısa süreliğine 400'e kadar (token bucket)
Yanıt
429 ERR_RATE_LIMITED + Retry-After
Kilit
tur operatörü başına eşzamanlı arama
Sınır (varsayılan ayar)
8 (hesaplama süresi: çekirdeklerin dörtte biri, en az 1)
Yanıt
429 ERR_SEARCH_BUSY + Retry-After: 1
Kilit
bir aramanın hesaplama süresi
Sınır (varsayılan ayar)
10 sn
Yanıt
503 ERR_SEARCH_TIMEOUT
Kilit
room olmadan /v1/price ve /v1/prices hesaplama süresi
Sınır (varsayılan ayar)
2 sn
Yanıt
503 ERR_PRICE_TIMEOUT
  • Sınırlar sunucu düğümü başına geçerlidir. Döngülere ve yük zirvelerine karşı bir korumadır, faturalandırılabilir bir kota değildir. İşletmeci bunları değiştirebilir.
  • Retry-After tam saniye cinsindendir (en az 1). Öncesinde tekrarlamayın; sonrasında aynı istekle (/v1/book'ta aynı idemKey ile) tekrarlayın.
  • Retry-After dolmadan yeniden gönderen ve tekrar reddedilen, 429 yanıtını gecikmeli alır (bildirilen bekleme süresinin sonuna kadar, en fazla 1 sn). Bekleme süresi API anahtarı başına geçerlidir: aynı anahtarla birden fazla süreç paralel çalışıyorsa, gecikme bir 429 sonrasında göndermeye devam eden herkesi etkiler, kaç bağlantıyla olursa olsun. Bağlantı açık kalır.
  • Reddedilen anahtarlar (401, 403 ERR_TENANT_SUSPENDED, 403 ERR_KEY_GROUP_INACTIVE) gönderici IP'si başına sayılır: kısa sürede 20 retten sonra saniyede yalnızca 5 istek hemen yanıtlanır, diğerleri gecikmeli (en fazla 1 sn), kaç bağlantıyla olursa olsun. Durum ve yanıt aynı kalır. Geçerli anahtarlı istekler bundan asla etkilenmez.
  • Bilinmeyen yollar (404) ve yanlış metotlar (405 ERR_METHOD_NOT_ALLOWED), reddedilen anahtarlarla aynı gönderici IP'si başına sınıra dahil edilir.
  • /v1/health'in gönderici IP'si başına kendi sınırı vardır: 50 çağrı hemen, sonra saniyede 10 hemen, diğerleri gecikmeli (en fazla 1 sn). Yanıt her zaman güncel durumdur, asla bir ret değildir.
  • Reddedilen istekler kullanım olarak sayılmaz.
  • Kesilmiş arama sayfaları hata değildir, bkz. 4.3.

Kısıtlama şöyle görünür (örnekler 10 sn'de 1 isteğe izin veren bir örneğe karşı çalışır):

### drossel-erste-anfrage
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "EZ",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
### drossel-zweite-anfrage
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{apiKey}}

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "EZ",
  "board": "RO",
  "checkIn": "{{D0}}",
  "checkOut": "{{D2}}",
  "occupancy": {"travellers": [{"age": 40}]}
}
{"errorCode": "ERR_RATE_LIMITED", "message": "Anfrage-Rate dieses API-Keys ueberschritten"}

Buna ait header: Retry-After: 10.

9. Idempotency ve eşzamanlılık

Rezervasyon:

  • idemKey zorunludur ve çağırana aittir. Tur operatörü ve anahtar çevresi başına (müşteri grubu veya temel sözleşme) geçerlidir: İki müşteri grubu birbirini etkilemeden aynı anahtarı kullanabilir.
  • Aynı idemKey, aynı rezervasyon verileri (otel, oda, konaklama, miktar) = aynı rezervasyon: 200, alreadyBooked: true, aynı referans. Bir tekrarın reference, metadata, leadPaxName ve priceCheck değerleri karşılaştırılmaz ve devralınmaz – ilk rezervasyon geçerlidir.
  • Aynı idemKey, farklı rezervasyon verileri: 409 ERR_IDEMPOTENCY_MISMATCH.
  • İptal edilmiş idemKey: 409 ERR_IDEM_KEY_RELEASED.
  • Idempotency kontrolü referans tarihi, sınırlar ve priceCheck'ten önce gelir: Rezervasyon mevcutsa, tekrar onu gece yarısından sonra da (giriş artık geçmişte, priceCheck.now artık referans tarihi değil) veya bir fiyat değişikliğinden sonra da onaylar. Yalnızca yeni bir idemKey bu kontrollerden geçer. İsteğin biçim hataları (bilinmeyen alanlar, eksik zorunlu alanlar, çok uzun metinler) yine önceden reddedilir.
  • Bir idemKey'in ilk rezervasyonu hâlâ sürüyorsa, bir tekrar onun sonucunu bekler ve ardından onu onaylar (200, alreadyBooked: true); ilki başarısız olursa tekrar yeni bir rezervasyon olarak çalışır. Bekleme çok uzun sürerse: Retry-After ile 503 ERR_BOOKING_BUSY – aynı idemKey ile tekrarlayın.
  • Aynı odanın iki eşzamanlı rezervasyonu kontenjanı asla aşırı dolduramaz: Her gece atomik olarak sayılır ve bir rezervasyon ya tüm geceleri alır ya hiçbirini.
  • Rezervasyon veya iptal aynı oteldeki eşzamanlı işlemler nedeniyle gerçekleşmezse: Retry-After ile 503 ERR_BOOKING_BUSY (Bölüm 7.4). Hiçbir şey rezerve veya iptal edilmemiştir; bekleme süresinden sonra aynı idemKey ile tekrarlayın.
  • Bir otel, onun için bir rezervasyon sürerken silinirse sıra geçerlidir: önce rezervasyon yapıldıysa, o kalır; aksi halde 404 ERR_HOTEL_NOT_FOUND, hiçbir şey rezerve edilmez.
  • Zaman aşımı veya 5xx sonrasında rezervasyon yapılıp yapılmadığı belirsizdir: aynı idemKey ile tekrarlayın, asla yenisiyle değil.

İptal:

  • Rezervasyonun idemKey değeri üzerinden adreslenir. Çift iptal bir başarıdır (alreadyReleased: true). Aynı rezervasyonun eşzamanlı iptalleri kontenjanı tam olarak bir kez iade eder.

10. EDF teslimatı (önbellek dışa aktarımı)

Amaç: Bir alıcı, anahtarının tüm otellerinin fiyat ve müsaitliklerini kendi önbelleğinde EDF dosyaları olarak tutar ve rezervasyondan önce canlı sorgular (/v1/price, sonra /v1/book). Bağlayıcı olan her zaman /v1/book'tur; önbellek bir tekliftir, stok değildir. Tam teslimat sözleşmesi (manifestin kanonik biçimi, sınırlar) işletmeciden talep üzerine alınabilir.

Metot/yol
GET /v1/export/edf/full[?max_bytes=N]
Yanıt
Tam durumu içeren 200 zip (büyük stoklarda ilk sayfa)
Metot/yol
GET /v1/export/edf/full?epoch=E&since=S&until=K[&max_bytes=N]
Yanıt
Tam durumun sonraki sayfasını içeren 200 zip
Metot/yol
GET /v1/export/edf/changes?epoch=E&since=S[&until=K][&max_bytes=N]
Yanıt
S durumundan sonraki tüm değişiklikleri içeren 200 zip; yeni bir şey yoksa 204
Metot/yol
POST /v1/export/edf/ack {"epoch": E, "seq": T}
Yanıt
204; „işlendi“ bildirir (yalnızca tur operatöründeki izleme için)
  • Kapsam yalnızca anahtar üzerinden: Neyin teslim edileceğini anahtar belirler – bu anahtar için /v1/search ve /v1/price ile aynı oteller ve fiyatlar (temel sözleşme, kendi fiyatıyla müşteri grubu, yalnızca tahsisli otellerle kontenjan grubu). Tur operatörünü veya grubu seçen bir parametre yoktur; bilinmeyen bir parametre 422 ERR_UNKNOWN_FIELD'dır.
  • Dışa aktarım yetkisi: anahtar başına etkinleştirilir (tur operatörü yöneticisi, konsolda anahtar satırında „Export erteilen“); yetki yoksa 403 ERR_EXPORT_NOT_ALLOWED.
  • Yalnızca yayınlananlar: Taslaklar teslimatı asla değiştirmez. Değişiklikler (yayınlama, satış durdurma, rezervasyon, kampanya, tahsis) en geç yaklaşık bir dakika sonra feed'de yer alır.

10.1 Paket

İlk giriş olarak manifest.json, ardından her otel için bir fiyat dosyası (hotels/hotelonly/EDF----<tenant>-<hotel>.xml, EDF 5.1.6) ve bir allotment dosyası (hotels/hotelonly/allotment/EDF----<tenant>-<hotel>.xml, HotelAllotmentRoot 1.012) içeren bir zip. Kimlik manifestten veya BasicData'dan alınır, asla dosya adından alınmaz (kodlar - içerebilir). Her dosyanın sha256 sağlama toplamı ve uzunluğu manifestte yer alır; bir paket ya tamamen uygulanır ya hiç uygulanmaz.

objects içinde bir giriş ve removed içinde bir giriş:

{"path": "hotels/hotelonly/EDF----TEST-TENANT-A-TEST-HOTEL-BASE.xml", "kind": "hotel", "hotel": "TEST-HOTEL-BASE", "seq": 3, "sha256": "9f2c…", "bytes": 2210, "source_rev": 4}
{"kind": "hotel", "hotel": "TEST-HOTEL-ALTAKTION", "seq": 5, "reason": "withdrawn:variant_error"}
  • Tombstone'lar: Geri çekilen oteller removed altında nedenleriyle yer alır (withdrawn:deleted, withdrawn:variant_error, withdrawn:not_exportable, withdrawn:no_currency, withdrawn:not_in_universe). Alıcı bunları kendi önbelleğinden siler. Bir full her zaman removed: [] içerir – önbelleği tamamen değiştirir.
  • Allotment dosyasının pattern'i: gece başına iki karakter (PatternLength="2", MTS teslimatları gibi): 00–99 = bu anahtar için boş birimler, ** = 99'dan fazla boş veya serbest satış, SS = satış durdurma, RR = talep üzerine; kapasitesiz bir gece 00'dır. Bir allotment dosyası eksikse otel müsait değil kabul edilir. Oda başına yapı: <Allotments RoomCode="DZ"><Allotment Start="JJJJ-MM-TT" End="JJJJ-MM-TT" Pattern="…"/>; Pattern'in ilk iki karakteri Start gecesine, sonraki her çift bir sonraki geceye aittir.
  • Pattern ve minFree: SS, RR ve 00 açıkça minFree 0 verir (rezerve edilemez); yalnızca ** minFree'yi düşürmez ve -1 şu anlama gelir: her gece **. Bir konaklamanın geceleri boyunca minimum, aynı anahtar için /v1/price'ın availability.minFree değeridir (Bölüm 3.3'teki tablo) – ikisi de gece başına aynı kaynaktan gelir.

10.2 Tam durum, değişiklikler, onay

Başlangıçta ve 409/410 sonrasında alıcı tam durumu çeker. Manifestten epoch ve to_seq değerlerini not eder. Tam durum bir pakete sığmazsa (10.000'den fazla dosya, açılmış halde 1 GiB'den fazla veya max_bytes'tan fazla), sayfalar halinde gelir, bkz. aşağıda „Sayfalar“:

### export-voll
GET {{baseUrl}}/v1/export/edf/full
X-Api-Key: {{apiKey}}
{
  "format": "tourapi-edf-feed/1",
  "tenant": "TEST-TENANT-A",
  "scope": "",
  "epoch": "{{exportEpoch}}",
  "type": "full",
  "from_seq": 0,
  "to_seq": "{{*}}",
  "more": false,
  "generated_at": "{{*}}",
  "rules": {"edf": "5.1.6", "allotment": "1.012", "spec": ""},
  "objects": "{{*}}",
  "removed": []
}

Her 200/204 yanıtının header'ları: X-Export-Epoch, X-Export-Seq (son tam durum), X-Export-From (teslimatın başladığı durum), X-Export-More; bir changes paketinde ve more: true olan bir full sayfasında ayrıca X-Export-Until (zincir anahtarı, bkz. aşağıda). Bir sayfa bir durumun ortasında biterse (manifestte to_after), X-Export-Seq ondan önceki durumu belirtir – bir full'ün ilk sayfalarında yani 0. since, uygulama ve onay için belirleyici olan manifestteki to_seq/to_after'dır. Bir müşteri grubunun anahtarı kendi grubunun durumunu (scope), grubun fiyatlarıyla alır:

### export-partner
GET {{baseUrl}}/v1/export/edf/full
X-Api-Key: {{rabattKey}}

Ardından döngü içinde değişiklikleri sorgular. since, son uygulanan paketin to_seq değeridir; zaten güncel durumdaysa içeriksiz 204 gelir:

### export-nichts-neu
GET {{baseUrl}}/v1/export/edf/changes?epoch={{exportEpoch}}&since={{exportSeq}}
X-Api-Key: {{apiKey}}

Sayfalar (max_bytes): full ve changes sayfa zincirleridir. Bir sayfada en fazla 10.000 dosya ve açılmış halde 1 GiB bulunur; max_bytes=N ile sunucu ayrıca en fazla N baytlık sayfalar keser (sayfa başına en az bir dosya); N en az 65536'dır (64 KiB), daha küçüğü 400 ERR_EXPORT_BAD_CURSOR'dır. İlk sayfa (parametresiz full veya until olmadan changes) zincirin hedefini (güncel durumu) belirler ve bunu zincir anahtarı olarak X-Export-Until içinde verir (full'de yalnızca more: true ise): anahtara ve epoch'a bağlı, opak, mühürlü bir karakter dizisi (changes'te u1.…, full'de f1.…). Alıcı sonraki sayfaları since=<to_seq> veya since=<to_seq>:<to_after> (manifest to_after taşıyorsa) ve until=<X-Export-Until> (önceki sayfanın değeri, URL kodlu, değiştirilmeden) ile, full'de ayrıca epoch=<epoch> (ilk sayfanın değeri) ile, more false olana kadar sorgular. Her sayfa anahtarı yeniden verir; anahtar son sayfadan sonra 15 dakika, full'de yalnızca tam olarak sonraki sayfanın durumu için geçerlidir. Kendi seçilmiş bir until (sayı), yabancı veya süresi dolmuş bir anahtar 400 ERR_EXPORT_BAD_CURSOR'dır – o zaman zinciri yeniden başlatın. Bir full sayfası asla removed taşımaz; ilki from_seq: 0'da, sonraki her biri önceki sayfanın to değerinde başlar. Durum ancak zincirin sonunda tutarlıdır: uygulama (değiştirme) orada yapılır, tur operatörü arada değişiklik yapmaya devam etse bile – zincir tam olarak hedefinde biter.

İptal ve tekrarlama: Bir paket, 200 zaten gönderildikten sonra başarısız olursa (örneğin tur operatöründe bir dosya bu arada eksik olduğu için), sunucu bağlantıyı keser. Alıcı o zaman bir iletim hatası görür (unexpected EOF, bağlantı sıfırlandı), asla düzgün biçimde sonlandırılmış, kısaltılmış bir paket görmez. Yine de her paketi uygulamadan önce manifestine karşı kontrol etmelidir: objects içindeki her dosya mevcut, uzunluk (bytes) ve sha256 doğru, fazladan dosya yok. Bir akış okuyucusu (ör. Java'nın ZipInputStream'i), bir giriş sınırında biten bir zip'i kendiliğinden eksik olarak tanımaz. Kısa süreliğine başarısız olan bir sonraki sayfayı (iletim hatası, yarıda kesilmiş veya okunamayan paket, 5xx), zinciri yeniden başlatmak yerine aynı epoch/since/until ile tekrar sorgular (anahtar 15 dakika geçerlidir, 503 durumunda Retry-After sonrasında) – yeni bir başlangıç döngü hakkını tüketir (full: bir saat). Yalnızca 400 sonrasında zinciri yeniden başlatır, 409/410 sonrasında full çeker. Bir zincir yarıda kalırsa eski durum geçerli kalır.

Uyguladıktan sonra alıcı durumu onaylar. Onay isteğe bağlıdır ve teslimatta hiçbir şeyi değiştirmez; tur operatörü bundan alıcının hangi durumu işlediğini görür:

### export-quittung
POST {{baseUrl}}/v1/export/edf/ack
Content-Type: application/json
X-Api-Key: {{apiKey}}

{"epoch": "{{exportEpoch}}", "seq": {{exportSeq}}}

10.3 Hatalar ve döngü

Durum
400
Kod
ERR_EXPORT_BAD_CURSOR
Ne zaman
epoch/since eksik veya okunamıyor, until geçerli bir zincir anahtarı değil (yabancı, süresi dolmuş, sayı, full'de başka bir durum için), max_bytes ≥ 65536 bir sayı değil, until olmadan bir durumun ortasında sayfa, durum zincir hedefinin üzerinde, full'ün epoch/since/until olmadan sonraki sayfası
Alıcının yapacağı
İsteği düzeltin veya zinciri yeniden başlatın
Durum
403
Kod
ERR_EXPORT_NOT_ALLOWED
Ne zaman
Dışa aktarım yetkisi olmayan anahtar
Alıcının yapacağı
Tur operatörüne sorun
Durum
403
Kod
ERR_KEY_GROUP_INACTIVE
Ne zaman
Anahtarın müşteri grubu devre dışı (asla sessizce temel sözleşme)
Alıcının yapacağı
Tur operatörüne sorun
Durum
409
Kod
ERR_EXPORT_EPOCH
Ne zaman
epoch uymuyor (feed yeniden oluşturuldu) veya since/until ya da onayın seq değeri güncel teslimat durumunun üzerinde (son teslim edilen durumdan büyük; daha eski bir since'e izin verilir ve sonrasındaki her şeyi teslim eder)
Alıcının yapacağı
full
Durum
410
Kod
ERR_EXPORT_CURSOR_EXPIRED
Ne zaman
Durum saklama süresinden (14 gün) eski
Alıcının yapacağı
full
Durum
429
Kod
ERR_RATE_LIMITED
Ne zaman
Döngü aşıldı, bkz. aşağıda
Alıcının yapacağı
Retry-After sonrasında
Durum
503
Kod
ERR_EXPORT_NOT_READY
Ne zaman
Bu anahtar için teslimat henüz hiç oluşturulmadı (yeni anahtar, yeni grup) veya teslimat geçici olarak güncel değil (verilerin 2 dakikadan fazla gerisinde)
Alıcının yapacağı
Retry-After sonrasında, eski durum geçerli kalır
### export-stand-unlesbar
GET {{baseUrl}}/v1/export/edf/changes?epoch={{exportEpoch}}&since=gestern
X-Api-Key: {{apiKey}}
### export-fremder-stand
GET {{baseUrl}}/v1/export/edf/changes?epoch=01J00000000000000000000000&since=1
X-Api-Key: {{partnerKey}}
{"errorCode": "ERR_EXPORT_EPOCH", "message": "epoch veraltet (Feed neu aufgebaut) oder Stand neuer als der aktuelle Lieferstand — full abrufen"}
### export-ohne-recht
GET {{baseUrl}}/v1/export/edf/full
X-Api-Key: {{poolKey}}
{"errorCode": "ERR_EXPORT_NOT_ALLOWED", "message": "dieser API-Key hat kein Export-Recht (EDF-Lieferung) — der Veranstalter-Admin schaltet es frei"}

Anahtar başına döngü: yeni bir full saatte en fazla bir kez, yeni bir changes zinciri dakikada en fazla bir kez. Bir zincirin sonraki sayfaları (full veya changes, zincir anahtarıyla) ve onayların kendi, cömert döngüsü vardır (saniyede 5, bir seferde 50). Bir zincir başlangıcının hangi çağrısı döngü hakkını tüketir:

Yanıtdöngü hakkını tüketir
400 ERR_EXPORT_BAD_CURSOR, 422 ERR_UNKNOWN_FIELD (geçersiz istek, her işten önce kontrol edilir)hayır
503 ERR_EXPORT_NOT_READY (teslimat henüz hiç oluşturulmadı veya geçici olarak güncel değil)hayır – Retry-After'a uyan 429 almaz
200 (yarıda kesilmiş bir indirme dahil), 204 („yeni bir şey yok“), 409 ERR_EXPORT_EPOCHevet

Döngü kontrolü, epoch ve durum kontrolünden önce gelir. Eski epoch'a sahip bir alıcı döngü içinde önce 429'u, ancak onun Retry-After süresinden sonra 409'u görür. Örnek akış changes (ölçülmüş, döngü 1 dk):

Zaman
0 sn
İstek
changes?epoch=E&since=S&foo=1 (yazım hatası)
Yanıt
422 ERR_UNKNOWN_FIELD
Alıcının yapacağı
düzeltin; döngü hakkı tüketilmedi
Zaman
0 sn
İstek
changes?epoch=E&since=S
Yanıt
204
Alıcının yapacağı
Döngü hakkı tüketildi; sonraki zincir en erken 60 sn sonra
Zaman
0 sn
İstek
changes?epoch=E&since=S, feed bu arada yeniden oluşturuldu (E eski)
Yanıt
429 ERR_RATE_LIMITED, Retry-After: 60
Alıcının yapacağı
Retry-After süresini bekleyin
Zaman
60 sn
İstek
aynı çağrı
Yanıt
409 ERR_EXPORT_EPOCH
Alıcının yapacağı
full çekin (kendi döngüsü, saatte 1)

Bunun ötesinde anahtarın hız sınırı geçerlidir (Bölüm 8). Aynı saat içinde ikinci bir full:

### export-zu-oft
GET {{baseUrl}}/v1/export/edf/full
X-Api-Key: {{apiKey}}
{"errorCode": "ERR_RATE_LIMITED", "message": "full hoechstens einmal je 1 h je API-Key (danach changes)"}

10.4 Uygulama ve önbellekten hesaplama (referans alıcı)

TourAPI'nin tam olarak bu kuralları uygulayan ve her derlemede gerçek API'ye karşı kontrol edilen bir referans alıcısı vardır: edf-empfaenger aracı (işletmeciden talep üzerine) (pull, apply, stand, rechne, reset; anahtar yalnızca EDF_EMPFAENGER_KEY ortam değişkeninden gelir). Teslimattan hesapladığı şey, test kümesinin her isteği için aynı anahtarın /v1/price sonucuyla sent düzeyinde aynıdır – toplam fiyat, yolcu başına fiyat, seçilen oda, available ve minFree, ret durumunda aynı kod; bu, geçersiz ve birden fazla açıdan geçersiz istekler için de geçerlidir (hangi sınırın önce devreye girdiği). Referans alıcı ve make e2e-export TourAPI'nin kaynak ağacında bulunur ve teslimatın parçası değildir: Siz müşteri olarak anahtar ve erişim paketi alırsınız ve bu bölümün kurallarını kendi sisteminizde uygularsınız – referans alıcı, bu kuralların işlediğinin kanıtıdır.

EDF_EMPFAENGER_KEY=… edf-empfaenger pull   --dir <bestand> --api <baseUrl> [--max-bytes N] [--ohne-ack]
edf-empfaenger stand  --dir <bestand>
edf-empfaenger rechne --dir <bestand> --anfragen anfragen.json [--heute JJJJ-MM-TT]

anfragen.json bir listedir; her istek için hotel, room (isteğe bağlı, yoksa = en uygun fiyatlı müsait oda), board, checkIn, checkOut ve yolcuların yaşları ages olarak (/v1/price'taki gibi occupancy.travellers değil), ör. [{"hotel": "TEST-HOTEL-BASE", "room": "DZ", "board": "RO", "checkIn": "2026-10-27", "checkOut": "2026-10-29", "ages": [40, 38]}]. Yanıt her istek için anfrage (indeks), room, currency, totalCents, perTravellerCents, availability (available, minFree) veya errorCode belirtir.

  • Ya hep ya hiç: Paketi kontrol edin (dosya başına sha256 ve uzunluk), yeni durumu eskisinin yanında eksiksiz kurun, ardından atomik olarak geçiş yapın; ancak bundan sonra epoch ve to_seq kaydedilmiş sayılır. Sayfalı bir zincir (full gibi changes de) ancak zincirin sonunda (more: false) geçiş yapılır; yarıda kalırsa eski durum geçerli kalır. Bir full zinciri her zaman from_seq: 0 ile başlar; bir full sonraki sayfası yalnızca açık zincire bağlanır, asla kaydedilmiş bir duruma bağlanmaz – from_seq değeri kaydedilmiş to_seq'e eşit olsa bile (bir full removed taşımaz, aksi halde silinen oteller yerinde kalırdı).
  • Asla geriye doğru değil: Belirleyici olan zincirin hedefidir, yani son sayfanın to_seq değeri (more: false), ilk sayfanınki değil. Bir full zincirinin ilk sayfası çoğu zaman kaydedilmiş durumdan daha küçük bir to_seq taşır (örneğin 410 sonrasında: en eski dosyalar önce gelir) ve yine de bir geri adım değildir. Hedefi kaydedilmiş durumdan küçük olan, aynı epoch'a ait bir full zinciri veya daha eski bir epoch'a ait bir full (epoch zamana göre sıralı bir ULID'dir) reddedilir – gecikmeli teslim edilmiş eski bir paket önbelleği geri almaz. changes durumuna boşluksuz bağlanmalıdır (from_seq = kaydedilmiş to_seq). Başka bir kiracının veya kapsamın full'ü (karıştırılmış anahtar) da reddedilir, geriye doğru kontrolden önce kapsam hatası olarak.
  • Reddedilen full: Eski durum geçerli kalır, önbellek onunla hesaplamaya devam eder, ancak artık yeni bir şey çekmez. Bu kasıtlı olarak yüksek sesle olur – asla sessizce geri sıçramaz. Karıştırılmış anahtar söz konusu değilse ve teslimat gerçekten daha eski bir epoch üzerindeyse (örneğin saati geri kalan bir makinede yapılan bir geri yüklemeden sonra: yeni epoch ULID'si o zaman daha küçüktür) veya daha küçük bir to_seq üzerindeyse, durumu bilinçli olarak sıfırlayın (referans alıcı: edf-empfaenger reset) ve yeni bir full çekin. Şüphe durumunda önce işletmeciye danışın.
  • Çekme sırasında okuma: Bir çekme uzun sürebilir (büyük zincir, 429 beklemesi). Önbellekten fiyatlar bu sırada eski durumla, geçişten sonra ise yeni durumla çalışmaya devam eder; eski durumu ancak artık kimse onu okumuyorsa silin. Referans alıcı stok başına bir yazıcıya (pull, apply, reset) ve istenen sayıda okuyucuya (stand, rechne) izin verir. 429 için yanıt başına en fazla --max-warten, çekme başına toplam en fazla --max-warten-gesamt bekler, sonra iptal eder (durum değişmez).
  • Yeniden başlamak yerine tekrarlama: Kısa süreli hata veren bir sonraki sayfayı referans alıcı aynı parametrelerle üç kereye kadar çeker (10.2'deki kural); zincir başlangıcını tekrarlamaz.
  • Önbellekten fiyat: Beyan edilen kurallarla EDF 5.1.6, ayrıca API'nin kuralları: aynı istek sınırları (Ek, aynı hata kodları; referans tarihi = Europe/Berlin'de bugün); oda ile bu oda kodunun en uygun fiyatlı teklifi, oda olmadan en uygun fiyatlı müsait oda, hiçbiri müsait değilse fiyatlandırılabilir en uygun fiyatlı oda ( Bölüm 3.1'deki gibi); pansiyon tipini sunmayan bir oda fiyatlandırılmaz.
  • Beyan edilen kurallar: Her otel dosyası SellingData içinde çocuk sıralamasını belirtir (ChildrenAgeOrder, Descending = önce en büyük çocuk: „1. çocuk“ kimdir, hangi çocuk boş bir tam ücret yerini doldurur), FullPayerDoesNotAffectBoardCharges/PersonType=C (tam ücret yerindeki bir çocuk tam temel fiyatı, pansiyon tipini ise çocuk tarifesiyle öder) ve Rounding Mode="Commercial" DecimalPlace="2" Scope="Person" (yolcu başına bir yuvarlama, Bölüm 1.4). Şemanın serbestlik bıraktığı yerlerde TourAPI şöyle hesaplar – önbellek de aynısını yapmalıdır, aksi halde sapar: MinCount/MaxCount çocukları kişi türü başına ilk çocuktan itibaren, yetişkinleri tam ücret ödeyenlerden sonraki ilk yetişkinden itibaren sayar; bir bebek bir kişi koşulunda („3 kişiden itibaren“) yalnızca oda onu doluluğa sayıyorsa sayılır (Infants/@ApplyToOccupancy Min, Max veya Yes); eksik bir ExtraMaxApply 1 anlamına gelir; Operator="AND" ile birden fazla tarih penceresi kesişim olarak geçerlidir; bir ücretsiz gecenin yanındaki yüzde ek ücret ve indirimleri yalnızca bağışlanmayan gecelere uygulanır; kişi başına sabit bir tutar (PS, PN) obje fiyatında da kişi başına etki eder; oda kalemleri (obje fiyatı, doldurulmamış tam ücret yeri) en büyük yolcuya aittir ve onunla birlikte yuvarlanır.
  • Pattern'den müsaitlik: Odanın [checkIn, checkOut) geceleri boyunca minimum, 10.1'deki gibi; dosyanın belirtmediği bir gece kapalıdır (asla „hiçbir şey teslim edilmediği için açık“ değil). /v1/price'ın configured alanının önbellekte karşılığı yoktur: kontenjanı olmayan bir oda orada 00 olarak yer alır.
  • Rezervasyondan önce canlı: Önbellek API'nin bir durum gerisinde olabilir; önbellekte açık, bu arada kapatılmış veya dolmuş bir geceyi /v1/book reddeder (ERR_STOP_SALE, ERR_SOLD_OUT).

11. Sandbox: test anahtarları ve test rezervasyonları

Bir test anahtarı (tk_test_…) bir canlı anahtarın ikizidir: aynı tur operatörü, aynı müşteri grubu, aynı yetkiler (dışa aktarım yetkisi, ileride arama profili) — çalışma zamanında canlı anahtardan devralınır. Canlı anahtarla aynı verileri okur (oteller, fiyatlar, hedefler, müsaitlik, EDF teslimatı), ancak rezervasyonlar sandbox'a düşer: asla tur operatörüne değil, kontenjan yok, dışa aktarım yok, konsolun rezervasyon listesi yok. Böylece rezervasyon, rezervasyon bilgisi, iptal, idempotency ve tekrarlama mantığı gerçek tekliflerle test edilebilir.

  • Test anahtarını tur operatörü konsolda canlı anahtar için oluşturur (anahtar satırındaki „Test-Key“ düğmesi) ve kendi erişim paketiyle iletir. 30 gün geçerlidir (en fazla 90), erişim başına en fazla 5 aktif test anahtarı.
  • Canlı anahtar askıya alınmışsa veya test anahtarının süresi dolmuşsa: 401 ERR_UNAUTHORIZED. Müşteri grubu devre dışı: canlıdaki gibi 403 ERR_KEY_GROUP_INACTIVE. Canlı anahtar yenilendikten sonra test anahtarı yeni canlı anahtarla çalışmaya devam eder; asla canlı anahtarından daha geç sona ermez.
  • Tur operatörü izin verdiyse test anahtarını iş ortağı portalında kendiniz de verebilirsiniz (7, 30 veya 90 gün).
  • Mod için belirleyici olan anahtarın öneki değil, tur operatöründeki eşleştirmedir.

11.1 İşaretleme ve karıştırmaya karşı koruma

Kabul edilen bir anahtara verilen her yanıt X-TourAPI-Mode: test veya live header'ını taşır (header adlarını HTTP'de olağan şekilde büyük/küçük harf ayrımı yapmadan karşılaştırın: sunucu X-Tourapi-Mode yazar). Bir test anahtarının rezervasyon, rezervasyon bilgisi ve iptal yanıtları ayrıca "sandbox": true alanını taşır, referans SB- ile başlar (canlıda TA-). Test anahtarıyla okuma canlı anahtarla aynı yanıtı verir:

### sandbox-preis
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{testKey}}
X-TourAPI-Require-Mode: test

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "board": "RO",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "occupancy": {"travellers": [{"age": 40}, {"age": 38}]}
}

Test ortamları (CI, ajanlar, geliştirici bilgisayarları) X-TourAPI-Require-Mode: test header'ını gönderir. Bu durumda bir canlı anahtar gelirse API her isteği reddeder ve hiçbir şey yürütmez – böylece yanlışlıkla kullanılan bir canlı anahtar rezervasyon yapamaz (kontrol istemcide değil, sunucudadır). X-TourAPI-Require-Mode: live tersine bir canlı anahtar gerektirir; başka bir değer 400 ERR_BAD_REQUEST'tir.

### sandbox-live-key-in-testumgebung
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{apiKey}}
X-TourAPI-Require-Mode: test

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0003"
}
{"errorCode": "ERR_MODE_MISMATCH", "message": "X-TourAPI-Require-Mode: test verlangt, der API-Key ist ein live-Key — nichts ausgefuehrt"}

11.2 Test rezervasyonu, rezervasyon bilgisi, iptal

Test anahtarıyla /v1/book, canlıdakiyle aynı kontrollerden aynı sırayla geçer (alanlar, tekrar, otel, referans tarihi ve sınırlar, satış kuralları, priceCheck, müsaitlik) ve aynı hata kodlarını verir. Müsaitlik kontrol edilir, ancak tüketilmez:

### sandbox-buchen
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{testKey}}
X-TourAPI-Require-Mode: test

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0001",
  "reference": "TEST-4711",
  "leadPaxName": "Test Person",
  "priceCheck": {
    "board": "RO",
    "currency": "EUR",
    "occupancy": {"travellers": [{"age": 40}, {"age": 38}]},
    "expectedCents": 18000,
    "tolerancePercent": 0
  }
}
{"booked": true, "alreadyBooked": false, "reference": "{{sandboxRef}}", "correlationId": "{{*}}", "sandbox": true}
### sandbox-buchung-lesen
GET {{baseUrl}}/v1/booking?ref={{sandboxRef}}
X-Api-Key: {{testKey}}
{"reference": "{{sandboxRef}}", "customerReference": "TEST-4711", "hotel": "TEST-HOTEL-BASE", "room": "DZ",
 "checkIn": "{{D2}}", "checkOut": "{{D4}}", "quantity": 1, "status": "confirmed",
 "bookedAt": "{{*}}", "updatedAt": "{{*}}", "totalCents": 18000, "currency": "EUR", "board": "RO", "sandbox": true}

Test ve canlı dünya ayrıdır: Bir test anahtarı yalnızca test rezervasyonlarını görür ve iptal eder, bir canlı anahtar yalnızca gerçek olanları. Aynı idemKey her iki dünyada birbirinden bağımsız olarak rezervasyon yapar.

### sandbox-live-key-sieht-testbuchung-nicht
GET {{baseUrl}}/v1/booking?ref={{sandboxRef}}
X-Api-Key: {{apiKey}}
{"errorCode": "ERR_BOOKING_NOT_FOUND", "message": "keine Buchung zu '{{sandboxRef}}'"}
### sandbox-stornieren
POST {{baseUrl}}/v1/cancel
Content-Type: application/json
X-Api-Key: {{testKey}}

{"idemKey": "BEISPIEL-0001"}
{"released": true, "alreadyReleased": false, "sandbox": true}
  • Test rezervasyonları oluşturulmalarından 30 gün sonra silinir. Erişim başına en fazla 5.000 açık (iptal edilmemiş) test rezervasyonu, üzerinde 422 ERR_SANDBOX_LIMIT.
  • Bir test anahtarının çağrılarını (metot, yol, durum, errorCode, süre, istek ve yanıt) TourAPI 7 gün boyunca kaydeder, böylece tur operatörü hata ayıklamada yardımcı olabilir. Bu nedenle: test rezervasyonlarında test isimleri kullanın.
  • Test anahtarıyla EDF teslimatı: çağrılar (full, changes) ve onaylar (ack) yalnızca test çağrılarının bu kaydında yer alır, tur operatörünün teslimat kaydında asla yer almaz – teslimatın health durumunda alım veya işleme olarak sayılmazlar.

11.3 Senaryolar: hata durumlarını zorlama

X-TourAPI-Sandbox-Scenario header'ı ile bir test anahtarı bir hata durumunu zorlar – tekrarlama mantığını test etmek için (Bölüm 8 ve 9). Bir canlı anahtarla, bilinmeyen bir adla veya senaryonun etkisiz olduğu bir endpoint'te: 422 ERR_SCENARIO_NOT_ALLOWED.

Senaryo
booking_busy
Endpoint'ler
/v1/book, /v1/cancel
Etki
idemKey ve endpoint başına ilk deneme (rezervasyon ve iptal ayrı sayılır): Retry-After: 1 ile 503 ERR_BOOKING_BUSY, hiçbir şey rezerve veya iptal edilmez; aynı idemKey ile tekrar başarılı olur
Senaryo
price_drift
Endpoint'ler
priceCheck ile /v1/book
Etki
idemKey başına ilk deneme: 409 ERR_PRICE_DRIFT; fiyatın kendisi değişmez (message güncel ve beklenen fiyatı verir, ikisi aynıdır); yeni fiyatı alın, yeniden rezervasyon yapın
Senaryo
sold_out
Endpoint'ler
/v1/book
Etki
her zaman 422 ERR_SOLD_OUT (diğer tüm kontrollerden sonra)
Senaryo
rate_limited
Endpoint'ler
tümü
Etki
test anahtarı ve endpoint başına ilk istek: Retry-After: 1 ile 429 ERR_RATE_LIMITED
Senaryo
search_busy
Endpoint'ler
/v1/search
Etki
test anahtarı başına ilk istek: Retry-After: 1 ile 429 ERR_SEARCH_BUSY
Senaryo
price_timeout
Endpoint'ler
/v1/price, /v1/prices
Etki
test anahtarı ve endpoint başına ilk istek: 503 ERR_PRICE_TIMEOUT

„İlk istek“ 10 dakika geçerlidir; bu süre içindeki her tekrar normal şekilde işlenir. Bellek API'nin her düğümünde ayrı tutulur.

### sandbox-szenario-busy
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{testKey}}
X-TourAPI-Sandbox-Scenario: booking_busy

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0002"
}
{"errorCode": "ERR_BOOKING_BUSY", "message": "Buchung/Storno kam wegen gleichzeitiger Vorgaenge am selben Hotel nicht durch; nichts geaendert — mit demselben idemKey wiederholen (Sandbox-Szenario booking_busy)"}
### sandbox-szenario-busy-wiederholt
POST {{baseUrl}}/v1/book
Content-Type: application/json
X-Api-Key: {{testKey}}
X-TourAPI-Sandbox-Scenario: booking_busy

{
  "hotel": "TEST-HOTEL-BASE",
  "room": "DZ",
  "checkIn": "{{D2}}",
  "checkOut": "{{D4}}",
  "quantity": 1,
  "idemKey": "BEISPIEL-0002"
}
{"booked": true, "alreadyBooked": false, "reference": "{{*}}", "correlationId": "{{*}}", "sandbox": true}
### sandbox-szenario-live-key
GET {{baseUrl}}/v1/destinations
X-Api-Key: {{apiKey}}
X-TourAPI-Sandbox-Scenario: rate_limited
{"errorCode": "ERR_SCENARIO_NOT_ALLOWED", "message": "X-TourAPI-Sandbox-Scenario: nur mit einem Test-Key (Sandbox)"}

11.4 Test anahtarlarının sınırları

Bir erişimin tüm test anahtarları tek bir kotayı paylaşır: saniyede 20 istek, burst 40 (bunun yanında canlı anahtarın sınırı etkilenmez). Bir tur operatörünün test anahtarları en fazla 2 eşzamanlı arama kullanır. EDF teslimatının döngüsü (full 1/saat, yeni changes zinciri 1/dk) test anahtarı başına değil, erişim başına geçerlidir.

11.5 Sandbox'ın kanıtlamadıkları

  • Kontenjan ve son oda için yarış: Test rezervasyonları hiçbir şey tüketmez. Bir test rezervasyonu, canlıda başkasının daha hızlı olduğu yerde başarılı olabilir.
  • Rezervasyondan sonra tur operatöründeki süreçler (onay, değişiklik, fatura).
  • Canlı yük altındaki davranış (ayrı, daha küçük kota, bkz. 11.4).

11.6 Yapay zeka ajanları: portaldaki MCP sunucusu

Portal, /mcp adresinde bir MCP sunucusu sunar (Model Context Protocol, “Streamable HTTP” aktarımı, durumsuz, yalnızca araçlar). Claude Code, Codex veya Antigravity gibi bir yapay zeka ajanı, HTTP kodu yazmak yerine API'yi bu araçlar üzerinden çağırır. Her araç, MCP isteğinin anahtarıyla (X-Api-Key veya Authorization: Bearer) ve X-TourAPI-Require-Mode: test ile yapılan tam olarak bir /v1 çağrısıdır – davranış, sınırlar, yalıtım ve hata kodları API'ninkilerdir. Yalnızca test anahtarları: canlı anahtar ERR_MODE_MISMATCH (403) alır, hiçbir şey yürütülmez. Portal hiçbir anahtarı saklamaz.

AraçÇağrıArgümanlar
list_destinationsGET /v1/destinationsyok
search_hotelsPOST /v1/searchistek gövdesi; sayfalar cursor ile
price_offerPOST /v1/priceistek gövdesi
price_all_roomsPOST /v1/pricesistek gövdesi
sandbox_bookPOST /v1/bookistek gövdesi, idemKey zorunlu
get_bookingGET /v1/bookingref
sandbox_cancelPOST /v1/cancelistek gövdesi
get_limitsGET /v1/limitsyok
open_searchPOST /v1/search/openistek gövdesi
open_search_datesPOST /v1/search/open/datesistek gövdesi
hotel_detailsGET /v1/content/hotels/{code}code, lang

İstek gövdesi olan araçlar argümanlarını değiştirmeden JSON gövdesi olarak API'ye iletir (şema = uç noktanın OpenAPI tanımı). Diğerleri yalnızca listelenen argümanları alır; başka bir argüman ERR_UNKNOWN_FIELD, geçersiz bir değer ERR_BAD_REQUEST verir – API çağrısı yapılmadan.

hotel_details yalnızca içerik yetkisiyle. tools/list, isteğin anahtarıyla GET /v1/limits sorgular ve hotel_details aracını yalnızca content.allowed: true ise listeler (içerik API'siyle aynı kural, 4c); anahtar yoksa listede yer almaz. Yine de çağrılırsa API ERR_CONTENT_NOT_ALLOWED (403) döndürür. API bu kontrolde anahtarı reddederse (ör. canlı anahtar için ERR_MODE_MISMATCH) veya yanıt vermezse tools/list, message içinde hata kodu ya da nedenle bir JSON-RPC hatasıdır (-32000) – araç listesi yoktur.

hotel_details ve lang. Tur operatörü istenen bir içerik dilini sunmuyorsa araç ERR_LANGUAGE_NOT_OFFERED hatasını iletmez: içerik dillerini okur (GET /v1/content/catalog, languages) ve istenen dilleri sunulduğu ölçüde, aksi halde bir yedek dili (en, sonra de, sonra sunulan ilk dil) sorar. structuredContent bunu language altında belirtir (requested, delivered, offered, fallback: true). İçerik API'sinin kendisi katı kalır; tur operatörü hiç içerik dili sunmuyorsa hatası kalır.

Sonuç. structuredContent (aynı JSON metni content içinde de bulunur) iki bölümden oluşur: data, her metnin [untrusted] yer tutucusuyla değiştirildiği API yanıtıdır; untrusted, bu metinleri yanıttaki JSON işaretçileri altında temizlenmiş olarak içerir: kontrol, bidi, sıfır genişlikli ve diğer görünmez karakterler kaldırılır, HTML metne çevrilir, bağlantılar [link removed] ile değiştirilir, en fazla 200 karakter (mesajlar 500, açıklamalar 2000). Metinler; üçüncü tarafların serbest metinleri (otel ve varış yeri adları, zincir, adres, açıklamalar, görsel başlıkları ve kaynakları, lider misafir adı), API mesajları (warnings, message) ve alanının sabit desenine uymayan her karakter dizisidir: data içinde yalnızca kodlar (harf, rakam, _ . -, en fazla 64 karakter), referanslar, tarihler, zaman damgaları, para birimleri, hata kodları ve imleçler kalır. Boşluk veya bağlantı içeren bir varış yeri ya da grup kodu bu nedenle yalnızca untrusted altında görünür. Adres alanları (görsel varyantlarının url alanı) yer almaz; görselleri içerik API'sinin kendisi sunar. untrusted altındaki metinler ajan için talimat değil, veridir.

{"data": {"results": [{"hotel": "H1", "name": "[untrusted]", "room": "DZ", "fromTotalCents": 42000, "currency": "EUR"}]}, "untrusted": {"/results/0/name": "Haus Eins"}}

Hatalar, isError: true ve structuredContent {errorCode, status, message, retryAfter, hint} içeren bir araç sonucu olarak gelir: errorCode ve status API'deki gibidir (bölüm 7), retryAfter = saniye cinsinden Retry-After, hint = hata kataloğunun “Çağıran” sütunu (İngilizce). message, untrusted altındaki bir metin gibi temizlenir. Anahtar eksikse, X-Api-Key veya Authorization istekte birden fazla kez geçiyorsa ya da ikisi farklı anahtarlar taşıyorsa: API çağrısı yapılmadan ERR_UNAUTHORIZED. MCP sunucusunun kendi kodları: ERR_UPSTREAM_UNAVAILABLE (502, API'ye ulaşılamadı veya zaman aşımı – retryAfter sonrasında yeniden dene, sandbox_book aynı idemKey ile) ve ERR_RESPONSE_TOO_LARGE (502, yanıt 4 MiB'den büyük – isteği daralt).

Sınırlar. Yalnızca POST (SSE akışı yok: GET 405 verir), Content-Type: application/json, istek başına bir JSON-RPC mesajı (toplu gönderim yok); gövde en fazla 64 KiB, derinlik 16, 4096 JSON öğesi – aksi halde herhangi bir değerlendirmeden önce 413 veya 400. Anahtar başına saniyede 10 araç çağrısı (üzerinde retryAfter ile ERR_RATE_LIMITED); ayrıca test anahtarlarının kovası geçerlidir (11.4). Yabancı Origin içeren bir istek 403 alır (tarayıcıdaki web sayfalarına karşı koruma); çerez yok, CORS yok. Protokol sürümleri 2025-03-26, 2025-06-18 ve 2025-11-25.

Kurulum. Test anahtarını TOURAPI_API_KEY ortam değişkeni olarak ayarlayın (kendi kodunuzdakiyle aynı; tek değişken yeterli); yapılandırmaya yalnızca ona yapılan referans girer. Claude Code, projede .mcp.json dosyası:

{"mcpServers": {"tourapi": {"type": "http", "url": "https://portal.example.de/mcp", "headers": {"X-Api-Key": "${TOURAPI_API_KEY}"}}}}

Codex (anahtar, ortam değişkeninden bearer token olarak gönderilir):

codex mcp add tourapi --url https://portal.example.de/mcp --bearer-token-env-var TOURAPI_API_KEY

Antigravity headers içine ortam değişkeni doldurmaz; mcp-remote köprüsü anahtarı ortamdan okur (mcp_config.json içindeki girdi; anahtarı denetlenmemiş bir sürüm almasın diye köprü sürümü sabittir):

{"mcpServers": {"tourapi": {"command": "npx", "args": ["-y", "mcp-remote@0.14.3", "https://portal.example.de/mcp", "--header", "X-Api-Key:${TOURAPI_API_KEY}"]}}}

Portaldaki “Yapay zeka ajanları” sayfası aynı girdileri portalınızın adresiyle gösterir.

12. Otel içerikleri: /v1/content/*

Content API, tur operatörünün otel içeriklerini alıcıların web siteleri ve katalogları için sunar: ana veriler (tesis türü, zincir, adres), konum (koordinatlar), kategoriler, bilgiler, metinler, görseller ve olanaklar. Fiyat ve rezervasyonun yanında ayrı bir yoldur: içerikler hiçbir fiyatı ve müsaitliği değiştirmez.

RotaAmaç
GET /v1/content/hotelsAnahtarın otellerinin dizini, code'a göre artan, sayfalı
GET /v1/content/hotels/{code}Bir otelin içeriği (ETag, If-None-Match → 304)
GET /v1/content/changes?since=…Bir durumdan bu yana değişiklikler, kaydedildikleri sırayla
GET /v1/content/catalogEtiketli kataloglar (tesis türleri, kategori ölçekleri, metin ve görsel türleri, olanaklar)

12.1 Erişim ve kapsam

  • Content API yalnızca ikisi de geçerliyse yanıt verir: platform işletmecisi içerikleri tur operatörü için etkinleştirmiştir ve anahtar içerik yetkisini taşır (tur operatörü yöneticisi API erişimi altında verir, varsayılan kapalı). Aksi halde 403 ERR_CONTENT_NOT_ALLOWED. Bir test anahtarı canlı anahtarının yetkisine sahiptir ve aynı içerikleri okur.
  • Platform işletmecisinin test ortamı olarak yürüttüğü bir tur operatörüne yalnızca bunun için kurulan kurulum hizmet verir. Diğer her yerde etkin değil sayılır: aynı yanıt 403 ERR_CONTENT_NOT_ALLOWED.
  • Bir anahtarın hangi otelleri gördüğünü /v1/destinations'daki gibi anahtar belirler (1.1): müşteri grubu olmadan ve fiyat grubuyla tur operatörünün tüm otelleri, kontenjan grubuyla yalnızca tahsisi olan oteller – müsaitlikten bağımsız. Diğer her otel 404 ERR_HOTEL_NOT_FOUND'dur.
  • Yalnızca tur operatörünün içerik dillerindeki görünür metinler ve görünür, tamamen işlenmiş görseller sunulur. Otelin iletişim bilgileri (telefon, e-posta, web) Content API'de yoktur, boş alan olarak bile.
  • Görseller herkese açık, tahmin edilemeyen adreslerde bulunur (<base>/m/<key>/<size>.jpg, anahtarsız alınabilir, 1 gün önbelleğe alınabilir). Her görselin yanında credit vardır; attributionRequired: true ise atıf görselin yanında gösterilmelidir.
  • Anahtarlar tarayıcı koduna ait değildir (1.1): web sitesi Content API'yi sunucuda okur, tarayıcıya yalnızca görsel adresleri gider.

12.2 Dizin

### inhalt-verzeichnis
GET {{baseUrl}}/v1/content/hotels?pageSize=2
X-Api-Key: {{apiKey}}
{
  "hotels": [
    {"code": "TEST-HOTEL-ALTAKTION", "name": "TEST Altaktion Teneriffa", "destination": "TFS", "contentVersion": 0, "websiteReady": false, "missing": ["general_text", "geo", "category", "images", "amenities"]},
    {
      "code": "TEST-HOTEL-BASE",
      "name": "TEST Basis Palma",
      "destination": "PMI",
      "contentVersion": "{{*}}",
      "updatedAt": "{{*}}",
      "category": {"kind": "official", "scheme": "stars", "value": 4, "superior": true},
      "geo": {"lat": 39.565, "lon": 2.627, "precision": "address"},
      "websiteReady": false,
      "missing": ["images", "amenities"]
    }
  ],
  "nextCursor": "{{*}}",
  "feedToken": "{{*}}",
  "scopeHash": "{{*}}"
}
  • pageSize 1–1000 (varsayılan 500). Sonraki sayfa cursor=<nextCursor> ile alınır; nextCursor yoksa dizin eksiksizdir.
  • contentVersion bir otel için sunulan içeriğin her değişikliğini sayar; 0 = henüz içerik yok (o zaman updatedAt yoktur). category resmi ülke kategorisidir, geo konum – ikisi de yalnızca bakımı yapılmışsa. websiteReady ve missing bir otelin içeriğindeki gibi (12.3).
  • feedToken, /v1/content/changes'in devam ettiği durumdur. Bir dizin geçişinin tüm sayfaları aynı durumu taşır (ilk sayfanınkini).
  • scopeHash, anahtarın otel kümesi değişir değişmez değişir (yeni veya silinmiş otel, tahsis eklendi veya kaldırıldı). Bu akışta yer almaz: o zaman dizini yeniden alın.
  • cursor, feedToken ve next opaktır ve anahtara bağlıdır: başka bir anahtarla, değiştirilmiş olarak veya sunucunun anahtar değişikliğinden sonra 422 ERR_BAD_CURSOR.
### inhalt-verzeichnis-weiter
GET {{baseUrl}}/v1/content/hotels?pageSize=2&cursor={{inhaltCursor}}
X-Api-Key: {{apiKey}}
{
  "hotels": [
    {"code": "TEST-HOTEL-CLOSED", "name": "TEST Geschlossen Mahon", "destination": "MAH", "contentVersion": 0, "websiteReady": false, "missing": ["general_text", "geo", "category", "images", "amenities"]},
    {
      "code": "TEST-HOTEL-DISCOUNT",
      "name": "TEST Rabatt Palma",
      "destination": "PMI",
      "contentVersion": "{{*}}",
      "updatedAt": "{{*}}",
      "category": {"kind": "official", "scheme": "stars", "value": 4},
      "geo": {"lat": 39.5696, "lon": 2.6502, "precision": "locality"},
      "websiteReady": false,
      "missing": ["general_text", "images", "amenities"]
    }
  ],
  "nextCursor": "{{*}}",
  "feedToken": "{{*}}",
  "scopeHash": "{{*}}"
}

12.3 Bir otelin içeriği

### inhalt-hotel
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-BASE?lang=de,tr
X-Api-Key: {{apiKey}}
{
  "code": "TEST-HOTEL-BASE",
  "name": "TEST Basis Palma",
  "destination": "PMI",
  "contentVersion": "{{*}}",
  "updatedAt": "{{*}}",
  "accommodationType": "HOTEL",
  "categories": [{"kind": "official", "scheme": "stars", "value": 4, "superior": true}],
  "address": {"street": "Passeig Marítim 12", "postalCode": "07014", "city": "Palma", "region": "Mallorca", "country": "ES"},
  "geo": {"lat": 39.565, "lon": 2.627, "precision": "address"},
  "facts": {"rooms": 120, "checkInFrom": "14:00", "checkOutUntil": "11:00"},
  "texts": [
    {"type": "GENERAL", "lang": "de", "html": "<p>TEST-Hotel am Strand mit <b>Pool</b> und Garten.</p>\n<p>TEST-Lage: ruhige Bucht.</p>", "updatedAt": "{{*}}"},
    {"type": "GENERAL", "lang": "de", "fallbackFrom": "tr", "html": "<p>TEST-Hotel am Strand mit <b>Pool</b> und Garten.</p>\n<p>TEST-Lage: ruhige Bucht.</p>", "updatedAt": "{{*}}"}
  ],
  "media": [],
  "amenities": [
    {"code": "WIFI_ROOM", "available": true, "charge": "included"},
    {"code": "SPA", "available": false},
    {"code": "DIST_AIRPORT", "available": true, "distanceM": 12000, "ref": "PMI"}
  ],
  "websiteReady": false,
  "missing": ["images", "amenities"]
}
  • Diller: lang olmadan tur operatörünün tüm içerik dillerindeki tüm metinler gelir. lang ile (1–5 dil, ISO 639-1 küçük harf, virgülle ayrılmış) her metin türü ve istenen dil için tam olarak bir kayıt gelir. Metin bu dilde yoksa tur operatörünün varsayılan dili, ardından İngilizce devreye girer; kayıt o zaman istenen dili fallbackFrom olarak taşır (burada: Türkçe metin yok, Almanca gelir). Orada da yoksa kayıt eksik kalır. Görsellerin başlıkları (titles) ve alternatif metinleri (alts) aynı kurala uyar. Tur operatörünün sunmadığı bir dil: 422 ERR_LANGUAGE_NOT_OFFERED (etkin diller warnings içindedir).
  • html yalnızca özniteliksiz p, br, b, strong, i, em, ul, ol, li içerir. machineTranslated: true bir makine çevirisini işaretler.
  • categories: kind official (ülke kategorisi) veya operator (tur operatörünün derecelendirmesi), scheme stars veya keys, value yarım adımlarla 1–5; "4 Superior" value: 4, superior: true'dur, asla 4,5 değil.
  • geo.precision: address, street, locality veya unknown.
  • media gösterim sırasıyla, order: 1 ana görseldir. Her görsel için oluşturulan genişliklerle variants (w320 ile w2048 arası, asla orijinalden geniş değil), her biri genişlik, yükseklik, bayt ve url ile; focus (yüzde olarak x, y) kendi kırpmalarınız için görselin en önemli noktasıdır. id, görsel otelde kaldığı sürece sabittir.
  • amenities: katalogdan kodlu olanaklar (12.5). available: false açıkça mevcut değil demektir; eksik bir olanak bilinmiyordur. Olanağa göre count, distanceM, areaM2, ref (mesafe için havalimanı kodu) ve ücretli olabilen olanaklarda charge (included, extra, unknown) ile.
  • name, destination ve giataCode otelin sözleşmesinden gelir. Bunlardan biri değişirse otel akışta yer alır (12.4, reason: contract) ve bundan sonra detay ile dizin yeni değeri döndürür.
  • websiteReady ve missing: "web sitesine hazır" olgunluk düzeyi – tur operatörünün bir web sitesi için yeterli içerik girip girmediği. Yalnızca bir göstergedir; ne satışı ne de sunumu değiştirir. missing eksik ölçütleri sabit sırayla listeler, boş = web sitesine hazır: general_text (operatörün varsayılan dilinde genel metin), geo (konum), category (kategori veya tesis türü), images (ana görsel dahil en az 5 sunulan görsel), amenities (açıkça "mevcut değil" dahil en az 10 girilmiş özellik). Değerlendirme yalnızca içerikle değişir (yeni contentVersion); operatörün konsolu aynısını gösterir. missing içindeki bilinmeyen değerleri yok sayın (ölçüt eklenebilir). Dizinde de her iki alan bulunur.

Her yanıt bir ETag taşır. İçerik, dil seçimi ve gösterimle birlikte değişir. If-None-Match ile hiçbir şey değişmediği sürece gövdesiz 304 gelir:

### inhalt-hotel-unveraendert
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-BASE?lang=de,tr
X-Api-Key: {{apiKey}}
If-None-Match: {{inhaltEtag}}

lang olmadan tüm içerik dilleri (burada en makine çevirisi olarak):

### inhalt-hotel-alle-sprachen
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-BASE
X-Api-Key: {{apiKey}}
{
  "code": "TEST-HOTEL-BASE",
  "name": "TEST Basis Palma",
  "destination": "PMI",
  "contentVersion": "{{*}}",
  "updatedAt": "{{*}}",
  "accommodationType": "HOTEL",
  "categories": "{{*}}",
  "address": "{{*}}",
  "geo": "{{*}}",
  "facts": "{{*}}",
  "texts": [
    {"type": "GENERAL", "lang": "de", "html": "{{*}}", "updatedAt": "{{*}}"},
    {"type": "GENERAL", "lang": "en", "html": "<p>TEST hotel on the beach with a <b>pool</b> and garden.</p>\n<p>TEST location: quiet bay.</p>", "machineTranslated": true, "updatedAt": "{{*}}"}
  ],
  "media": [],
  "amenities": "{{*}}",
  "websiteReady": false,
  "missing": ["images", "amenities"]
}

12.4 Değişiklikler

### inhalt-aenderungen
GET {{baseUrl}}/v1/content/changes?since={{feedToken}}
X-Api-Key: {{apiKey}}
{"changes": [], "next": "{{*}}", "more": false, "scopeHash": "{{*}}"}
  • since, dizinin feedToken'ı veya önceki yanıtın next değeridir (zorunlu, aksi halde 400 ERR_BAD_REQUEST). pageSize 1–1000 (varsayılan 500).
  • Her kayıtta code, change (upsert = içerik değişti, yeniden alın; removed = otel silindi), upsert için güncel contentVersion ve isteğe bağlı reason (languages = tur operatörünün içerik dilleri değişti, media_ready = bir görsel tamamen işlendi, contract = name, destination veya giataCode sözleşmeyle değişti, removed için deleted). Bilinmeyen değerleri yok sayın.
  • Değişikliklerin kaydedildiği sırayla, boşluksuz; bir otel her sayfada en fazla bir kez yer alır (son durumuyla). more: true = hemen next ile okumaya devam edin.
  • Yalnızca anahtarın otelleri. Otel kümesinin değişmesi akışta değil, scopeHash'te yer alır.
  • Akış 30 gün geriye gider. Daha eski bir durum: 410 ERR_CONTENT_CURSOR_EXPIRED – o zaman dizini yeniden alın ve onun feedToken'ı ile devam edin.

12.5 Katalog

### inhalt-katalog
GET {{baseUrl}}/v1/content/catalog?lang=de
X-Api-Key: {{apiKey}}
{
  "version": "2026.1",
  "languages": ["de", "en", "tr"],
  "defaultLanguage": "de",
  "labelLanguages": ["de"],
  "accommodationTypes": "{{*}}",
  "categorySchemes": [{"code": "sterne", "sort": 1, "labels": {"de": "Sterne"}}, {"code": "schluessel", "sort": 2, "labels": {"de": "Schlüssel"}}],
  "textTypes": "{{*}}",
  "mediaTypes": "{{*}}",
  "amenityGroups": "{{*}}",
  "amenities": "{{*}}"
}
  • languages tur operatörünün içerik dilleridir, defaultLanguage varsayılan dilidir. lang etiket dillerini seçer (de, en, tr; lang olmadan tümü).
  • Her olanak için group, valueType (flag, anzahl, meter, flaeche_m2, meter_mit_bezug), unit ve chargeable. Bir kod asla yeniden yorumlanmaz; locked: true kaldırıldı demektir (okunabilir kalır). version her yeni katalogla değişir; katalog bir ETag taşır.

12.6 Alıcılar için eşitleme

  1. İlk yükleme: dizini sayfa sayfa alın, feedToken ve scopeHash'i saklayın, her otel için içeriği alın ve ETag'i kaydedin.
  2. Sürekli (aşağı yukarı birkaç dakikada bir): changes?since=<token> (ilk çağrıda feedToken, sonrasında en son saklanan next), değişen otelleri yeniden alın, removed olanları silin, next'i saklayın. scopeHash değişirse: hemen 3. adım.
  3. Günlük ve 410 durumunda: dizini yeniden alın ve kendi verilerinizle eşitleyin (eksik otelleri silin, yenilerini alın, contentVersion'ı karşılaştırın).
  4. İçerikleri her zaman If-None-Match ile alın.

Erişim ve kapsam hataları:

### inhalt-ohne-recht
GET {{baseUrl}}/v1/content/hotels
X-Api-Key: {{poolKey}}
{"errorCode": "ERR_CONTENT_NOT_ALLOWED", "message": "dieser API-Key hat kein Inhalts-Recht — der Veranstalter-Admin schaltet es unter API-Zugang frei"}
### inhalt-nicht-im-verzeichnis
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-DISCOUNT
X-Api-Key: {{partnerKey}}
{"errorCode": "ERR_HOTEL_NOT_FOUND", "message": "Hotel 'TEST-HOTEL-DISCOUNT' nicht im Verzeichnis dieses API-Keys"}
### inhalt-sprache-nicht-angeboten
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-BASE?lang=fr
X-Api-Key: {{apiKey}}
{"errorCode": "ERR_LANGUAGE_NOT_OFFERED", "message": "lang: 'fr' ist keine Inhaltssprache dieses Veranstalters (ISO 639-1, klein)", "warnings": ["aktive Inhaltssprachen: de, en, tr (Standard de)"]}
### inhalt-stand-fremder-key
GET {{baseUrl}}/v1/content/changes?since={{feedToken}}
X-Api-Key: {{partnerKey}}
{"errorCode": "ERR_BAD_CURSOR", "message": "since: unlesbar, von einem anderen API-Key oder nicht von diesem Server — den Wert der Vorseite unveraendert zurueckgeben, sonst neu beginnen"}

13. Planlananlar ve sınırlar

Planlananlar: /v1 içinde yalnızca eklemeli değişiklikler yapılır (bölüm 1.9); her değişiklik tarihiyle birlikte değişiklik günlüğünde yer alır. Şu anda mevcut bir entegrasyonun uyarlanmasını gerektiren duyurulmuş bir değişiklik yoktur.

Sınırlar: Sayısal değerler (boyutlar, hızlar, zaman sınırları) "Bir bakışta sınırlar" ekindedir; sandbox'ın kapsamadıkları 11.5'tedir.

Ek: Bir bakışta sınırlar

Ne
İstek başına body
Değer
1 MiB
Ne
Konaklama başına gece
Değer
1–30
Ne
Giriş
Değer
referans tarihinden referans tarihi + 732 güne kadar
Ne
İstek başına yolcu, yaş
Değer
1–20, 0–120
Ne
Rezervasyon başına oda (quantity)
Değer
1–1.000.000
Ne
idemKey, reference
Değer
128 karakter (daha uzunu: 422 ERR_VALIDATION)
Ne
leadPaxName
Değer
255 karakter (daha uzunu: 422 ERR_VALIDATION)
Ne
metadata.correlationId
Değer
64 karakter (daha uzunu: 422 ERR_VALIDATION)
Ne
Anahtar başına hız
Değer
200/sn, burst 400
Ne
Erişim başına test anahtarları
Değer
en fazla 5, 90 güne kadar geçerli; toplam 20/sn, burst 40; tur operatörü başına 2 eşzamanlı arama
Ne
Test rezervasyonları
Değer
erişim başına 5.000 açık, oluşturulduktan 30 gün sonra silinir; test çağrılarının kaydı 7 gün
Ne
Arama süre sınırı
Değer
10 sn
Ne
Arama sayfası
Değer
varsayılan 50, en fazla 100 otel
Ne
Açık arama
Değer
anahtar başına arama profiline göre (pencere, süreler, hedefler, sayfa, zaman bütçesi, istek hızı; tarih matrisi: pencere ve hücreler), GET /v1/limits ile alınabilir; belirtilmezse sayfa 20; cursor 15 dk
Ne
Bağlantı
Değer
15 sn okuma, 15 sn yazma
Ne
Anahtar başına EDF teslimatı
Değer
full 1/saat, yeni changes zinciri 1/dk, sonraki sayfalar/onaylar 5/sn (burst 50); paket yanıtları 10 dakikaya kadar yazabilir
Ne
Content API
Değer
dizin ve akış sayfası 1–1000 (varsayılan 500), lang 1–5 dil, akış 30 gün