20 KiB
BusProNet XML API — working reference
Condensed from the vendor PDF docs/bpn_docs.pdf (41 pages, German), keeping what this project
uses or plausibly could. The PDF stays authoritative; a few of its sentences are mangled by a
broken font encoding, so anything unclear there is flagged rather than guessed at.
The PDF lags the live API. The vendor has shipped elements since it was written —
<ausstiege>(return boarding points) is one confirmed example. So absent from the PDF means "not in this snapshot", never "unsupported". Treat the document as a floor on what the API accepts, not a ceiling, and prefer a livebuchungsart=Anfrageprobe over an argument from silence.
Read this alongside buspro-database-reference.md (what BusPro actually stores) — the two answer
different questions, and where they disagree the schema is the record of what happened.
Use it for: what an element is called and what it may contain; which codes a failure can return; which documented capabilities we are not using; and which elements we send that the vendor never documented — those are the ones that can break without warning.
Envelope
Every request is <anfrage> with <user>, <key> and <satz typ="…"/>.
key = md5( user + password + date("YYYYMMDD") + satz-typ )
Per satz type and per day. ApiClient::createKey() implements it, and sendRawXml() regenerates
the key before sending, so a hand-built payload can carry a placeholder.
Satz types
Documented: ADRESSFELDER, ADRESSFELDERGUTSCHEIN, ADRESSFELDERKATALOGANFRAGE,
AGENTUREN, AGENTURKONTO, AKTIONSGUTSCHEIN, BUCHUNG, BUCHUNGAENDERUNG, CRM,
CRMKATALOGANFRAGE, GUTSCHEINBUCHUNG, GUTSCHEINDRUCK, GUTSCHEINPRUEFUNGEINLOESUNG,
GUTSCHEINVORLAGEN, GUTSCHEINVORSCHAU, GUTSCHEINZAHLUNGSARTEN, KATALOGANFRAGE,
KUNDENKONTO, KUNDENLISTE, MOEGLICHEAENDERUNGEN, PRODUKTDATEN, PRODUKTE, SITZPLATZ,
STAMMHOTELS, STAMMLAENDER, STAMMVERSICHERUNGEN, STAMMZUSTIEGE, VERFUEGBARKEIT,
VERFUEGBARKEITHOTEL, ZAHLUNG, ZAHLUNG2, ZAHLUNGSARTEN, ZAHLUNGSARTEN2.
SITZPLATZAUSWAHL and ZAHLUNG4 are named in prose without their own section.
Used here but undocumented: VERFUEGBARKEIT2 (ApiClient::TYPE_AVAILABILITY_EXTENDED) — the
extended availability call returning buchungstatusmoeglich and per-service pflicht/frei.
Its contract is observed, not specified.
Documented but unused here: SITZPLATZ/SITZPLATZAUSWAHL, KUNDENLISTE, ZAHLUNG2,
ADRESSFELDER, STAMMHOTELS, STAMMVERSICHERUNGEN, STAMMZUSTIEGE, KATALOGANFRAGE.
BUCHUNG — the create contract
<buchungsart> decides what the call does:
| Value | Effect |
|---|---|
Anfrage |
Enquiry. Validates and returns the full price breakdown. Nothing is stored. |
Buchung |
Stores the booking. |
<status> at the root: F Festbuchung · O Optionsbuchung · A Anfragebuchung. Gated by
error 962 ("Buchungsstatus #1# kann nicht verarbeitet werden, nur Status #2# möglich"), and by
982/983 when the travel forbids options or enquiries outright.
Two accommodation models
The base payload books accommodation as an ordinary service:
<unterbringungen>
<unterbringung idleistung="22341" anzahl="1" zuordnung="1,2"/>
</unterbringungen>
For the Ferienziel domain — what this app books — unterbringungen is replaced by
ferienzielunterbringungen, and a root <idpartner> added:
<ferienzielunterbringungen>
<ferienzielunterbringung idzimmer="2" kategorie="Ik" idverpflegung="2"
anreise="30.01.2018" abreise="06.02.2018"
anzahl="1" zuordnung="1,2"/>
</ferienzielunterbringungen>
…
<idpartner>78687</idpartner>
<idpartner>is introduced as the element you add when switching to the Ferienziel model, and914 Partner-ID fehltguards it. Note it is not confined to bookings that have a room: a teamer booking with zeroBuchungTNFerienzielrows still returns<idpartner>222925</idpartner>fromVorgang_Details. Where that comes from is unresolved —Buchunghas no partner FK, so either it is stored onBuchung.IDAdresse_FS_ExtraFerienzielPartner, or it is derived from the travel. That booking's product has exactly one hotel, so the response cannot distinguish the two; one read-only query settles it.
Accommodation is mandatory — 913 Unterbringungen fehlen / 916 Ferienziel-Unterbringungen fehlen. Confirmed empirically on the test system 2026-09-02, not just documented: see
"Validation gate order" below. Room dates are cross-checked against the transport legs (921,
923).
Full documented request shape
<anfrage>
<user/><key/><satz typ="BUCHUNG"/>
<buchungsart>Anfrage|Buchung</buchungsart>
<status>F|O|A</status>
<idagentur/> <idcrm/> <idreise/> <idpartner/>
<fremdvorgang/> <!-- your own reference, stored as Buchung.FremdVorgang -->
<datenschutztfmp/> <!-- ad consent, combinable: T Telefon, F Telefax, M EMail, P Post -->
<anmelder>
name, vorname, anrede, titel, geschlecht, nationalitaet, geburtsdatum,
<anschrift>strasse, plz, ort, ortsteil, land</anschrift>
<kommunikation>telefonmobil, email, telefonprivat</kommunikation>
<bemerkung/>
</anmelder>
<teilnehmerliste>
<teilnehmer id="1">
…same personal fields…
<wünsche><unterbringungswunsch/><beförderungswunsch/></wünsche>
<aktionscode/>
</teilnehmer>
</teilnehmerliste>
<beförderungen><beförderung idleistung anzahl zuordnung/></beförderungen>
<ferienzielunterbringungen>…</ferienzielunterbringungen>
<zusatzleistungen><zusatzleistung idleistung anzahl zuordnung/></zusatzleistungen>
<zustiege><zustieg idzustieg anzahl zuordnung/></zustiege>
<sitzpläne><sitzplan idleistung idsitzplan sitzplätze="1B;1A"/></sitzpläne>
<versicherungen><versicherung idversicherung anzahl zuordnung/></versicherungen>
<gutscheine><gutschein gutscheinnr einloesecode/></gutscheine>
<zuschlaege>
<zuschlag text betrag anzahl zuordnung
provision altersrabatt gruppenrabatt frühbucherrabatt bzberechnung/>
</zuschlaege>
<zahlung idzahlungsart bezeichnung art>
<bankverbindung kreditinstitut bic iban kontoinhaber/>
</zahlung>
</anfrage>
zuordnung is a comma-joined list of 1-based teilnehmer/@id values throughout.
Responses
buchungsart=Anfrage → <buchung>möglich</buchung> plus <preise>: one <preis> per line with
position, art, unterart, bezeichnung, terminvon/terminbis, anzahl, zuordnung,
preis, gesamtpreis, id; then <gesamtpreis> and <zahlungsbedingungen> with <anzahlung>
and <restzahlung> (each betrag + termin). art values seen: BEF transport, UNT
accommodation, SON other, ERM discount, VER insurance.
buchungsart=Buchung → <buchung>erfolgt</buchung>, <vorgang>, <rechnung>, <idbuchung>,
<email>, <pdfpasswort>.
Validation gate order (measured)
Established with buchungsart=Anfrage against the test system on 2026-09-02, by mirroring a real
room-less teamer booking (Vorgang 110628) element for element:
- Accommodation. No
ferienzielunterbringungen→916, regardless of everything else being valid. Nothing further is evaluated. - Personal data, once a room is present →
650with the field list "Fehlende Anmelder/Teilnehmer Daten … Nachname, Vorname, Strasse, Plz, Ort, Telefon-Mobil, Mail-Adresse, Geburtsdatum, Nationalität", for the Anmelder and every participant.
So sending idadresse/idadresseperson alone is not enough on create — BusPro validates the
personal fields whether or not it could resolve them from the ids. Echoing the record back
(idadresse + idadresseperson + full data) narrows the complaint to whatever that record is
genuinely missing, which is a useful way to audit an address: the placeholder account 19465:41003
fails on Telefon-Mobil and Nationalität because its Vorgang_Details record has only
telefonprivat and no nationalitaet.
Note 650 again: both of those are hard rejections delivered under the code
Notification::isError() treats as success.
BUCHUNGAENDERUNG — the edit contract
Structurally identical to BUCHUNG plus <idbuchung>, and the responses follow buchungsart
the same way. Ask MOEGLICHEAENDERUNGEN first (<idreise>), which answers:
<änderungen>
<änderung art="anzahl_teilnehmer" möglich="False"/>
<änderung art="beförderung" möglich="False"/>
<änderung art="zustieg" möglich="False"/>
<änderung art="sitztplatz" möglich="False"/> <!-- vendor's typo -->
<änderung art="unterbringung" möglich="False"/>
<änderung art="zusatzleistung" möglich="True" möglichbiszum="23.02.2020"/>
<änderung art="teilnehmerdaten" möglich="False"/>
</änderungen>
How this app maps onto the contract
BookingPayloadBuilder::buildCreatePayload() builds create; buildBasePayload() + the
addTo…Payload() helpers build edit; ApiClient::createBooking()/updateBooking() wrap them.
| Documented element | Sent on create | Sent on edit | Notes |
|---|---|---|---|
buchungsart |
yes | yes | Edit hardcodes Buchung (ApiClient.php:233) |
status |
yes | yes | Edit replays the value read back from BusPro — see the caveat below |
idreise, idpartner, idagentur |
yes | yes | idpartner sent unconditionally, even with no room |
anmelder |
yes | yes | Create builds it from participant[0]; edit uses PersonalData::toPayload() |
anmelder/bemerkung |
— | yes | Via PersonalData::toPayload() |
teilnehmerliste |
yes | yes | |
wünsche (unterbringungswunsch, beförderungswunsch) |
yes | yes | |
aktionscode |
yes | yes | Promo code, paired with AKTIONSGUTSCHEIN |
beförderungen, zusatzleistungen, zustiege |
yes | yes | Built by ServiceMappingCollector |
ferienzielunterbringungen |
yes | yes | Create omits the node entirely when no room maps; edit always emits the empty skeleton |
versicherungen |
yes | yes | |
gutscheine |
yes | yes | Paired with GUTSCHEINPRUEFUNGEINLOESUNG |
zahlung + bankverbindung |
yes | yes | |
idcrm |
no | no | |
fremdvorgang |
no | no | Would give our own bookings a stable external reference |
datenschutztfmp |
no | no | Advertising consent — we track newsletter consent locally instead |
sitzpläne |
no | no | See Seats |
zuschlaege |
no | no | Free-text manual surcharge or discount with its own amount |
Newer than the PDF
These are absent from the document but work against the live API — vendor additions that post-date it. Listed so nobody mistakes the PDF's silence for a contract violation, and so the gaps in this reference are known.
| Element | Where | Note |
|---|---|---|
<ausstiege><ausstieg idzustieg …/></ausstiege> |
create and edit | Return boarding points. Confirmed as a recent vendor addition; the PDF documents only <zustiege>. |
<status> inside <teilnehmer> |
create (hardcoded F) and edit |
Not in the PDF in either direction, though Vorgang_Details returns status_teilnehmer. |
<login>J</login> on KUNDENKONTO |
probe command | Passwordless read escape hatch; see bpn-login-j-passwordless-reads. |
VERFUEGBARKEIT2 |
availability path | Whole satz type post-dates the PDF. Returns buchungstatusmoeglich and per-service pflicht. |
When one of these needs pinning down, the authoritative move is a live Anfrage call, not a
reading of this file.
Caveat: the edit flow replays a derived status
buildBasePayload() sends 'status' => $bookingData->status, and Booking::$status is parsed
from the response's root <status> (BookingParser.php:38). But Buchung has no status
column — the root value is an aggregate over BuchungTN.Status, the only real store
(<status>F</status> beside <status_teilnehmer>F/F/…/F</status_teilnehmer> in
tests/Resources/booking_data.xml). So on a booking whose participants do not all share one
status, the edit flow sends an aggregate back as a directive. Worth confirming against a mixed
booking before relying on it.
Gap: edit never dry-runs
Create is two-phase — Anfrage to validate and price, then Buchung. Edit sends
buchungsart=Buchung directly (ApiClient.php:233), even though BUCHUNGAENDERUNG supports
Anfrage identically. An enquiry pass would surface 913/924/962 and return the recalculated
price before mutating a live booking.
Seats
SITZPLATZ asks BusPro to pick seats:
<satz typ="SITZPLATZ"/>
<idreise/><idleistung_hin/><idleistung_rueck/><anzahl_personen/>
<wunsch_vornemittehinten>V|M|H</wunsch_vornemittehinten>
<wunsch_linksrechts>L|R</wunsch_linksrechts>
<wunsch_fenstergang>F|G</wunsch_fenstergang>
Response: one <sitzplan> per direction with sitzplaetze="0D;1D;1C", idsitzplan,
richtung="H|R", bezeichnung, idleistung, and grafik (base64 PNG of the seat map). Where
seats carry surcharges the response adds <zuschläge> (id, bezeichnung, preis, colours) and
the request must then carry idzuschlag. SITZPLATZAUSWAHL returns the raw plan structure
instead of a picture.
Chosen seats go back into a booking via <sitzpläne>, landing in
BuchungTNLeistungZusatz.Sitzplatz / IDSitzplan_FS. Guarded by 933 Sitzplan mit ID #1# bei Leistung #2# nicht freigegeben and 304 Es konnte nicht für alle Teilnehmer ein Sitzplatz ermittelt werden.
KUNDENKONTO
<art>: Adressdaten, Adressdaten_Ändern, Adresse_Neu, SelektionCRM,
SelektionCRM_Ändern, Vorgänge, Vorgangdruck, Voucherdruck, Dokumentdruck,
Passwort_Anfrage, Newsletter, Adressdokument, Vorgang_Details, Prüfung, PrüfungEmail,
Ausweisdaten_Ändern, Stornoanfrage, Storno, Versicherungspolice.
Stornoanfrage answers stornierung_moeglich + stornopreis; Storno answers storniert —
the documented way to cancel, including cleaning up a test booking. Ausweisdaten_Ändern takes a
<personen> list with <ausweis> data and answers änderung. Versicherungspolice returns
per-policy document URLs.
AGENTURKONTO
<art>: Vorgänge, Vorgangdruck, Rechnungdruck, Hotelgutscheine, Hotelgutscheindruck,
OptionInFest — the last converts a held option into a firm booking, answering
<optioninfestumgewandelt>true</optioninfestumgewandelt>. Vorgangdruck takes idbuchung,
Rechnungdruck takes idrechnung, Hotelgutscheindruck takes idgutschein; print arts return
base64 PDFs.
Availability and payment
VERFUEGBARKEIT takes <idreise> plus a <leistungen><leistung id="…"/></leistungen> filter and
answers <reise id status> plus per-service status ("buchbar"), frei, preis,
preisfaellig. VERFUEGBARKEIT2, which we actually use, additionally reports
buchungstatusmoeglich and pflicht but is undocumented.
ZAHLUNGSARTEN2 lists payment methods; ZAHLUNG2 records a payment against a booking
(vorgang, idbuchung, datum, betrag, idzahlungsart, zahlungstext — all mandatory).
ZAHLUNG reports a credit-card authorisation after booking; the amount must equal the confirmed
anzahlung or errors 1011-1018 follow.
Error codes
Returned as <satz typ="HINWEIS"><nr>…</nr><text>…</text></satz>. #1#/#2# are substituted at
runtime.
650is not a success code. Its documented text is a bare#1#— a generic message carrier whose entire content is substituted.Notification::isError()(src/BusProNet/Model/Notification.php:33-36) treats it as success, which happens to hold on theKUNDENKONTOpaths but not onBUCHUNG/BUCHUNGAENDERUNG, where the same code delivers real rejections such as "Es wurde nicht jedem Teilnehmer eine Pflichtleistung zugeordnet".BookingEditSubmitter.php:81therefore reports some genuine failures as INFO.
Envelope / auth — 100 Anfrageknoten fehlt · 101 Satzknoten fehlt · 102 Ungültiger Satztyp · 103/104 User fehlt/fehlerhaft · 105/106 Key fehlt/fehlerhaft · 999 Systemfehler
Lookup — 200 Keine Einträge · 201/202 Partner · 203 Ungültiger Termin · 300 IDReise fehlt · 301 Reise nicht gefunden · 302 IDLeistung (Hin & Rück) fehlt · 303 IDLeistung passt nicht zu IDReise · 304 Kein Sitzplatz für alle Teilnehmer ermittelbar · 305 Anzahl Personen fehlt · 306/307 Leistungen · 308 Keine Zahlungsarten · 400 IDPartner fehlt · 401 Partner nicht gefunden · 402 Bis-Termin fehlt · 403 Keine Unterbringungsleistungen gefunden
Customer / address — 500 Art fehlt · 502 Keine Kunden · 840-842 Personen · 850 IDAdresse und EMail fehlen · 851 Passwort fehlt · 852 Art fehlt · 853 ID, EMail oder Passwort falsch · 855 Keine Daten für Adresse · 860 Adressdaten fehlen · 862-867 Selektionsmerkmale / CRM-Aktionen · 870-875 Newsletter · 880-882 Dokumente · 885-891 Pflichtfelder (Name, Vorname, Geschlecht, EMail, PLZ, Straße) · 889 Kunde bereits angelegt (ID #1#)
Booking (create and edit) — 900 Buchungsart fehlt · 901 Buchungsart falsch · 902 Status fehlt · 903 Status falsch · 904/905 Agentur · 906 Reise-ID fehlt · 907 Reise nicht gefunden · 908 Reise ist storniert · 909 Beförderungen fehlen · 910 Leistung gehört nicht zur Reise · 911 Leistung gehört nicht zum Produkt · 912 Beförderungsleistung fehlt · 913 Unterbringungen fehlen · 914 Partner-ID fehlt · 915 Partner nicht gefunden · 916 Ferienziel-Unterbringungen fehlen · 917 Zimmer-IDZ fehlt · 918 Kategorie fehlt · 919 Verpflegungs-ID fehlt · 920 Anreise fehlt · 921 Anreise passt nicht zur Beförderungsleistung (Hinfahrt) · 922 Abreise fehlt · 923 Abreise passt nicht zur Beförderungsleistung (Rückfahrt) · 924 Ferienziel: Keine Preise zu den Daten gefunden · 925 Preis zu den Daten nicht gefunden · 930 Zustiege fehlen · 931 Zustieg nicht gefunden · 932 Zustieg bei Leistung nicht freigegeben · 933 Sitzplan bei Leistung nicht freigegeben · 935-938 Versicherungen · 940-942 Zahlungsart · 950 Anmelder fehlt · 951 Teilnehmerliste fehlt · 952 Teilnehmer-ID fehlt · 953 Keine Teilnehmer übergeben · 960 Preisfehler · 961 Buchung konnte nicht gespeichert werden · 962 Buchungsstatus #1# kann nicht verarbeitet werden, nur Status #2# möglich · 970 CRM-Merkmal nicht gültig · 980 Reise ist fürs Internet gesperrt · 981 Reise ist nicht mehr buchbar (#1#) · 982 Optionsbuchungen nicht zugelassen · 983 Anfragebuchung nicht zugelassen · 984 Leistung nicht fürs Internet buchbar · 985 Zustieg nicht fürs Internet buchbar · 986/987 Rechnung · 990-994 Agenturkonto · 995/996 Gutschein · 997/998 Buchung-ID · 1001 Reisedatum fehlerhaft · 1002 Buchungsdatum fehlerhaft · 1003 Status im Vorgang nicht korrekt · 1011-1018 Zahlungsabgleich
Vouchers — 600-608 Gutscheine · 610/611 Buchungsart · 612-621 Gutscheinart / Stamm / Kapazität · 622 Einlösecode fehlt · 630-632 Aktionsgutschein
Applying this: the teamer placeholder booking
Answered 2026-09-02: BUCHUNG cannot create it. A payload mirroring the real teamer booking
(travel 12429, hotel 222925, agency 77876, both bus legs, ski pass, Ortstaxe, Keycard-Pfand,
pickup and drop-off, root and participant status O, ids-only identity, no room) returns
916 Ferienziel-Unterbringungen fehlen. Adding a room to the identical payload clears that gate
and moves on to 650, which proves accommodation was the only structural objection.
Two blockers that were not the problem:
- Status
Ois fine here. Travel 12429 reportsbuchungstatusmoeglich="F, O, A". The982refusal seen earlier is per travel, not a global setting. NurFahrtnever came up — the request never got far enough for it to matter.
What the real booking shows about how the office builds one, worth reproducing if the work moves to a direct write:
<idpartner>is present despite there being no accommodation — 222925 is the ordinary hotel of the travel (Adresse.IDAdresse, "NEU Sportclub Weissfluh Midweeks und Wochenreisen", the sole hotel of product 1429, with real bookable rooms), not a special no-accommodation partner. Whether the booking stores it or the response derives it is still open.- Each teamer seat is its own
AdressePerson(407725, 407726) under the shared address 41003, with an empty<anschrift/>. <wünsche><beförderungswunsch>carries the role label — "Reiseleitung + Busbegleitung".- Per participant,
<optionsdatum>sets the option expiry. - The total is driven to zero with a manual
<zuschlag>: Ortstaxe 44,00 + Keycard-Pfand 10,80- Abfahrtsortzuschlag 25,80 = 80,60, offset by an unnamed −80,60 line.
<zuschlaege>is part of the documentedBUCHUNGpayload, so this part is expressible over XML.
- Abfahrtsortzuschlag 25,80 = 80,60, offset by an unnamed −80,60 line.
See .ddev/plans/teamer-placeholder-booking-plan.md.