Public API docs
Sign in
API docs

Examples

Buyer API v1: flow, rules, error catalogue and examples – every example runs as a test on every build.

The handbook requests in the format of the JetBrains and VS Code REST clients. Lines “# @…” are assertions of the test that runs every example against a fresh instance on every build.

01-preis.http

# Preisauskunft: /v1/price und /v1/prices (Handbuch Abschnitt 3).
# Zusicherungen "# @..." prueft cmd/tourapi-admin/handbuch_test.go (siehe README.md).

### preis-einzeln
# @status 200
# @feld totalCents = 18000
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}]}
}

### preis-guenstigstes-zimmer
# @status 200
# @feld totalCents = 12500
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}]}
}

### preis-guenstigstes-verfuegbares
# @status 200
# @feld room = "DZ"
# @feld availability.available = true
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}]}
}

### preis-alle-verpflegungen
# @status 200
# @feld rooms[0].boards[0].totalCents = 18000
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}]}
}

### preis-alle-verpflegungen-board-ignoriert
# @status 200
# @hinweis board: 'HB' wird bei /v1/prices 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}]}
}

### preis-kundengruppe
# @status 200
# @feld totalCents = 14400
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}]}
}

### preis-kind-bruchcent
# @status 200
# @feld totalCents = 48393
# @feld perTravellerCents[2] = 14433
# @feld breakdown[3].amountExact = "-13.485"
# @feld breakdown[3].amountCents = -1348
# @feld rounding.scope = "Person"
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}]}
}

02-suche.http

# Suche: /v1/search (Handbuch Abschnitt 4).

### suche-ziel
# @status 200
# @feld diagnostics.returned = 3
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}]}
}

### ziele
# @status 200
# @feld destinations[0].code = "ACE"
GET {{baseUrl}}/v1/destinations
X-Api-Key: {{apiKey}}

### suche-unbekanntes-ziel
# @status 422
# @feld errorCode = "ERR_UNKNOWN_DESTINATION"
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}]}
}

### suche-nicht-buchbare
# @status 200
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}]}
}

### suche-seitenweise
# @status 200
# @feld diagnostics.considered = 3
# @merke nextCursor -> naechsteSeite
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}]}
}

02b-suche-offen.http

# Offene Suche: /v1/search/open (Handbuch Abschnitt 4a).

### suche-offen
# @status 200
# @merke nextCursor -> offenCursor
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
}

### suche-offen-weiter
# @status 200
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}}"
}

### suche-offen-je-nacht
# @status 200
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
}

### suche-offen-stamm
# @status 200
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}
}

### suche-offen-ohne-recht
# @status 403
# @feld errorCode = "ERR_OPEN_SEARCH_NOT_ALLOWED"
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}]}
}

### suche-offen-fenster-zu-breit
# @status 422
# @feld errorCode = "ERR_WINDOW_TOO_WIDE"
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}]}
}

### suche-offen-ziel-nicht-erlaubt
# @status 403
# @feld errorCode = "ERR_DESTINATION_NOT_ALLOWED"
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}]}
}

### suche-offen-filter-kaputt
# @status 422
# @feld errorCode = "ERR_BAD_FILTER"
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}
}

02c-suche-termine.http

# Termin-Matrix /v1/search/open/dates (Handbuch Abschnitt 4b) und Grenzen /v1/limits (4c).

### suche-termine
# @status 200
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
}

### suche-termine-je-verpflegung
# @status 200
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
}

### suche-termine-zu-viele-zellen
# @status 422
# @feld errorCode = "ERR_SEARCH_TOO_BROAD"
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
}

### grenzen
# @status 200
# @feld openSearch.allowed = true
GET {{baseUrl}}/v1/limits
X-Api-Key: {{apiKey}}

### grenzen-ohne-recht
# @status 200
# @feld openSearch.allowed = false
GET {{baseUrl}}/v1/limits
X-Api-Key: {{poolKey}}

03-buchung.http

# Buchung (B) und Buchungsinfo: /v1/book, /v1/booking (Handbuch Abschnitt 5).
# Reihenfolge zaehlt: die Beispiele laufen nacheinander gegen dieselbe Welt.

