Introduction
REST-Schnittstelle für Landingpages zur Angebots- und Buchungsanfrage von Fahrten bei Primos.
Diese Schnittstelle erlaubt es Landingpages (LPs), bei Primos ein Angebot für eine Fahrt anzufragen
(verfügbare Fahrzeuge, Distanz, Preise) und anschließend eine Buchung anzulegen. Eine Buchung erzeugt
im Primos-CRM eine Fahrt (Status „Anfrage") und löst dort die üblichen Automatismen aus.
**Regulärer Ablauf – verbindlich in dieser Reihenfolge:**
1. `GET /api/v1/me` – Token prüfen und die enthaltenen Berechtigungen (`quote`, `book`, `estimate`) sehen.
2. `GET /api/v1/airports` und `GET /api/v1/vehicles` – Kataloge abrufen. Die hier gelieferten `iata`-Codes
(Flughäfen) und `vehicle_id`s sind die **einzig gültigen** Werte für alle Folgeanfragen; unverändert übernehmen.
3. *(optional – Marketing/Marktanalyse)* `POST /api/v1/estimates` – unverbindliche Preisschätzung.
Speichert **nichts** und gibt **kein** Token aus. Das Ergebnis ist **nicht buchbar**.
4. `POST /api/v1/quotes` – verbindliches, zeitlich begrenztes Angebot anfragen. Antwort enthält `quote_id`
sowie ein einmalig sichtbares `quote_token`.
5. `POST /api/v1/bookings` – mit `quote_id`, `quote_token` und gewähltem `vehicle_id` buchen.
**Wichtig:** Eine Buchung (Schritt 5) ist **nur mit einem zuvor erstellten, noch gültigen Angebot** (Schritt 4)
möglich – es gibt keinen Direkt-Buchungsweg. Preise sind im Angebot eingefroren; die Buchung verwendet
ausschließlich den serverseitig berechneten Preis. Ein Angebot ist zeitlich begrenzt gültig und nur einmal buchbar.
**Best Practice – eigenen `place_id`-Bestand pflegen:** Adress-Seiten müssen mit ihrer Google-`place_id`
angefragt werden (siehe „Angebot anfragen"). Speichere die `place_id` je Stadt/Adresse **dauerhaft im eigenen
Bestand** und sende sie bei Folgeanfragen wieder, statt sie jedes Mal neu über Google Places zu bestimmen.
Das vermeidet unnötige Google-Maps-Aufrufe und hält Distanz und Preis über alle Anfragen hinweg stabil und
identisch zum Website-Rechner.
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer {YOUR_AUTH_KEY}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Jeder Zugang gehört zu einer Auftragsquelle. Das Bearer-Token wird von Primos im Admin-Bereich erstellt und verwaltet. Token besitzen Berechtigungen: quote (Angebote) und book (Buchungen).
Origin-Beschränkung (optional): Für eine Auftragsquelle kann eine Liste erlaubter Origins hinterlegt sein. Ist sie gesetzt, werden Browser-Anfragen nur von einer dieser Origins akzeptiert (sonst 403); Anfragen ohne Origin-Header (Server-zu-Server) sind nicht betroffen. Ist keine Liste hinterlegt, gilt keine Einschränkung. Bei Bedarf teilt Primos die freigeschalteten Origins mit.
Angebote
Angebot anfragen
requires authentication
Berechnet für die angegebene Strecke die verfügbaren Fahrzeuge samt Distanz und Preisen und legt ein zeitlich begrenztes Angebot an.
Locator-Regeln (identisch zum Website-Rechner): Jede Seite (pickup/destination) ist
entweder eine Adresse oder ein Flughafen.
- Adress-Seite:
place_id(Pflicht, aus Google Places Autocomplete) plusformatted_address,streetundhouse_number. Es wird ausschließlich über dieplace_idgeroutet – reine Koordinaten oder Freitext-Adressen werden nicht akzeptiert, damit Distanz und Preis exakt mit der Website übereinstimmen und keine Abweichungen entstehen. - Flughafen-Seite: ausschließlich
airport_iataausGET /api/v1/airports. place_id, Koordinaten und Anzeigename löst Primos serverseitig auf.
Bei type=airport muss genau eine Seite ein airport_iata enthalten (die andere ist eine
Adress-Seite); bei type=transfer sind beide Seiten Adress-Seiten und airport_iata ist nicht erlaubt.
Example request:
curl --request POST \
"https://primos-fahrservice.de/api/v1/quotes" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"type\": \"airport\",
\"pickup\": {
\"airport_iata\": \"fra\",
\"place_id\": \"ChIJ7cv00DwsDogRAMDACa2m4K8\",
\"formatted_address\": \"Bahnhofstraße 1, 65552 Limburg an der Lahn, Deutschland\",
\"street\": \"Bahnhofstraße\",
\"house_number\": \"1\",
\"postal_code\": \"65552\",
\"city\": \"Limburg an der Lahn\"
},
\"destination\": {
\"airport_iata\": \"fra\",
\"place_id\": \"ChIJeflCVHQLvUcRMfP4IU3YdIo\",
\"formatted_address\": \"Zeil 1, 60313 Frankfurt am Main, Deutschland\",
\"street\": \"Zeil\",
\"house_number\": \"1\",
\"postal_code\": \"60313\",
\"city\": \"Frankfurt am Main\"
},
\"pickup_time\": \"2026-07-10T14:30:00+02:00\",
\"roundtrip\": false,
\"passengers\": 1,
\"child_seats\": 0,
\"flight_number\": \"LH123\",
\"options\": {
\"sign_service\": false
}
}"
const url = new URL(
"https://primos-fahrservice.de/api/v1/quotes"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"type": "airport",
"pickup": {
"airport_iata": "fra",
"place_id": "ChIJ7cv00DwsDogRAMDACa2m4K8",
"formatted_address": "Bahnhofstraße 1, 65552 Limburg an der Lahn, Deutschland",
"street": "Bahnhofstraße",
"house_number": "1",
"postal_code": "65552",
"city": "Limburg an der Lahn"
},
"destination": {
"airport_iata": "fra",
"place_id": "ChIJeflCVHQLvUcRMfP4IU3YdIo",
"formatted_address": "Zeil 1, 60313 Frankfurt am Main, Deutschland",
"street": "Zeil",
"house_number": "1",
"postal_code": "60313",
"city": "Frankfurt am Main"
},
"pickup_time": "2026-07-10T14:30:00+02:00",
"roundtrip": false,
"passengers": 1,
"child_seats": 0,
"flight_number": "LH123",
"options": {
"sign_service": false
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"quote_id": "9b1f2c34-1a2b-4c3d-8e9f-000000000000",
"type": "transfer",
"distance_km": 42,
"roundtrip": true,
"currency": "EUR",
"expires_at": "2026-07-15T14:30:00+02:00",
"vehicles": [
{
"vehicle_id": 3,
"name": "Business Van",
"passengers": 7,
"luggage": 7,
"child_seats": 3,
"image": "vehicles/van.jpg",
"price": {
"amount": 189,
"currency": "EUR",
"roundtrip_included": true
}
}
]
},
"meta": {
"quote_token": "einmalig-sichtbarer-token"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Angebot abrufen
requires authentication
Liefert ein zuvor erstelltes Angebot erneut (ohne das einmalige Token).
Example request:
curl --request GET \
--get "https://primos-fahrservice.de/api/v1/quotes/019f435c-eb4e-732a-8a53-03b2e1a7f6f6" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://primos-fahrservice.de/api/v1/quotes/019f435c-eb4e-732a-8a53-03b2e1a7f6f6"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"quote_id": "9b1f2c34-1a2b-4c3d-8e9f-000000000000",
"type": "transfer",
"distance_km": 42,
"roundtrip": true,
"currency": "EUR",
"expires_at": "2026-07-15T14:30:00+02:00",
"vehicles": [
{
"vehicle_id": 3,
"name": "Business Van",
"passengers": 7,
"luggage": 7,
"child_seats": 3,
"image": "vehicles/van.jpg",
"price": {
"amount": 189,
"currency": "EUR",
"roundtrip_included": true
}
}
]
}
}
Example response (404):
{
"message": "No query results for model [App\\Models\\Quote]."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Authentifizierung
Eigene Auftragsquelle
requires authentication
Gibt die zum Token gehörende Auftragsquelle samt Berechtigungen zurück – nützlich als Token-Check.
Example request:
curl --request GET \
--get "https://primos-fahrservice.de/api/v1/me" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://primos-fahrservice.de/api/v1/me"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"name": "LP Flughafentransfer FFM",
"slug": "lp-ffm",
"contact_email": "[email protected]",
"is_active": true,
"abilities": [
"quote",
"book"
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Buchungen
Fahrt buchen
requires authentication
Bucht ein zuvor erstelltes Angebot und legt im CRM eine Fahrt an (Status „Anfrage"), wodurch die Automatismen (Benachrichtigungen, Kalender) ausgelöst werden. Der Preis wird immer aus dem Angebot übernommen – ein im Request gesendeter Preis wird ignoriert.
Optionaler Header Idempotency-Key: Wird derselbe Schlüssel erneut gesendet, wird die
bereits erstellte Buchung zurückgegeben (Status 200 statt 201), ohne eine zweite Fahrt anzulegen.
Example request:
curl --request POST \
"https://primos-fahrservice.de/api/v1/bookings" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Idempotency-Key: abc-123" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"quote_id\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
\"quote_token\": \"architecto\",
\"vehicle_id\": 16,
\"customer\": {
\"firstname\": \"b\",
\"lastname\": \"n\",
\"email\": \"[email protected]\",
\"phone\": \"v\"
},
\"payment_method\": \"paypal\",
\"billing\": {
\"different\": true,
\"name\": \"d\",
\"street\": \"l\",
\"house_number\": \"j\",
\"postal_code\": \"nikhwaykcmyuwpwl\",
\"city\": \"v\"
},
\"notes\": \"q\",
\"reference\": \"w\"
}"
const url = new URL(
"https://primos-fahrservice.de/api/v1/bookings"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Idempotency-Key": "abc-123",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"quote_id": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"quote_token": "architecto",
"vehicle_id": 16,
"customer": {
"firstname": "b",
"lastname": "n",
"email": "[email protected]",
"phone": "v"
},
"payment_method": "paypal",
"billing": {
"different": true,
"name": "d",
"street": "l",
"house_number": "j",
"postal_code": "nikhwaykcmyuwpwl",
"city": "v"
},
"notes": "q",
"reference": "w"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201):
{
"data": {
"booking_id": 12345,
"status": "inquiry",
"type": "transfer",
"vehicle": {
"vehicle_id": 3,
"name": "Business Van"
},
"pickup_time": "2026-07-15T14:30:00+02:00",
"roundtrip": true,
"external_reference": "LP-ORDER-5567",
"price": {
"amount": 189,
"currency": "EUR"
},
"created_at": "2026-07-08T12:00:00+02:00"
}
}
Example response (409):
{
"message": "Angebot ist abgelaufen oder wurde bereits gebucht."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Buchungsstatus abrufen
requires authentication
Liefert den aktuellen Status einer Buchung (z. B. inquiry, confirmed, booked, done).
Example request:
curl --request GET \
--get "https://primos-fahrservice.de/api/v1/bookings/40" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://primos-fahrservice.de/api/v1/bookings/40"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"booking_id": 12345,
"status": "confirmed",
"type": "transfer",
"vehicle": {
"vehicle_id": 3,
"name": "Business Van"
},
"pickup_time": "2026-07-15T14:30:00+02:00",
"roundtrip": true,
"external_reference": "LP-ORDER-5567",
"price": {
"amount": 189,
"currency": "EUR"
},
"created_at": "2026-07-08T12:00:00+02:00"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Fahrzeuge
Fahrzeugkatalog
requires authentication
Liefert die aktuell angebotenen Fahrzeuge (ohne Preise – Preise entstehen erst im Angebot).
Example request:
curl --request GET \
--get "https://primos-fahrservice.de/api/v1/vehicles" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://primos-fahrservice.de/api/v1/vehicles"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"vehicle_id": 3,
"name": "Business Van",
"description": "Bis zu 7 Personen",
"passengers": 7,
"luggage": 7,
"child_seats": 3,
"image": "vehicles/van.jpg"
},
{
"vehicle_id": 1,
"name": "Business Limousine",
"description": "Mercedes E-Klasse o. ä.",
"passengers": 3,
"luggage": 3,
"child_seats": 1,
"image": "vehicles/limo.jpg"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Flughäfen
Flughäfen
requires authentication
Liefert die aktuell bedienten Flughäfen. Der zurückgegebene iata-Wert ist exakt der Wert,
der bei type=airport-Angeboten als pickup.airport_iata bzw. destination.airport_iata
gesendet werden muss. Bitte diese Liste als einzige Quelle für gültige IATA-Codes nutzen und
die Werte unverändert übernehmen (der Vergleich erfolgt case-sensitive) – so entstehen keine
Abweichungen zwischen Landingpage und Angebotsanfrage.
Die mitgelieferten Koordinaten (lat/lng) und die Google-place_id entsprechen dem, was
Primos serverseitig für die Distanzberechnung verwendet, und können für ein eigenes
Google-Maps-Autocomplete genutzt werden.
Example request:
curl --request GET \
--get "https://primos-fahrservice.de/api/v1/airports" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://primos-fahrservice.de/api/v1/airports"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"iata": "fra",
"name": "Frankfurt International Airport",
"lat": 50.0353788,
"lng": 8.5518391,
"place_id": "ChIJeflCVHQLvUcRMfP4IU3YdIo"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Preise
Preis schätzen
requires authentication
Berechnet für die angegebene Strecke die verfügbaren Fahrzeuge samt Distanz und Preisen –
ohne ein Angebot zu speichern und ohne ein buchbares Token auszugeben. Gedacht als
Marketing-/Marktanalyse-Werkzeug, um Preise durchzurechnen. Die zurückgegebenen Preise sind
unverbindlich und können nicht gebucht werden; dafür muss ein Angebot über
POST /api/v1/quotes erstellt werden.
Eingabe- und Locator-Regeln sind identisch zu POST /api/v1/quotes (Adress-Seite:
place_id + formatted_address + street + house_number; Flughafen-Seite: airport_iata).
Dadurch entspricht der geschätzte Preis exakt dem späteren Angebotspreis.
Ablauf-Hinweis: Dieser Endpunkt ersetzt nicht den Buchungsweg. Wer buchen will, muss zuerst
POST /api/v1/quotes (Angebot) und dann POST /api/v1/bookings aufrufen – siehe Einführung.
Empfehlung für Marktanalysen: Lege dir einen eigenen Bestand an place_ids je Stadt/Adresse an
(z. B. einmal aus GET /api/v1/airports bzw. Google Places ermittelt) und sende diese place_ids bei jeder
Preisabfrage wieder. So bestimmt der aufrufende Client (z. B. Claude Code) die Orte einmalig und fragt danach
aus dem eigenen Bestand ab, statt bei jeder Anfrage neue Google-Maps-Aufrufe auszulösen. Das spart Kosten und
liefert stabile, reproduzierbare Preise.
Erfordert ein Token mit der Berechtigung estimate.
Example request:
curl --request POST \
"https://primos-fahrservice.de/api/v1/estimates" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"type\": \"airport\",
\"pickup\": {
\"airport_iata\": \"fra\",
\"place_id\": \"ChIJ7cv00DwsDogRAMDACa2m4K8\",
\"formatted_address\": \"Bahnhofstraße 1, 65552 Limburg an der Lahn, Deutschland\",
\"street\": \"Bahnhofstraße\",
\"house_number\": \"1\",
\"postal_code\": \"65552\",
\"city\": \"Limburg an der Lahn\"
},
\"destination\": {
\"airport_iata\": \"fra\",
\"place_id\": \"ChIJeflCVHQLvUcRMfP4IU3YdIo\",
\"formatted_address\": \"Zeil 1, 60313 Frankfurt am Main, Deutschland\",
\"street\": \"Zeil\",
\"house_number\": \"1\",
\"postal_code\": \"60313\",
\"city\": \"Frankfurt am Main\"
},
\"pickup_time\": \"2026-07-10T14:30:00+02:00\",
\"roundtrip\": false,
\"passengers\": 1,
\"child_seats\": 0,
\"flight_number\": \"LH123\",
\"options\": {
\"sign_service\": false
}
}"
const url = new URL(
"https://primos-fahrservice.de/api/v1/estimates"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"type": "airport",
"pickup": {
"airport_iata": "fra",
"place_id": "ChIJ7cv00DwsDogRAMDACa2m4K8",
"formatted_address": "Bahnhofstraße 1, 65552 Limburg an der Lahn, Deutschland",
"street": "Bahnhofstraße",
"house_number": "1",
"postal_code": "65552",
"city": "Limburg an der Lahn"
},
"destination": {
"airport_iata": "fra",
"place_id": "ChIJeflCVHQLvUcRMfP4IU3YdIo",
"formatted_address": "Zeil 1, 60313 Frankfurt am Main, Deutschland",
"street": "Zeil",
"house_number": "1",
"postal_code": "60313",
"city": "Frankfurt am Main"
},
"pickup_time": "2026-07-10T14:30:00+02:00",
"roundtrip": false,
"passengers": 1,
"child_seats": 0,
"flight_number": "LH123",
"options": {
"sign_service": false
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"estimate": true,
"type": "airport",
"distance_km": 63,
"roundtrip": false,
"currency": "EUR",
"vehicles": [
{
"vehicle_id": 1,
"name": "Limousine / Kombi",
"passengers": 3,
"luggage": 3,
"child_seats": 2,
"image": "uploads/vehicles/limo.jpg",
"price": {
"amount": 95,
"currency": "EUR",
"roundtrip_included": false
}
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Status
Public liveness endpoint.
Example request:
curl --request GET \
--get "https://primos-fahrservice.de/api/v1/health" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://primos-fahrservice.de/api/v1/health"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
access-control-allow-origin: *
{
"status": "ok",
"api": "primos-lp",
"version": "v1"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.