Fleetiva Guide Reference Deutsch OpenAPI (JSON)

Fleetiva API

With the Fleetiva API your system creates transport inquiries directly with a transport service that works with Fleetiva, for example from a booking form on a website or from the software of an institution. The transport service reviews every inquiry in its inbox. Nothing is booked automatically.

This page is for developers connecting a system. All calls with fields, data formats and a test console are in the reference; the field descriptions there are in English, as in the OpenAPI file.

Contents

Introduction

What the API does

The Fleetiva API connects your system with transport services that work with Fleetiva: transport for people with special needs, school transport, medical and senior transport. A connected system creates inquiries there, reads their processing status and reads master data such as customers.

Today the API can create an inquiry (POST /inquiries), read its status (GET /inquiries/{id}) and read and sync customers (GET /customers). More areas such as passengers, trips and tours will be added without changing the existing calls (see Versions).

Terms

  • Transport service: the company that runs the trips and uses Fleetiva. The inquiries belong to it.
  • Access: the credentials of a connected system, created by the transport service, with a key and scopes.
  • Inquiry: a request for one or more trips, with the requesting person, passengers and consent.
  • Customer: an institution, authority, cost bearer or private person the transport service works for.
  • Passenger: a person to be transported. Each passenger becomes one item of the inquiry.
  • Inbox: the list in Fleetiva where the transport service sees and processes new inquiries.
  • Acceptance: the transport service takes an inquiry over into its planning.

Quickstart

1. Get access

The transport service creates the key in Fleetiva under Settings → Integrations → API access, with the scope “Create inquiries”. It starts with flt_ and is shown only once. Have it handed over securely, not by email or chat, and store it on your server only, never in a web page or app.

2. Base URL

All calls go to this address, always with https://. Use it exactly as shown: a client that follows a redirect to another host drops the Authorization header, and the call fails with 401.

https://www.fleetiva.de/api/v1

3. Send an inquiry

Send the inquiry as JSON, with your key and an Idempotency-Key that you choose for this inquiry, for example your booking number. To try it out, also send the header Fleetiva-Dry-Run: true: Fleetiva then checks everything and creates nothing (see Dry run).

curl -X POST 'https://www.fleetiva.de/api/v1/inquiries' \
  -H "Authorization: Bearer $FLEETIVA_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: booking-2026-11-0042' \
  --data-binary @- <<'JSON'
{
  "requester": {
    "role": "institution",
    "first_name": "Petra",
    "last_name": "Schulz",
    "email": "p.schulz@example.org",
    "phone": "+49 30 1234567",
    "organization": "Tagesförderstätte Sonnenhof",
    "postal_address": {
      "street": "Lindenallee",
      "house_number": "12",
      "zip": "10115",
      "city": "Berlin"
    }
  },
  "passengers": [
    {
      "first_name": "Jonas",
      "last_name": "Weber",
      "birth_year": 2012,
      "pickup": {
        "street": "Gartenweg",
        "house_number": "4",
        "zip": "10117",
        "city": "Berlin"
      },
      "dropoff": {
        "street": "Lindenallee",
        "house_number": "12",
        "zip": "10115",
        "city": "Berlin"
      },
      "trip_kind": "recurring",
      "requested_date": "2026-12-01",
      "requested_time_window": "arrival by 8:00",
      "return_trip": true,
      "return_time_window": "15:30",
      "needs": {
        "wheelchair": true
      }
    }
  ],
  "message": "Monday to Friday, school days only.",
  "consent": {
    "given": true,
    "text_version": "2026-11"
  }
}
JSON

The same from JavaScript or TypeScript on the server, for example in a Next.js route:

const response = await fetch('https://www.fleetiva.de/api/v1/inquiries', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FLEETIVA_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': booking.id, // your own ID, the same on every retry
  },
  body: JSON.stringify(inquiry),
});

if (response.status === 201 || response.status === 200) {
  const { id } = await response.json(); // store it with the booking
} else if (response.status === 429 || response.status === 503) {
  // 429: wait Retry-After seconds, 503: wait a moment — then send the same request again
} else {
  const problem = await response.json(); // problem.code, problem.issues, problem.request_id
}

4. Handle the response

201 means the inquiry was created. Store the id so you can query the status. 200 with duplicate: true means this inquiry has already arrived; no second one is created. On 429 wait the seconds from Retry-After, on 503 a moment, then send the same request with the same Idempotency-Key again.

HTTP/1.1 201
Location: /api/v1/inquiries/0b7c6d1e-2f3a-4b5c-8d9e-0f1a2b3c4d5e

{
  "id": "0b7c6d1e-2f3a-4b5c-8d9e-0f1a2b3c4d5e",
  "duplicate": false
}