### buchen
# @status 200
# @feld booked = true
# @feld alreadyBooked = false
# @merke reference -> buchungsRef
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
  }
}

### buchen-wiederholt
# @status 200
# @feld alreadyBooked = true
# @feld reference = "{{buchungsRef}}"
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
  }
}

### buchen-anderer-vorgang
# @status 409
# @feld errorCode = "ERR_IDEMPOTENCY_MISMATCH"
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"
}

### buchung-lesen
# @status 200
# @feld status = "confirmed"
GET {{baseUrl}}/v1/booking?ref={{buchungsRef}}
X-Api-Key: {{apiKey}}

### buchung-lesen-kundenreferenz
# @status 200
# @feld reference = "{{buchungsRef}}"
GET {{baseUrl}}/v1/booking?ref=KUNDE-4711
X-Api-Key: {{apiKey}}

### buchen-ohne-preispruefung
# @status 200
# @feld booked = true
# @merke reference -> ohnePreisRef
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"
}

### buchung-lesen-ohne-preispruefung
# @status 200
GET {{baseUrl}}/v1/booking?ref={{ohnePreisRef}}
X-Api-Key: {{apiKey}}

### buchen-gleiche-kundenreferenz
# @status 200
# @feld alreadyBooked = false
# @merke reference -> zweiteRef
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"
}

### buchung-lesen-mehrdeutig
# @status 409
# @feld errorCode = "ERR_REFERENCE_AMBIGUOUS"
GET {{baseUrl}}/v1/booking?ref=KUNDE-4711
X-Api-Key: {{apiKey}}

### buchen-preis-geaendert
# @status 409
# @feld errorCode = "ERR_PRICE_DRIFT"
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
  }
}

### buchen-ausgebucht
# @status 422
# @feld errorCode = "ERR_SOLD_OUT"
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"
}

### buchen-stop-sale
# @status 422
# @feld errorCode = "ERR_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"
}

### buchen-zuteilung-erschoepft
# @status 422
# @feld errorCode = "ERR_GROUP_LIMIT"
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"
}

### buchen-preisgruppe
# @status 200
# @feld booked = true
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"
}

04-storno.http

# Storno (S): /v1/cancel (Handbuch Abschnitt 6). Laeuft nach 03-buchung.http.

### stornieren
# @status 200
# @feld released = true
# @feld alreadyReleased = false
POST {{baseUrl}}/v1/cancel
Content-Type: application/json
X-Api-Key: {{apiKey}}

{"idemKey": "BEISPIEL-0001"}

### stornieren-wiederholt
# @status 200
# @feld alreadyReleased = true
POST {{baseUrl}}/v1/cancel
Content-Type: application/json
X-Api-Key: {{apiKey}}

{"idemKey": "BEISPIEL-0001"}

### buchung-lesen-storniert
# @status 200
# @feld status = "released"
GET {{baseUrl}}/v1/booking?ref={{buchungsRef}}
X-Api-Key: {{apiKey}}

### buchen-nach-storno
# @status 409
# @feld errorCode = "ERR_IDEM_KEY_RELEASED"
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"
}

### stornieren-unbekannt
# @status 404
# @feld errorCode = "ERR_BOOKING_NOT_FOUND"
POST {{baseUrl}}/v1/cancel
Content-Type: application/json
X-Api-Key: {{apiKey}}

{"idemKey": "GIBT-ES-NICHT"}

05-fehler.http

# Fehler und Hinweise (Handbuch Abschnitt 2 und 7).

### fehler-ohne-key
# @status 401
# @feld errorCode = "ERR_UNAUTHORIZED"
POST {{baseUrl}}/v1/price
Content-Type: application/json

{}

### fehler-key-widerrufen
# @status 401
# @feld errorCode = "ERR_UNAUTHORIZED"
POST {{baseUrl}}/v1/price
Content-Type: application/json
X-Api-Key: {{gesperrterKey}}

{}

### fehler-methode
# @status 405
# @feld errorCode = "ERR_METHOD_NOT_ALLOWED"
GET {{baseUrl}}/v1/price
X-Api-Key: {{apiKey}}

### fehler-aufenthalt-zu-lang
# @status 422
# @feld errorCode = "ERR_STAY_TOO_LONG"
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}]}
}

