taxiID Integration API

This is the reference for developers building an integration into a company's taxiID account — quoting, creating and tracking rides, and receiving webhook events. All requests are made to integrations.taxiid.nl; you never call api.taxiid.nl directly. All money is integer cents, all timestamps are ISO-8601 UTC.

Pick the tab that matches what you're building — each only shows the calls you actually need. The Objects section below is shared reference material and always stays visible, whichever tab you pick — it documents the full shape of the data the calls further down (in Administration, Dispatch and Ride creation) send and receive, so it isn't repeated endpoint by endpoint.

Objects

The JSON shown next to each call below is trimmed to what that call is about — this section is the full shape of every object type, so you know what else you can read (and, where noted, write) that isn't shown inline everywhere.

Field names, types and enums here mirror the taxiID core API's models exactly (the same names you see in the api.taxiid.nl explorer), so a ride you read through the Integration API has the same fields as the ride in the portal. Nested objects marked freeform are stored as-is and not schema-validated.

Ride

A ride is the central object; every call above either creates one, reads one, or reports a change to one. Most fields are set by taxiID as the ride progresses (arrival ETA, assigned driver, computed price) — you only send the fields marked input when creating one via POST /v1/rides. Every ride also carries createdAt and modifiedAt (ISO-8601, set by taxiID).