5. Query the status

Use the id to read the processing status. You only see inquiries created through your own access.

curl 'https://www.fleetiva.de/api/v1/inquiries/0b7c6d1e-2f3a-4b5c-8d9e-0f1a2b3c4d5e' \
  -H "Authorization: Bearer $FLEETIVA_API_KEY"
HTTP/1.1 200

{
  "id": "0b7c6d1e-2f3a-4b5c-8d9e-0f1a2b3c4d5e",
  "status": "in_progress",
  "received_at": "2026-11-02T09:15:00+00:00"
}

6. Test

There is no sandbox. Check your calls first with Fleetiva-Dry-Run: true, most easily in the test console. For real test inquiries the transport service creates a separate test access (see Test access).

Access and testing

Key

The transport service creates the access in Fleetiva: Settings → Integrations → API access. It needs the Professional or Enterprise plan. The key starts with flt_ and is shown exactly once; Fleetiva does not store it in plain text.

The key is a password for the transport service. Have it handed over securely, not by email or chat, and keep it on your server only, never in the source of a web page or app. When the transport service renews the key, the old one stops working immediately; a blocked access gets 401.

Scopes

An access may only do what its scopes allow. If the scope for a call is missing, the API answers 403 with the code forbidden.

  • inquiries.create (in Fleetiva “Create inquiries”): create inquiries and read the status of inquiries created through this access.
  • master_data.read (in Fleetiva “Read master data”): read and sync customers and passengers.
  • accounting.read (in Fleetiva “Read accounting”): together with include=billing the billing data of a customer or passenger, for example bank account, VAT ID or billing address.
  • health_data (in Fleetiva “Health data”): together with include=health the health data of a passenger. Every access is logged for the operator.

Test access

There is no sandbox; testing happens against the live system. For development the transport service creates a separate access, for example “Booking form test”. Real test inquiries appear in its inbox under this name; it rejects them and deletes the test access once your connection works. For live operation it creates a separate access.

Dry run

With the header Fleetiva-Dry-Run: true a creating call checks everything the real call checks: key, scope, whether API access is active at the transport service, the limits, all fields, addresses and the consent. Nothing is created, stored or notified. On success the API answers 204 without a body, otherwise with the same error as the real call.

Not checked: whether the Idempotency-Key was already used for another inquiry. A dry run counts as a reading call. Without the header or with false the call takes effect; any other value is rejected with 400.

curl -X POST 'https://www.fleetiva.de/api/v1/inquiries' \
  -H "Authorization: Bearer $FLEETIVA_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: booking-2026-11-0042' \
  -H 'Fleetiva-Dry-Run: true' \
  --data-binary @- <<'JSON'
{
  "requester": {
    "role": "institution",
    "first_name": "Petra",
    "last_name": "Schulz",
    "email": "p.schulz@example.org",
    "phone": "+49 30 1234567",
    "organization": "Tagesförderstätte Sonnenhof",
    "postal_address": {
      "street": "Lindenallee",
      "house_number": "12",
      "zip": "10115",
      "city": "Berlin"
    }
  },
  "passengers": [
    {
      "first_name": "Jonas",
      "last_name": "Weber",
      "birth_year": 2012,
      "pickup": {
        "street": "Gartenweg",
        "house_number": "4",
        "zip": "10117",
        "city": "Berlin"
      },
      "dropoff": {
        "street": "Lindenallee",
        "house_number": "12",
        "zip": "10115",
        "city": "Berlin"
      },
      "trip_kind": "recurring",
      "requested_date": "2026-12-01",
      "requested_time_window": "arrival by 8:00",
      "return_trip": true,
      "return_time_window": "15:30",
      "needs": {
        "wheelchair": true
      }
    }
  ],
  "message": "Monday to Friday, school days only.",
  "consent": {
    "given": true,
    "text_version": "2026-11"
  }
}
JSON
HTTP/1.1 204

Test console

In the reference every call has a “Try it” panel. Enter your key at the top of the reference, adjust parameters and body and send the call. The console shows status, headers and response and the matching curl command.

The key stays in this browser tab only; it is not stored and is sent only to /api/v1 on this address. “Dry run” is preselected. In live operation the key still belongs on your server only.

Concepts

From inquiry to trip

An inquiry is a request, not a booking. It lands in the inbox of the transport service, and everyone with the permission is notified. The transport service reviews the inquiry and takes it over into its planning or rejects it.

Everything else, such as questions, confirmation and trip times, the transport service settles with the requesting person. The API reports the status of the inquiry.

Status

The status of an inquiry is always one of these values:

StatusDescription
newreceived, not processed yet
in_progressthe transport service is working on it
acceptedtaken over into planning
rejectedrejected

