Schnittstelle für Buchungssysteme
Damit verbindet ein Gastgeber sein eigenes Buchungssystem mit Urlaub mit Tieren, ohne Kalender-Links. Das Buchungssystem schickt uns Belegung und Preise. Wir melden jede Reservierung und Buchung innerhalb einer Minute an einen Webhook, nach der Zahlung mit Name, Kontakt und Anschrift des Gastes. Die Schnittstelle ist in der Testphase und wird je Gastgeber freigeschaltet.
Grundlagen
- Adresse
https://www.urlaub-mit-tieren.com/api/v1/. Genau so, mit https und www. Andere Schreibweisen lehnen wir mit 403 ab, ohne Weiterleitung: Über http wäre der Schlüssel schon ungeschützt unterwegs, und beim Weiterleiten wird aus einem PUT oft ein GET.- Anmeldung
- Kopfzeile
Authorization: Bearer umt_…. Den Schlüssel legt der Gastgeber unter Meine Unterkünfte, Schnittstelle an. Er sieht nur die Unterkünfte dieses Gastgebers. - Format
- JSON in UTF-8, Datum als
JJJJ-MM-TT, Zeitpunkte nach ISO 8601, Beträge in Euro als Zahl. Der Abreisetag ist nie belegt. - Fehler
- 401 ohne gültigen Schlüssel, 403 ohne Freischaltung, 404 für fremde Unterkünfte und Vorgänge,
409 von der Bremse (siehe Belegung) und bei Preisen, solange Smoobu die Unterkunft pflegt,
413 bei einem Rumpf über 1.000.000 Bytes (Umlaute zählen doppelt), 422 bei ungültigen Daten mit
messageunderrorsje Feld, 429 bei mehr als 60 Aufrufen in der Minute je Schlüssel, davon höchstens 20 mit PUT, je Gastgeber bei mehr als dem Doppelten, und vor der Anmeldung bei mehr als 120 Aufrufen in der Minute von derselben IP-Adresse, egal mit welchem Schlüssel.
Ablauf
- Unterkünfte abrufen und je Unterkunft im Buchungssystem unsere Nummer eintragen.
- Bei jeder Änderung die vollständige Belegung senden, dazu mindestens einmal am Tag. Bleibt sie länger als einen Tag aus, sehen wir nach.
- Preise und Mindestaufenthalt senden, wenn sie sich ändern.
- Webhook eintragen und jede Meldung als Reservierung oder Buchung im Buchungssystem anlegen oder aktualisieren.
- Buchungen, die von uns kommen, in der Belegung mit unserer Vorgangsnummer als
kennungzurückschicken oder weglassen. Sonst sperrt sich eine Reservierung gegen den eigenen Gast. - Kalender-Links, die der Gastgeber bei uns eingetragen hat, und eine Smoobu-Anbindung ruhen, sobald die erste Belegung über die Schnittstelle angekommen ist. Sperren aus ruhenden Links übernehmen wir, wenn die Schnittstelle dieselben Nächte meldet. Danach kann der Gastgeber die Links entfernen.
Unterkünfte
GET /api/v1/unterkuenfte
{
"unterkuenfte": [
{
"id": 1,
"titel": "Ferienwohnung am Mühlbach",
"status": "live",
"ort": "Musterstadt",
"url": "https://www.urlaub-mit-tieren.com/unterkunft/ferienwohnung-am-muehlbach/",
"grundpreis": 89.0,
"mindestaufenthalt": 2,
"max_personen": 4,
"belegung_erhalten_am": "2026-10-25T14:02:11+02:00",
"preise_erhalten_am": null
}
]
}
Zum Prüfen der Anmeldung gibt es dazu GET /api/v1/konto.
Belegung senden
PUT /api/v1/unterkuenfte/{id}/belegung
Immer die vollständige Belegung ab heute. Was nicht darin steht, geben wir frei, aber nur, was diese
Schnittstelle gesperrt hat. Sperren, die der Gastgeber bei uns selbst setzt, bleiben stehen. Sperren aus
Kalender-Links und Smoobu ruhen neben der Schnittstelle: Sie bleiben stehen, bis der Gastgeber den Link
entfernt. Meldet die Schnittstelle dieselben Nächte, übernehmen wir sie, und auf Nächten unserer eigenen
Reservierungen und Buchungen geben wir sie frei. Höchstens 1.500 Zeiträume. Was über 1.100 Tage hinausreicht, kappen wir dort.
Zusammen höchstens 5.000 Nächte ab heute.
Bremse: Würde ein Aufruf mehr als die Hälfte von mindestens 14 gesperrten Nächten auf einmal freigeben,
geben wir nichts frei, außer Nächten unserer eigenen Vorgänge, die der Aufruf zum ersten Mal mit unserer
Vorgangsnummer meldet, und antworten mit 409. Neue Sperren aus demselben Aufruf übernehmen wir trotzdem, die Antwort
nennt sie in gesperrte_tage. Ist das Freigeben gewollt, denselben Aufruf mit
"erzwingen": true wiederholen. Hast du gerade einen unserer Vorgänge gelöscht
(verfallen oder storniert) und greift deshalb die Bremse, sende erzwingen nur, wenn
sich an deiner Liste sonst nichts geändert hat. Sonst gibt es auch frei, was aus gutem Grund festgehalten wird. Bei 5xx oder Zeitüberschreitung einfach erneut senden,
der Aufruf lässt sich beliebig oft wiederholen.
{
"belegt": [
{ "anreise": "2026-11-02", "abreise": "2026-11-09", "kennung": "GS-4711", "notiz": "Booking.com" },
{ "anreise": "2026-12-20", "abreise": "2026-12-27", "kennung": "UMT-26-04012" }
]
}
Antwort:
{
"ok": true,
"gesperrte_tage": 7,
"eigene_buchungen": 1,
"konflikte": []
}
eigene_buchungen zählt Zeiträume mit der Vorgangsnummer einer laufenden Reservierung
oder Buchung bei uns. Davon übergehen wir genau die Nächte, die dieser Vorgang bei uns belegt.
konflikte nennt je Fall kennung, anreise,
abreise, vorgang, status und
grund. Bei ueberschneidung fällt ein Zeitraum auf eine unserer
Buchungen oder Reservierungen: Entweder fehlt dort unsere Vorgangsnummer, oder der Zeitraum ist doppelt vergeben.
Bei abweichung trägt ein Zeitraum unsere Vorgangsnummer, belegt aber Nächte außerhalb
dieses Vorgangs, etwa weil er verlängert wurde oder bei uns verfallen ist. Diese Nächte sperren wir.
Wir bekommen in beiden Fällen eine Nachricht.
Preise senden
PUT /api/v1/unterkuenfte/{id}/preise
Je Tag Preis für die Nacht, Preis für höchstens zwei Personen und Mindestaufenthalt bei Anreise an diesem Tag.
Nur die genannten Tage und Felder ändern sich, null nimmt einen Wert heraus,
dann gilt der Grundpreis bzw. der Mindestaufenthalt der Unterkunft. Preise mindestens 1 Euro,
Mindestaufenthalt 1 bis 90 Nächte. Höchstens 1.100 Tage je Aufruf,
vergangene Tage und Tage nach dem Horizont übergehen wir.
{
"tage": [
{ "datum": "2026-12-20", "preis": 119.0, "min_naechte": 7 },
{ "datum": "2026-12-21", "preis": 119.0, "preis_paar": 99.0 },
{ "datum": "2027-01-10", "preis": null }
]
}
Antwort: {"ok": true, "gesetzte_tage": 3, "uebergangene_tage": 0}
Kalender gegenprüfen
GET /api/v1/unterkuenfte/{id}/kalender?von=2026-11-01&bis=2026-11-30
Unser Kalender, wie Gäste ihn sehen, je Tag mit frei, gesperrt,
gebucht, reserviert, quelle
(wer sperrt: api, manuell für den Gastgeber bei uns, sonst der andere Kalender),
preis, preis_paar und min_naechte.
Ohne Angabe die nächsten 90 Tage, höchstens 400.
Reservierungen und Buchungen
Ein Vorgang ist eine Reservierung oder Buchung. Seine id ist unsere Vorgangsnummer und
bleibt von der Zusage bis zur Abreise gleich, nach der Zahlung kommt eine eigene buchungsnummer dazu.
- reserviert
- : Der Gastgeber hat zugesagt, die Zahlung ist offen. Der Zeitraum ist bei uns vorgemerkt,
bitte sperren, bis
verfallenoderstorniertkommt.reserviert_bisist die Zahlungsfrist und kann sich verschieben, etwa durch eine Nachfrist bei Überweisung, das kommt alsbuchung.geaendert. Nur der Name des Gastes, noch keine Kontaktdaten. - bestaetigt
- : bezahlt. Mit E-Mail, Telefon, Anschrift und Nachricht des Gastes und den Beträgen.
- storniert
- : bezahlt und danach storniert, der Zeitraum ist frei.
- verfallen
- : Reservierung ohne Zahlung abgelaufen oder abgesagt, der Zeitraum ist frei. Eine bezahlte Buchung verfällt nie. Fehlt sie bei uns durch einen Fehler, senden wir kein Ereignis und klären es mit dir. Zeigt ein Abruf eine Buchung, die dein System als bezahlt kennt, als reserviert oder verfallen, gib den Zeitraum bitte nicht frei, sondern sprich uns an.
GET /api/v1/buchungen?unterkunft=1&abreise_ab=2026-10-01
GET /api/v1/buchungen/{vorgangsnummer}
Die Liste enthält reservierte, bestätigte und stornierte Vorgänge mit Abreise ab gestern
(oder ab abreise_ab, höchstens 400 Tage zurück). Ein einzelner Vorgang ist auch
als verfallen abrufbar. Antwortet der Einzelabruf zu einer Nummer, die das
Buchungssystem schon kennt, mit 404, ist der Vorgang bei uns gelöscht und der Zeitraum frei. War die Buchung
schon bezahlt, gib den Zeitraum auch dann nicht frei, sondern sprich uns an.
{
"id": "UMT-26-04012",
"buchungsnummer": "UMT-26-04013",
"unterkunft_id": 1,
"status": "bestaetigt",
"anreise": "2026-12-20",
"abreise": "2026-12-27",
"naechte": 7,
"reserviert_bis": null,
"personen": 3,
"kinder_alter": [6],
"tiere": [ { "art": "hund", "anzahl": 1, "groessen": ["mittel"] } ],
"tiere_text": "1× Hund (mittel, bis 25 kg)",
"gast": {
"name": "Anna Beispiel",
"email": "anna@example.org",
"telefon": "+49 170 0000000",
"anschrift": { "strasse": "Hauptstraße 1", "plz": "97222", "ort": "Musterstadt", "land": "DE" }
},
"nachricht": "Wir kommen gegen 16 Uhr an.",
"betraege": {
"waehrung": "EUR",
"unterkunft": 833.0,
"kurtaxe": 0.0,
"spende": 0.0,
"gast_gezahlt": 833.0,
"erstattet": 0.0,
"provision": 108.29,
"auszahlung": 724.71
},
"link": "https://www.urlaub-mit-tieren.com/dashboard/anfrage/412/gespraech/",
"erstellt_am": "2026-10-25T10:14:03+02:00",
"geaendert_am": "2026-10-25T10:31:47+02:00"
}
Bei einer Reservierung stehen in betraege nur unterkunft,
kurtaxe, spende und gast_zahlt.
spende ist die freiwillige Spende des Gastes für den Tierschutz, sie geht nicht an den
Gastgeber. auszahlung ist, was an den Gastgeber geht, Kurtaxe eingeschlossen. Nach einem Storno ohne Kontaktdaten,
das Buchungssystem hat sie aus der Bestätigung.
Webhook
Bei jeder Änderung schicken wir ein POST an die Adresse, die der Gastgeber einträgt
(nur https auf Port 443, ohne Zugangsdaten in der Adresse, nur öffentlich erreichbare Server).
Typen: buchung.reserviert, buchung.bestaetigt,
buchung.geaendert, buchung.storniert,
buchung.verfallen, dazu test vom Knopf im Gastgeberbereich.
In buchung steht der Vorgang wie oben, und zwar im neuesten Stand. Ist ein Vorgang
bei uns gelöscht (der Gast hat seinen Daten widersprochen), steht darin nur id,
unterkunft_id, geaendert_am und
status mit storniert oder
verfallen: Zeitraum freigeben, außer die Buchung war schon bezahlt.
Maßgeblich ist deshalb buchung.status, nicht der Typ:
Mit der id als Schlüssel anlegen oder aktualisieren, dann ist auch eine doppelte Meldung kein Problem.
{
"id": 57,
"typ": "buchung.bestaetigt",
"erstellt_am": "2026-10-25T10:31:52+02:00",
"buchung": { "id": "UMT-26-04012", "status": "bestaetigt", "...": "..." }
}
Jede Meldung trägt X-UMT-Signatur: t=<Unixzeit>,v1=<HMAC>, gebildet als
HMAC-SHA256 über Unixzeit + "." + Rumpf mit dem Webhook-Secret. Prüfen in PHP:
$rumpf = file_get_contents('php://input');
preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $_SERVER['HTTP_X_UMT_SIGNATUR'] ?? '', $m);
$soll = $m ? hash_hmac('sha256', $m[1].'.'.$rumpf, $secret) : '';
if (! $m || ! hash_equals($soll, $m[2]) || abs(time() - (int) $m[1]) > 300) {
http_response_code(400);
exit;
}
$ereignis = json_decode($rumpf, true);
// $ereignis['buchung']['id'] anlegen oder aktualisieren, $ereignis['buchung']['status'] übernehmen
http_response_code(200);
Erwartet wird eine 2xx-Antwort innerhalb von acht Sekunden, Weiterleitungen folgen wir nicht. Die Antwort lesen wir
nicht, sie darf höchstens 64 KB groß sein. Sonst versuchen wir es nach 1, 2, 5, 10, 30 und 60 Minuten und dann in
größer werdenden Abständen, zusammen gut 30 Stunden. Auf 4xx (außer 408 und 429) folgen nur drei Versuche, denn
eine Absage ändert sich durch Warten nicht. Meldungen zu einem Vorgang kommen in ihrer Reihenfolge: Hängt eine,
warten die späteren zu diesem Vorgang, Meldungen zu anderen Vorgängen kommen weiter. Holst du daneben selbst ab,
kann ein älterer Stand nach einem neueren ankommen: Maßgeblich ist der mit dem späteren
geaendert_am. Was wir aufgeben, bleibt über
/ereignisse abrufbar.
Ereignisse nachholen
GET /api/v1/ereignisse?nach=57&limit=100
Alle Ereignisse nach einer Nummer, in derselben Form wie der Webhook, höchstens 200 je Aufruf.
weitere: true heißt, dass noch mehr da sind. Damit lässt sich auch ganz ohne Webhook arbeiten,
dann am besten jede Minute abfragen. Lässt sich ein Vorgang gerade nicht abrufen, steht dort
"buchung": null (der Webhook wartet dann und versucht es später). Dann nichts ändern,
nach der Reparatur kommt ein neues Ereignis mit dem Stand. In beiden Wegen steht dort null,
wenn die Unterkunft nicht mehr zu deinem Konto gehört oder eine schon bezahlte Buchung bei uns fehlt.
Du willst dein Buchungssystem anbinden?
Die Schnittstelle ist in der Testphase, wir schalten sie je Gastgeber frei. Schreib uns, welches Buchungssystem du nutzt, dann melden wir uns.