Property Type Input? Description
id string no taxiID's ride identifier. Use this in every later call about the ride.
intId integer no Human-friendly sequential ride number, shown in the portal and to drivers.
reference string optional Free-text booking reference shown in the portal and on invoices.
isTestBooking boolean optional Marks the ride as a test; defaults to false.
type string no Kind of job; defaults to ride.
status string enum no quoteRequested/quoted/requested (pre-process, not yet live/dispatchable; quote_expired is where an unaccepted quote ends) → unassigned/accepted → assigned → active/driving_to_pickup → arrived_at_pickup → started → completed; or canceled/canceled_driver/canceled_passenger from most non-final states. Full enum: quoteRequested, quoted, requested, accepted, unassigned, assigned, active, driving_to_pickup, arrived_at_pickup, started, completed, canceled, canceled_driver, canceled_passenger, quote_expired. A ride created without a status starts as unassigned. completed, the three canceled* values and quote_expired are terminal — no further transitions. A dispatch offer (waiting on a specific driver to accept) is a separate, sub-status concept that does not appear in this field — see Webhook: ride.status_changed and POST /v1/rides/{id}/offer.
cancellationReason string optional Why the ride was cancelled; also drives whether a cancellation charge applies.
requestedDate ISO-8601 datetime yes When the ride should start. Omit for an immediate/ASAP ride.
pickupDate ISO-8601 datetime no Actual/scheduled pickup moment as dispatch has it.
requestedDestinationDate ISO-8601 datetime optional When the passenger needs to be at the destination (arrive-by bookings).
requestedScheduleDate ISO-8601 datetime no Moment the ride was put on the dispatch schedule.
dispatchTimeToPickup ISO-8601 datetime no When dispatch is due to start driving to the pickup.
timezone number optional UTC offset of the pickup location, in hours.
departure Address yes The pickup location. (origin on a ride is not the pickup — see below.)
destination Address yes The drop-off. Optional for some vehicle types (e.g. a driver-at-disposal booking).
stopOvers array of Address optional Intermediate stops between departure and destination, in order. Defaults to [].
origin Origin no The booking channel the ride came in through (e.g. the portal, an integration) — not a location.
passengerId string no The Passenger riding. Created/linked by taxiID from the passenger details you send.
passengerCount integer optional Number of people travelling. Affects vehicle-type eligibility. Defaults to 4.
luggageCount integer optional Number of luggage items.
businessRide boolean optional Tags the ride as a one-off "business ride".
businessInfo {companyName, address} optional Company name and postal address shown alongside a business ride; address is {streetName, houseNumber, postalCode, city, countryCode}.
flightNumber string optional For airport pickups — enables flight-tracked pickup timing.
note string optional Free-text remark on the booking (e.g. "call on arrival").
driverNote string optional Instruction shown to the driver.
operatorNote string optional Internal note for the dispatcher/operator; never shown to the passenger or driver.
language string optional Passenger's language for SMS/email/driver-app messaging. Defaults to EN.
vehicleType string optional No fixed platform-wide enum — a plain string matched against the vehicle types/products the company has configured in its own pricing catalog. There is currently no endpoint to discover a given company's allowed values. Open question for the API team: should a GET /v1/vehicle-types discovery endpoint be added?
products array, freeform optional The priced product lines on the ride. Defaults to [].
debtorCode string optional Overrides which of the company's debtors is billed, if the company bills multiple debtors.
debtorId / couponId / driverTariffId string no Resolved debtor, applied coupon and driver tariff.
foreignBookingId string optional, echoed back Your own booking reference. Not interpreted by taxiID — round-tripped as-is onto every webhook so you can match events back to your system.
foreignDispatchId string optional The ride's id in the other party's dispatch system, when it was handed to or from a partner.
quoteId string optional (POST /v1/rides only) The id from a prior POST /v1/quotes response, so the ride is created at that locked-in price. Open question for the API team: what happens when it's omitted, and whether a stored quote id exists at all — the core pricing endpoint is stateless today, so this would need to be introduced by integrations.taxiid.nl's still-unbuilt /v1/quotes. It is not a stored Ride field.
paymentMeta PaymentMeta optional How the ride is paid for. Defaults to invoicing the company's account if omitted.
price Price no Computed by taxiID, normally from a prior POST /v1/quotes. Never send your own price on create.
priceMethod string enum no How the price is determined: meter (default; legacy alias onMeter), fixed, hourlyMeter, hourlyFixed.
calculationMethod string enum no manual (default) or group_pricing_rules.
priceChangedReason string enum no "" (default), invalid_mileage or invalid_duration — why the price was adjusted after the ride.
priceMeta object, freeform no Pricing audit data (which rule/product produced the price).
driverPrice / driverSettlement / commission {total, currency, …} no What the driver is paid, what is settled to them, and the platform/fleet commission.
commissionRules array, freeform no The commission rules applied to the ride. Defaults to [].
taxiMeter string enum optional Where the fare meter runs: app, car or none.
metricsMethod string enum optional How distance/duration are measured: onMeter, fixed, odoMeter, googleRoute (default), driverAppTracking, external.
metrics / actualMetrics Metrics no Expected route metrics, and the metrics actually driven.
eta / etp object, freeform no Live estimate of arrival at pickup (eta) and estimated time of pickup (etp); only present once a driver is en route.
estimations object, freeform no Route/time estimates computed at booking.
events JobEvents no Timestamped status history — the audit trail behind the current status.
vehicleStatus object, freeform no Live status of the assigned vehicle.
holdWindow {productId, start, end, calculationType} no The product-capacity window the ride currently blocks; set asynchronously after create/update, so it can be briefly absent or stale.
pendingChange object, nullable no A passenger edit awaiting payment of its price increase. At most one at a time.
meta object, freeform optional Your own key/value data, stored and returned untouched. Not shown to the driver or passenger — use note/driverNote for that.
foreignMeta object, freeform no The equivalent bag the other side (driver app / dispatch) attaches; read-only to you.
resources object, freeform no Additional resources attached to the ride.
companyId string no The company account the ride belongs to — always your own.
ownerId / ownerType string no Who owns the ride and its discriminator (Company or Driver).
serviceProviderId / serviceProviderType string no Who performs the ride (Company or Driver).
driverId / vehicleId string no Set once a driver is assigned (from status:'assigned' onward). Absent before that.
operatorId / dispatcherId string no The operator and dispatcher handling the ride.
creatorId / lastModifierId / lastModifierType string no Who created and who last modified the ride (and what kind of actor).
pluginInstallId string no The integration/channel installation the ride arrived through.
rideCombinationId / sequenceIndex string / integer no The combined route the ride belongs to and its position in it.
recurringOrderId string no Set when the ride was generated from a recurring order.
rideLogId / userId / shiftId string no Internal links to the ride log, the booking user and the driver shift.
dispatchSettings / triggers / dispatcherAccessLevel object no Internal dispatch state. Documented for completeness; don't rely on them in an integration (dispatchSettings and triggers are hidden from API responses).