Idempotency

Networks lose responses. So that a retry does not create a second inquiry, every creating call carries the header Idempotency-Key: 1 to 120 visible ASCII characters, a separate value for each inquiry, for example your booking number or a UUID.

If the same key arrives again with the same content, the API answers 200 with duplicate: true and the existing id. With different content it rejects the call with 422 (idempotency_key_reused). The content is the requesting person, the passengers and the message, not the consent. A key applies per access and stays bound to its inquiry.

Consent and health data

The needs (needs) are health data under Art. 9 GDPR. Obtain the person’s consent before sending and include with every inquiry the version of the consent text they saw (consent.text_version). Fleetiva stores this version as proof. Without consent the API does not accept an inquiry.

consent.given_at is optional; without it the time of receipt applies. If the time is more than 5 minutes in the future, the inquiry is rejected (in_future).

Lists and sync

A list answers with data and next_cursor. For the next page send next_cursor unchanged as cursor, with the same filters. null means there are no more records. A page has 50 entries, up to 200 with limit.

Records come ordered by their last change, oldest first. If a record changes while you page, it comes again further back. So store by id: create it when it is new, otherwise overwrite it.

To keep a copy current:

  • Remember the start time of each sync. Next time, ask from that time minus a few minutes, because a change carries the time it started.
  • Page through GET /customers?updated_since=… and store every record by id.
  • Page through GET /customers/deleted?since=… with the same time and delete these records on your side.

The same applies to passengers with /passengers and /passengers/deleted.

Deletions stay available for 90 days. If since is further back, the API answers 400 with the reason sync_window_exceeded. Then reload all records.

Field groups

Sensitive parts of a record are field groups. They come only when the call asks for them with include and the access has the matching scope. Without the request the key of the group is missing from the response; with it the key is always there and empty fields are null.

  • billing (scope accounting.read): billing data of a customer (tax number, VAT ID, Leitweg-ID, payment method, bank account) or of a passenger (name and address for invoices, VAT rate).
  • health (scope health_data, API keys only): health data of a passenger – medical notes, allergies, emergency contact, notes on the needs and the flags autism, visual and cognitive impairment. Every passenger delivered with this group is logged for the operator (access, time, request_id). If Fleetiva cannot log the access, the API answers 503 instead of data.

If the scope for a requested group is missing, the API answers 403 (forbidden) without reading any data.

Technical rules

Base URL

All paths on this page are relative to https://www.fleetiva.de/api/v1. Use the address exactly as shown: a client that follows a redirect to another host drops the Authorization header, and the call fails with 401.

HTTPS only. A call over http:// is redirected to https://, but by then the key and the body have already been sent unencrypted. So always enter the base URL with https://. If it happened anyway, have the transport service renew the key. The HTTP/1.1 … lines in the examples are only the status line of the response; the connection is always HTTPS.

Authentication

Every call carries the header Authorization: Bearer <key>. If it is missing or the key is wrong, blocked or renewed, the API answers 401 with the header WWW-Authenticate.

Body and encoding

Creating calls send a JSON object with Content-Type: application/json in UTF-8, at most 64 KB. Another content type gives 415, a body that is too large 413, invalid JSON 400 with invalid_json. Unknown fields are rejected (unknown_field), so a typo in a field name shows up at once.

Reading calls take their parameters from the address, for example ?limit=100&include=billing. Unknown parameters are rejected here as well (unknown_field). A parameter may only be repeated for lists such as include.

Empty values

In every optional field, null, an empty string and only spaces mean “not given”. You do not need to remove empty fields.

In responses every field is always present, an empty field is null. The exception are field groups, which come only on request.

Addresses

An address may be left out entirely. If any part is given, it needs at least street and city; otherwise the API rejects it with the reason address_incomplete at the missing field. House number and postal code are optional.

Limits

Per access, 60 creating and 600 reading calls per hour are possible, per transport service across all accesses 300 and 3000. Above that the API answers 429 and gives the waiting time in seconds in the Retry-After header. A limit per IP address applies as well.

Versions

/api/v1 only grows: optional fields, new calls and new reasons in issues may be added, existing ones keep their meaning. Ignore unknown fields in responses. An incompatible change gets a new version with its own path. What changed is listed under Changes.

Request ID

Every response carries the header X-Request-Id, error responses also the field request_id. Quote it when you contact support; it lets support find the call. Responses are never cached (Cache-Control: no-store).

Error handling

Structure of an error response

Errors come as application/problem+json (RFC 9457). Branch on the code field, not on the text. For invalid input, issues names path and reason, never the submitted value. Quote the request_id when you contact support. The fields are listed in the data format Error response.

Error codes