### fehler-anreise-vergangen
# @status 422
# @feld errorCode = "ERR_STAY_IN_PAST"
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}]}
}

### fehler-tippfehler-belegung
# @status 422
# @feld errorCode = "ERR_UNKNOWN_FIELD"
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}]}
}

### fehler-tippfehler-feld
# @status 422
# @feld errorCode = "ERR_BAD_DATE"
# @hinweis unbekanntes Feld 'checke'
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}]}
}

### fehler-verpflegung-nicht-angeboten
# @status 422
# @feld errorCode = "ERR_BOARD_NOT_OFFERED"
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}]}
}

### hinweise-now-und-waehrung
# @status 200
# @hinweis now: '{{gestern}}' ignoriert
# @hinweis currency: angefragt 'USD'
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}]}
}

### fehler-text-zu-lang
# @status 422
# @feld errorCode = "ERR_VALIDATION"
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"
}

### fehler-verpflegung-preis
# @status 422
# @feld errorCode = "ERR_BOARD_NOT_OFFERED"
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}]}
}

06-fairness.http

# Fairness: Rate-Limit je API-Key (Handbuch Abschnitt 8).
# "@instanz drossel": der Test schickt diese Anfragen an eine zweite Instanz mit
# Rate 0,1/s und Burst 1 (statt 200/s, Burst 400), damit 429 in zwei Anfragen entsteht.

### drossel-erste-anfrage
# @instanz drossel
# @status 200
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
# @instanz drossel
# @status 429
# @feld errorCode = "ERR_RATE_LIMITED"
# @kopf Retry-After = 10
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}]}
}

07-betrieb.http

# Betrieb: Gesundheit des Knotens (Handbuch Abschnitt 1.8).

### gesundheit
# @status 200
# @feld status = "ok"
GET {{baseUrl}}/v1/health

08-export.http

# EDF-Lieferung: Vollstand, Aenderungen, Quittung (Handbuch Abschnitt 10).
# Die Hauptinstanz laeuft mit dem Voreinstellungs-Takt (full 1/h, changes 1/min je Key):
# darum ruft jedes Beispiel, das den Takt verbraucht, mit einem eigenen Key ab.

### export-voll
# @status 200
# @kopf X-Export-Epoch
# @kopf X-Export-Seq
# @kopf X-Export-From = 0
# @kopf X-Export-More = false
# @feld tenant = "TEST-TENANT-A"
# @feld scope = ""
# @feld type = "full"
# @merke epoch -> exportEpoch
# @merke to_seq -> exportSeq
GET {{baseUrl}}/v1/export/edf/full
X-Api-Key: {{apiKey}}

### export-partner
# @status 200
# @feld tenant = "TEST-TENANT-A"
# @feld scope = "TEST-PARTNER-DISCOUNT"
GET {{baseUrl}}/v1/export/edf/full
X-Api-Key: {{rabattKey}}

### export-nichts-neu
# @status 204
# @kopf X-Export-Seq
# @kopf X-Export-More = false
GET {{baseUrl}}/v1/export/edf/changes?epoch={{exportEpoch}}&since={{exportSeq}}
X-Api-Key: {{apiKey}}

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

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

### export-stand-unlesbar
# @status 400
# @feld errorCode = "ERR_EXPORT_BAD_CURSOR"
GET {{baseUrl}}/v1/export/edf/changes?epoch={{exportEpoch}}&since=gestern
X-Api-Key: {{apiKey}}

### export-fremder-stand
# @status 409
# @feld errorCode = "ERR_EXPORT_EPOCH"
GET {{baseUrl}}/v1/export/edf/changes?epoch=01J00000000000000000000000&since=1
X-Api-Key: {{partnerKey}}

### export-zu-oft
# @status 429
# @feld errorCode = "ERR_RATE_LIMITED"
# @kopf Retry-After
GET {{baseUrl}}/v1/export/edf/full
X-Api-Key: {{apiKey}}

### export-ohne-recht
# @status 403
# @feld errorCode = "ERR_EXPORT_NOT_ALLOWED"
GET {{baseUrl}}/v1/export/edf/full
X-Api-Key: {{poolKey}}