Address

Used for a ride's departure, destination and every entry of stopOvers, and for the same fields on POST /v1/quotes. gps is what taxiID routes and prices on; the street/city fields are shown to the driver and passenger.

taxiID validates and enriches addresses on ride creation. Confirmed in api.taxiid.nl (AddressValidationService): for Dutch addresses it checks the postcode and house number against pro6pp, the BAG (Kadaster/PDOK) and Google reverse-geocoding, so a ride you create can come back with a corrected postalCode, streetName or houseNumber, or with missing fields filled in from a known location (a favourite address, or a pin within 50 m). Send as complete an address as you have — gps plus street, house number, postal code, city and countryCode — and do not rely on the returned address being byte-identical to what you sent. If an address stays unusable after that (for example no gps and no street/city, only a description), the ride is refused. A geocoder outage never blocks a ride. Open question for the API team: the HTTP status and error body of that refusal, and whether POST /v1/quotes applies the same validation, are not confirmed (the core model marks gps required, but the validator accepts an address without it when it has enough identity to resolve).

Property Type Input? Description
gps {lat, lng} yes WGS-84 coordinates (GeoPoint). Used for routing and pricing.
streetName / houseNumber string optional Street and house number.
postalCode / city string optional Postal code and city.
countryCode string optional Country of the address, ISO 3166-1 alpha-2 (e.g. NL). Used for local address-format rules and duty/VAT handling. The stored default is EN, which is not a valid ISO code — always send it explicitly.
type string enum optional generic (default), airport, trainStation or poi.
code string optional Code for an airport/station pickup (e.g. IATA AMS), alongside flightNumber on the ride.
poiCode string optional Point-of-interest code.
synonym string optional Familiar name of the place (e.g. "Hotel Okura").
internationalAlias string optional Name of the place in English/international form.
description string optional Free-text description of the location.
placesId string optional Google Places id, if the address was picked from Places.
gpsSpeed / gpsBearing / gpsAccuracy number no Speed, bearing and accuracy of the GPS fix, when the address came from a device.
gpsTime ISO-8601 datetime no Time of the GPS fix.
gpsOrigin / gpsPriority string no Where the fix came from and its priority.

Origin

The booking channel a ride came in through — what Ride.origin holds. Read-only to you.

Property Type Description
id string Origin identifier.
name string Channel name (e.g. the portal, a booking widget, an integration).
originId string The id of the channel in the external system, if any.
pluginInstallId string The integration installation this channel belongs to.

Passenger

A ride references its passenger through passengerId; when you create a ride you send the passenger's details and taxiID finds or creates the matching record. Required fields are fname, lname, email and phoneNumber.

Property Type Input? Description
id string no Passenger identifier.
fname / lname string yes First and last name. Shown to the driver.
email string yes Used for ride confirmations/receipts.
phoneNumber string (E.164) yes Used for driver-arrival SMS and driver contact. Always include the country code.
language string optional Passenger's language; defaults to en.
foreignId string optional The passenger's id in your system, kept for matching.
companyId / realm string no The company the passenger belongs to, and the branding realm they signed up under.
addressId string no The passenger's saved address.
branding object, freeform no Branding applied to the passenger's emails/app.
stripeCustomerId string no The passenger's Stripe customer, if they pay online.
lastLogin / inviteSend ISO-8601 datetime no Last self-service login; when the passenger-app invitation was sent.
isDeleted / deletedAt boolean / ISO-8601 datetime no Soft-delete marker.

The password hash and reset token fields on the model are never returned by any call.

Price

Always computed by taxiID (from POST /v1/quotes or on ride completion) — never sent by you.

Property Type Description
total integer Total price, in cents (matches the workspace-wide money convention).
currency string (ISO 4217) Almost always EUR.
breakdown PriceBreakdown Optional itemisation, see below — present on completed rides, may be absent on a fresh quote.