CodeHTTPMeaning
unauthorized401Key missing, wrong, unknown or blocked.
forbidden403The access lacks the scope for this call or for a requested field group.
access_inactive403API access is not active at the transport service, for example after a plan change.
not_found404Unknown, deleted or, for inquiries, created through another access.
invalid_input400Invalid JSON, a field breaks the rules or the Idempotency-Key is missing; details in issues.
idempotency_key_reused422The same Idempotency-Key was already used with different content.
payload_too_large413The body is larger than allowed.
unsupported_media_type415The Content-Type is not application/json.
rate_limited429Too many calls; try again after Retry-After seconds.
unavailable503Temporarily unavailable; try again after a moment.

Reasons in issues

With invalid_input, issues names for every problem the path of the field and a fixed reason in message. For lengths, counts and numbers the broken limit is in limit. The list only grows; treat an unknown reason like invalid_value.

ReasonMeaning
requiredRequired field missing or empty.
too_shortText shorter than limit characters.
too_longText longer than limit characters.
too_fewFewer than limit entries.
too_manyMore than limit entries.
too_smallNumber below limit.
too_largeNumber above limit.
invalid_typeWrong type, for example text instead of a number.
invalid_valueValue not allowed, for example not one of the possible values.
invalid_formatWrong format, for example of an email address, a timestamp or a UUID.
unknown_fieldUnknown field, for example a typo in the name.
invalid_jsonThe body is not valid JSON (empty path).
invalid_charactersCharacters not allowed, for example in the Idempotency-Key.
contact_requiredEmail or phone of the requesting person is missing.
address_incompleteHalf-filled address: street or city is missing.
invalid_dateNot a real calendar date in the format YYYY-MM-DD.
in_futureThe time lies in the future.
return_time_requires_return_tripReturn time given, but return_trip: false.
invalid_cursorThe cursor was not taken from an API response or is damaged. Without cursor the list starts from the beginning.
sync_window_exceededsince is more than limit days back; older deletions are no longer known. Reload all records.

Example of an error response

HTTP/1.1 400
Content-Type: application/problem+json

{
  "type": "tag:fleetiva.de,2026:api/problems/invalid_input",
  "title": "Invalid request",
  "status": 400,
  "code": "invalid_input",
  "request_id": "5f0d3c2a-7b1e-4c9d-a6f8-2e4b1c7d9a03",
  "issues": [
    {
      "path": "passengers.0.pickup.city",
      "message": "address_incomplete"
    }
  ]
}

Support

Who helps with what

  • Access, keys and scopes: the transport service you create inquiries for. Only it can create, block and renew accesses.
  • Questions about an inquiry: also the transport service; it processes the inquiry in its inbox.
  • Technical questions about the API: Fleetiva at support@fleetiva.de. Quote the request_id, the time and the call. Never send the key.

FAQ

Why do I get 401 although the key is right?

Usually the client follows a redirect to another host and drops the Authorization header. Use the base URL exactly as shown. Otherwise the key was renewed or the access blocked; ask the transport service.

What happens on a call over http://?

The server redirects it to https:// (308), but by then the key and the body have already been sent unencrypted. Correct the base URL to https:// and have the transport service renew the key (see Base URL).

Why do I get 200 instead of 201?

The inquiry with this Idempotency-Key and this content already exists. The response names its id with duplicate: true; no second inquiry is created.

Why do I get 422 when I retry?

The same Idempotency-Key arrived with different content. A retry needs the same content, a new inquiry a new key (see Idempotency).

How do I test without a sandbox?

With a dry run: the header Fleetiva-Dry-Run: true checks everything and creates nothing. For real test inquiries there is a separate test access.

Why does the status call return 404?

You only see inquiries created through your own access. Inquiries of other accesses, unknown and deleted ones are all “not found”.

What do I do on 429 or 503?

On 429 wait the seconds from Retry-After, on 503 a moment. Then send the same request with the same Idempotency-Key; no second inquiry is created.

May I use the key in the browser?

No. The key belongs on your server only; a form in the browser sends its data to your server, which calls the API. The test console on this page is meant for trying things out only.

Changes

What changed in /api/v1, newest first. Existing fields and calls keep their meaning.

  • · Read and sync passengers (GET /passengers, GET /passengers/{id}, GET /passengers/deleted), scope health_data with the field group health (every access is logged) and the field group billing on the passenger.
  • · Read and sync customers (GET /customers, GET /customers/{id}, GET /customers/deleted), scopes master_data.read and accounting.read, field group billing and the reasons invalid_cursor and sync_window_exceeded.
  • · First version: create inquiries (POST /inquiries) including the postal address of the requesting person, query the status (GET /inquiries/{id}), dry run with Fleetiva-Dry-Run: true and fixed reasons in issues.