09-sandbox.http

# Sandbox: Test-Key und Testbuchungen (Handbuch Abschnitt 11). Laeuft nach 08-export.http.
# testKey ist der Test-Zwilling des Basis-Keys apiKey (Seed TEST-SANDBOX).

### sandbox-preis
# @status 200
# @kopf X-TourAPI-Mode = test
# @feld totalCents = 18000
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}]}
}

### sandbox-buchen
# @status 200
# @kopf X-TourAPI-Mode = test
# @feld sandbox = true
# @merke reference -> sandboxRef
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
  }
}

### sandbox-buchung-lesen
# @status 200
# @feld sandbox = true
# @feld status = "confirmed"
GET {{baseUrl}}/v1/booking?ref={{sandboxRef}}
X-Api-Key: {{testKey}}

### sandbox-live-key-sieht-testbuchung-nicht
# @status 404
# @kopf X-TourAPI-Mode = live
# @feld errorCode = "ERR_BOOKING_NOT_FOUND"
GET {{baseUrl}}/v1/booking?ref={{sandboxRef}}
X-Api-Key: {{apiKey}}

### sandbox-stornieren
# @status 200
# @feld sandbox = true
# @feld alreadyReleased = false
POST {{baseUrl}}/v1/cancel
Content-Type: application/json
X-Api-Key: {{testKey}}

{"idemKey": "BEISPIEL-0001"}

### sandbox-szenario-busy
# @status 503
# @kopf Retry-After = 1
# @feld errorCode = "ERR_BOOKING_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"
}

### sandbox-szenario-busy-wiederholt
# @status 200
# @feld booked = true
# @feld alreadyBooked = false
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"
}

### sandbox-live-key-in-testumgebung
# @status 403
# @feld errorCode = "ERR_MODE_MISMATCH"
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"
}

### sandbox-szenario-live-key
# @status 422
# @feld errorCode = "ERR_SCENARIO_NOT_ALLOWED"
GET {{baseUrl}}/v1/destinations
X-Api-Key: {{apiKey}}
X-TourAPI-Sandbox-Scenario: rate_limited

10-inhalte.http

# Abschnitt 12: Hotelinhalte (Content-API).

### inhalt-verzeichnis
# @status 200
# @feld hotels[0].code = "TEST-HOTEL-ALTAKTION"
# @merke feedToken -> feedToken
# @merke nextCursor -> inhaltCursor
GET {{baseUrl}}/v1/content/hotels?pageSize=2
X-Api-Key: {{apiKey}}

### inhalt-verzeichnis-weiter
# @status 200
GET {{baseUrl}}/v1/content/hotels?pageSize=2&cursor={{inhaltCursor}}
X-Api-Key: {{apiKey}}

### inhalt-hotel
# @status 200
# @kopf ETag
# @merke-kopf ETag -> inhaltEtag
# @feld code = "TEST-HOTEL-BASE"
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-BASE?lang=de,tr
X-Api-Key: {{apiKey}}

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

### inhalt-hotel-alle-sprachen
# @status 200
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-BASE
X-Api-Key: {{apiKey}}

### inhalt-aenderungen
# @status 200
# @feld more = false
GET {{baseUrl}}/v1/content/changes?since={{feedToken}}
X-Api-Key: {{apiKey}}

### inhalt-katalog
# @status 200
# @kopf ETag
GET {{baseUrl}}/v1/content/catalog?lang=de
X-Api-Key: {{apiKey}}

### inhalt-ohne-recht
# @status 403
GET {{baseUrl}}/v1/content/hotels
X-Api-Key: {{poolKey}}

### inhalt-nicht-im-verzeichnis
# @status 404
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-DISCOUNT
X-Api-Key: {{partnerKey}}

### inhalt-sprache-nicht-angeboten
# @status 422
# @hinweis aktive Inhaltssprachen: de, en, tr (Standard de)
GET {{baseUrl}}/v1/content/hotels/TEST-HOTEL-BASE?lang=fr
X-Api-Key: {{apiKey}}

### inhalt-stand-fremder-key
# @status 422
GET {{baseUrl}}/v1/content/changes?since={{feedToken}}
X-Api-Key: {{partnerKey}}