PriceBreakdown has the keys route (always present), toll, parking, waiting, discount, cleaning and additional. Each is {total, taxPercentage} — total in cents and taxPercentage the VAT rate applied to that line.

PaymentMeta

Property Type Input? Description
type string enum optional The platform-wide bucket: inTaxi, invoice (bill the company, default), or online. There is no separate cash/card value — inTaxi covers any in-vehicle payment and online covers a passenger-paid-online booking. Legacy callers may send the bucket as origin; taxiID maps it to type on create. Open question for the API team: does type:'online' trigger a live PSP charge synchronously at ride creation, or only label the ride for later settlement? Not confirmed — don't document a card value as available until it is.
method string optional How it is paid within the bucket (e.g. onAccount for an invoice booking).
moment string optional When it is paid (e.g. afterRide).
description string optional Human-readable label for the payment choice.
reference string optional Your own payment/reference note, echoed back untouched — same pattern as foreignBookingId.

Metrics

Used for a ride's metrics (expected) and actualMetrics (driven).

Property Type Description
distance number Distance, in the unit given by distanceFormat.
duration number Trip duration.
waitingTime number Time spent waiting.
startMileage / endMileage number Odometer readings, when metricsMethod is odoMeter.
distanceFormat string Unit of distance; defaults to km.

JobEvents

A ride's events object — one entry per status the ride has reached, each a timestamped location stamp: {city?, streetName?, postalCode?, houseNumber?, gps, date}. Keys present only once reached: booked (set at creation), unassigned, assigned, driving_to_pickup, arrived_at_pickup, started, arrived_at_destination, completed, canceled.

Webhook envelope

Every webhook delivery (see Dispatch → Verifying a webhook) shares this outer shape; data's contents depend on event.

Property Type Description
event string enum ride.created, ride.status_changed, or ride.updated (opt-in).
data object For ride.created/ride.updated, the Ride fields that changed plus id. For ride.status_changed, {id, status, previousStatus}.

Administration

Setup and account calls. Every integration needs these first, regardless of what it does.

Authentication

Subject: send Authorization: Bearer <api-key> on every request. Get your key from the portal under Booking channels → Developer API; it is shown once at creation/rotation time.

Enabling Developer API

Subject: the key/webhook are set up from the portal, not via this API — confirmed against api.taxiid.nl's shipped Developer API channel (its own /developer-api routes, JWT-authenticated, called by the portal UI on your behalf; never called directly by your integration).

  1. Attach the developer_api product to your contract (portal Offering → Contract) — this is the entitlement check every step below requires.
  2. Enable the channel (portal Booking channels → Developer API, "Enable" button) — mints your API key and shows it once. Enabling a second time while already enabled is rejected (409, see Errors below).
  3. Rotate your key any time from the same page — the previous key stops working immediately, and the new one is shown once.
  4. Set your webhook URL and events — a webhook secret is minted the first time you set a URL and is never shown again automatically; use "Rotate webhook secret" to get a new one (also shown once).
  5. Disable removes the whole channel — your key and webhook config are both gone; re-enabling afterwards mints a fresh key.

GET /v1/me

Subject: your account's status — company name, entitlement, enabled webhook events, and your current rate limit.

Confirmed groundwork, endpoint itself still unbuilt: integrations.taxiid.nl doesn't expose /v1/me yet, but the internal contract it would read from is live — api.taxiid.nl's gateway-facing key-resolution call already returns {pluginInstallId, companyId, entitled, rateLimitPerMinute, webhook:{url, events}} for a given API key. companyName isn't part of that payload (it would need a separate company lookup in integrations.taxiid.nl itself), so treat every field below as expected except companyName, which needs to be added when this endpoint is actually built.

Rate limits

Subject: 120 requests/minute per API key by default — confirmed as api.taxiid.nl's DEFAULT_RATE_LIMIT_PER_MINUTE, applied unless your account has a custom rateLimitPerMinute configured. A 429 response carries Retry-After in seconds; back off and retry.

Errors

Subject: every error is {"error":{"statusCode","code","message"}}. Confirmed platform-wide codes: 401 unknown/rotated-out key, 402 not entitled (contract product not attached), 409 Developer API already enabled (only relevant to the portal's own enable call, see Enabling Developer API above), 429 rate limited.

Open question for the API team: the booking-specific codes below (invalid/missing fields, invalid coordinates, ride/quote not found, invalid state transitions) don't exist in any shipped code yet — integrations.taxiid.nl's /v1/* surface is unbuilt — but they follow the same {statusCode, code, message} envelope and the conventions api.taxiid.nl already uses elsewhere in this codebase (a 404 when a referenced id isn't found or isn't yours, a 409-style rejection of an invalid state transition). Treat these as the expected shape pending confirmation, not a guarantee:

Status code When
400 invalid_request A required field is missing, or malformed (e.g. gps.lat/gps.lng outside valid range).
404 ride_not_found {id} doesn't exist, or exists but isn't scoped to your company.
404 quote_not_found quoteId doesn't exist, or isn't yours.
409 quote_expired quoteId is past its expiry (see POST /v1/quotes).
409 quote_already_used quoteId was already consumed by an earlier POST /v1/rides.
409 invalid_state_transition E.g. cancelling a ride that's already completed or otherwise terminal (see POST /v1/rides/{id}/cancel).

Subject: 402 not_entitled means the company hasn't attached the required contract product — confirmed directly in api.taxiid.nl's DeveloperApiService (DEVELOPER_API_SKU = 'developer_api'), matching the seed catalog's "Developer API-koppeling" product. A company enables it from the portal under Booking channels → Developer API, which attaches that product to the company's contract (the same accept-and-attach flow used for other paid integrations like Taxsys) before the enable/API-key routes become usable — see Enabling Developer API above for the exact step sequence.

Webhooks setup

Subject: webhook URL, secret, and subscribed events are configured in the portal (see Enabling Developer API above), not via this API. The default subscribed events if you never change them are ride.created and ride.status_changed — confirmed as api.taxiid.nl's DEFAULT_WEBHOOK_EVENTS. Each delivery is signed — see Dispatch for verifying the signature.

Dispatch

Calls for integrations that monitor or hand off rides to drivers/fleets, rather than creating them.

GET /v1/rides

Subject: list your rides, filterable by status and date range, paginated with cursor/limit in → nextCursor out.

Pass the previous response's nextCursor back as the cursor query param to get the next page; omit it for the first page. limit defaults to 20; the maximum is not yet defined (see below).

Open question for the API team: this pagination shape (cursor in, nextCursor out) has no precedent anywhere in api.taxiid.nl — every existing list endpoint in this workspace uses LoopBack's ?filter= (skip/limit/where) instead, and /v1/rides itself doesn't exist yet (integrations.taxiid.nl's /v1/* surface is unbuilt). Confirm whether the gateway will actually introduce cursor pagination or follow the existing skip/limit convention, and what the real default/max limit is, before treating this as final.

GET /v1/rides/{id}

Subject: retrieve a single ride's current state.

POST /v1/rides/{id}/offer

Subject: offer a ride to a driver or company for dispatch. driverId is expected to be scoped to your own company — every existing write endpoint in api.taxiid.nl enforces company ownership this way (e.g. a pluginInstallId/company id supplied in a request body is always checked against the caller's own company, 404ing otherwise), and this endpoint is expected to follow the same convention once built. This specific check has not been implemented yet, so treat it as the expected behavior rather than a confirmed guarantee until integrations.taxiid.nl's /v1/* surface actually ships.

Webhook: ride.status_changed

Subject: delivered whenever a ride you created or were offered changes status.

Webhook reliability

Subject: delivery is via the same outbox mechanism used for every other taxiID integration (Wintax/Taxsys) — confirmed in api.taxiid.nl: 8 attempts by default (OUTBOX_MAX_ATTEMPTS), exponential backoff starting at 5 seconds and doubling each attempt (5s, 10s, 20s, 40s, …). After the final attempt the delivery is marked failed (dead-lettered) and not retried further.

Open question for the API team: no replay/recovery mechanism for a dead-lettered event exists today — there is no endpoint to re-trigger a missed webhook. Until one exists, the only fallback is to poll GET /v1/rides/GET /v1/rides/{id} for rides you haven't heard a terminal status for in a while (that endpoint isn't built yet either, so this is a recommendation, not a guarantee).

Verifying a webhook

Subject: header X-TaxiID-Signature: t=<unix>,v1=<hex hmac> — HMAC-SHA256 over ${t}.${rawBody} using your webhook secret. Reject stale timestamps.

Open question for the API team: the allowed clock-skew window (how old a t= value may be before it's rejected as stale) is not yet defined anywhere in api.taxiid.nl or integrations.taxiid.nl — the signing side (PluginInstallDeliveryService) only signs, it doesn't itself define a verification tolerance, and the receiving/verification side is unbuilt. Confirm a specific window (e.g. 5 minutes) before integrators rely on one.

Ride creation

Calls for integrations that book rides on behalf of a passenger or company.

POST /v1/quotes

Subject: get a price quote for a route before booking. Priced by taxiID's pricing service (the core POST /prices/quotation), which returns every product that can serve the route.

Field Type Required Description
requestedDate ISO-8601 datetime yes Pickup moment; also used for the fleet-availability check.
departure / destination Address yes Send the full address (gps, street, house number, postal code, city, countryCode), not just coordinates.
stopOvers Address[] optional Intermediate stops, in order.
passengerCount / luggageCount number optional Filter out products whose maxPassengers / maxLuggage is too small.
vehicleTypes string[] optional Restrict the quote to these vehicle types.
isReturnBooking boolean optional Price a return trip (see also returnRequestedDate).

Unknown fields are rejected by the pricing service (HTTP 422), so send only the fields above. The response is an array of priced products; a product whose vehicle type is already full at requestedDate is still returned but flagged available: false. When nothing can be priced the call answers 406 with an error body.

Open question for the API team: the exact per-product response fields (product id/name keys, price breakdown) and which request fields are mandatory are validated by the pricing service (tps.dispatchapi.io), outside this repo's sources — confirm before relying on them. The core quotation call is also stateless: the quoteId and expiresAt shown below are what the planned integrations.taxiid.nl /v1/quotes is expected to add, they do not exist in the core response.

POST /v1/rides

Subject: create a new ride booking. See the Ride object above for what quoteId's omission means (unconfirmed).

Idempotency — open question for the API team: there is no dedicated Idempotency-Key header anywhere in this API today. The closest confirmed precedent, from taxiID's other integrations (Wintax/Taxsys via integrations.taxiid.nl), is deduplication by looking up an existing ride with the same natural key (Ride.foreignBookingId) before creating a new one — not a header-based mechanism. Whether POST /v1/rides will apply that same dedup-by-foreignBookingId rule, a real Idempotency-Key header, or neither, is not yet decided. Until confirmed, do not assume a retry after a client-side timeout is safe — check GET /v1/rides/{id} (once built) or your own record of the foreignBookingId you sent before retrying, to avoid double-booking.

POST /v1/rides/{id}/cancel

Subject: cancel an existing ride. Confirmed in api.taxiid.nl: a ride can be cancelled from any non-terminal status (i.e. anything other than already completed or already canceled/canceled_driver/canceled_passenger — see the Ride status enum above); cancelling a terminal ride is rejected as an invalid state transition (409 invalid_state_transition, see Administration → Errors).

A cancellation charge can apply, governed by the company's own configured refund policy (an allow-list of chargeable cancellation reasons plus fee tiers) — not a fixed platform-wide fee. It only applies before the ride starts and only if the ride was already paid for. Open question for the API team: this depends on the caller supplying a cancellation reason, and on whether the (unbuilt) gateway endpoint accepts a reason field and reports back whether a charge applied — neither is confirmed yet for the Developer API specifically.

Webhook: ride.created

Subject: delivered when a ride you created is accepted into the system.

Webhook: ride.updated

Subject: delivered on other ride field changes. Off by default — enable it in the portal if you need it; it is noisier than ride.status_changed.

Generated from the source Markdown · run ./generate.sh to rebuild.