How to Use TREK's Booking Import Feature with KDE Itinerary for PDF and Email Extraction

TREK uses the KDE Itinerary binary (kitinerary-extractor) to parse travel confirmations from PDFs, emails, and other formats into structured reservations, with optional AI fallback and a two-step preview-to-confirm workflow.

TREK is an open-source travel planning platform that automates reservation creation from travel documents. The booking import feature leverages KDE Itinerary's extraction capabilities to turn PDFs, EML files, and PKPass data into fully-fledged trip reservations. This integration eliminates manual data entry by parsing confirmation emails and boarding passes directly into your itinerary.

Architecture Overview

The integration is built around four distinct layers that handle everything from HTTP requests to database persistence.

Controller Layer

The BookingImportController in server/src/nest/booking-import/booking-import.controller.ts exposes the REST endpoints for the import workflow. It accepts multipart file uploads at POST /api/trips/:tripId/reservations/import/booking, validates user permissions, and initiates the extraction process. The controller also manages asynchronous imports via the /booking/async endpoint and handles final confirmation through /booking/confirm.

Service Layer

BookingImportService (located in server/src/nest/booking-import/booking-import.service.ts) orchestrates the entire extraction pipeline. This service coordinates between the KDE Itinerary binary, optional LLM parsing, and TREK's internal data models. It handles the heavy lifting of geocoding endpoints, resolving trip days, and persisting confirmed reservations through createReservation.

Extraction Layer

The KitineraryExtractorService in server/src/nest/booking-import/kitinerary-extractor.service.ts serves as a wrapper around the external kitinerary-extractor binary. When invoked, it writes uploaded files to temporary storage and executes the binary, capturing the JSON-LD output as an array of KiReservation objects defined in server/src/nest/booking-import/kitinerary.types.ts.

Mapping Layer

kitinerary-mapper.ts transforms the raw KDE Itinerary output into TREK's internal ParsedBookingItem format. Based on the @type field (such as FlightReservation or HotelReservation), it routes each object to specialized mappers like mapFlight or mapLodging. This layer also enriches airport data via airportService.findByIata and flags AI-derived content for review.

The Booking Import Workflow

The import process follows a strict seven-step pipeline from file upload to persisted reservation.

  1. Upload and Validation – The frontend sends a multipart/form-data POST request. BookingImportController.validateImport verifies write access to the trip, validates the import mode (no-ai, fallback-on-empty, or force-ai), and checks file types and size limits.

  2. KItinerary Extraction – If the mode is not force-ai and the binary is present, KitineraryExtractorService.extract executes kitinerary-extractor against the temporary file. The binary returns a JSON-LD array of reservations.

  3. LLM Fallback – When the mode is fallback-on-empty or force-ai, LlmParseService processes the document using a language model to produce equivalent KiReservation objects.

  4. Data Mapping – mapReservations iterates over the extraction results, converting each KiReservation into a ParsedBookingItem. It normalizes ISO-8601 dates using the toIsoString helper and resolves IATA codes to full airport metadata.

  5. Preview Response – The service returns a list of ParsedBookingItem objects and any extraction warnings. Items derived from AI automatically include needs_review: true to flag them for user verification.

  6. User Confirmation – The frontend POSTs selected items to /api/trips/:tripId/reservations/import/booking/confirm. The user can edit details, accept valid entries, or reject incorrect extractions.

  7. Persistence – BookingImportService.confirm auto-creates venue places via createPlace, geocodes missing coordinates using searchNominatim, resolves check-in/out days through resolveDayId, and finally persists the reservation with createReservation. WebSocket events broadcast the new reservations to connected clients.

Configuring the KItinerary Binary

TREK locates the kitinerary-extractor binary using a hierarchical search strategy. The system checks for the binary in the following order:

  • The path specified by the KITINERARY_EXTRACTOR_PATH environment variable
  • The system library path at /usr/lib/*/libexec/kf6/kitinerary-extractor
  • The system PATH for an executable named kitinerary-extractor

The binary must have read access to temporary files created by TREK and must output valid JSON-LD conforming to the KiReservation interface. Date handling in KDE Itinerary can produce either plain ISO strings or wrapped KiDateTime objects; TREK's mapper normalizes both to simple YYYY-MM-DDTHH:MM format.

API Usage Examples

Preview Upload (Synchronous)

Extract reservations from PDFs and emails before confirming them:

curl -X POST "https://your.trek.instance/api/trips/12345/reservations/import/booking" \
  -H "Authorization: Bearer <JWT>" \
  -F "files=@/path/to/booking.pdf" \
  -F "files=@/path/to/confirmation.eml" \
  -F "mode=no-ai"

The response contains a preview of extracted items:

{
  "items": [
    {
      "type": "flight",
      "title": "Delta DL123 (JFK → LHR)",
      "reservation_time": "2024-10-12T14:30",
      "reservation_end_time": "2024-10-13T06:45",
      "confirmation_number": "ABCDEF",
      "metadata": { "airline": "Delta", "flight_number": "DL123" },
      "endpoints": [
        { "role":"from","name":"John F. Kennedy Intl (JFK)","code":"JFK","lat":40.6398,"lng":-73.7789,"timezone":"America/New_York","local_time":"14:30","local_date":"2024-10-12"},
        { "role":"to","name":"London Heathrow (LHR)","code":"LHR","lat":51.4700,"lng":-0.4543,"timezone":"Europe/London","local_time":"06:45","local_date":"2024-10-13"}
      ],
      "needs_review": false,
      "source": { "fileName":"booking.pdf","index":0 }
    }
  ],
  "warnings": []
}

Confirm Imported Items

Persist the validated items to your trip:

curl -X POST "https://your.trek.instance/api/trips/12345/reservations/import/booking/confirm" \
  -H "Authorization: Bearer <JWT>" \
  -H "Content-Type: application/json" \
  -d '{
        "items": [
          {
            "type":"flight",
            "title":"Delta DL123 (JFK → LHR)",
            "reservation_time":"2024-10-12T14:30",
            "reservation_end_time":"2024-10-13T06:45",
            "confirmation_number":"ABCDEF",
            "metadata":{"airline":"Delta","flight_number":"DL123"},
            "endpoints":[
              {"role":"from","name":"John F. Kennedy Intl (JFK)","code":"JFK","lat":40.6398,"lng":-73.7789,"timezone":"America/New_York","local_time":"14:30","local_date":"2024-10-12"},
              {"role":"to","name":"London Heathrow (LHR)","code":"LHR","lat":51.4700,"lng":-0.4543,"timezone":"Europe/London","local_time":"06:45","local_date":"2024-10-13"}
            ],
            "source":{"fileName":"booking.pdf","index":0}
          }
        ]
      }'

The response returns the created reservation objects:

{
  "created": [
    {
      "id": 987,
      "type": "flight",
      "title": "Delta DL123 (JFK → LHR)",
      "trip_id": "12345"
    }
  ]
}

Asynchronous Import (Background Jobs)

For large batches or slow extractions, use the async endpoint:

curl -X POST "https://your.trek.instance/api/trips/12345/reservations/import/booking/async" \
  -H "Authorization: Bearer <JWT>" \
  -F "files=@/path/to/many-files.zip" \
  -F "mode=fallback-on-empty"

Response:

{ "jobId": "8a4f5c2d-c3b1-4e7a-9a23-f1e2b9d7c0e1" }

Poll the job status:

curl -X GET "https://your.trek.instance/api/trips/12345/reservations/import/jobs/8a4f5c2d-c3b1-4e7a-9a23-f1e2b9d7c0e1" \
  -H "Authorization: Bearer <JWT>"

The result contains status, done, total, and either the preview result or an error message.

Data Mapping and Enrichment

The mapping process transforms KDE Itinerary's generic KiReservation objects into TREK-specific schemas. For flight reservations, mapFlight extracts IATA codes and queries airportService.findByIata to populate latitude, longitude, city names, and timezone data. For non-airport endpoints like train stations or bus stops, the system defers geocoding to the confirmation stage via searchNominatim.

The mapper also handles edge cases in date representation. KDE Itinerary sometimes wraps timestamps in KiDateTime objects; the toIsoString helper normalizes these to standard ISO strings. Each mapped item inherits a source object tracking the original filename and extraction index, enabling precise audit trails.

AI Fallback Modes

TREK supports three extraction strategies controlled by the mode parameter:

  • no-ai – Uses only the KDE Itinerary binary. If extraction fails or returns empty, the import fails.
  • fallback-on-empty – Attempts KDE Itinerary extraction first. If the binary returns no results or errors, automatically falls back to LlmParseService for AI-powered extraction.
  • force-ai – Skips the binary entirely and routes all files through the LLM parser.

Items produced via AI are always marked with needs_review: true in the preview response, ensuring users verify machine-generated data before persistence.

Summary

  • TREK's booking import uses KitineraryExtractorService to wrap the KDE Itinerary binary and convert PDFs/EMLs into structured data.
  • The workflow is split between preview (BookingImportController.preview) and confirmation (BookingImportService.confirm) to allow user verification.
  • Three extraction modes (no-ai, fallback-on-empty, force-ai) provide flexibility for documents that resist standard parsing.
  • Data enrichment occurs in two phases: immediate airport resolution via airportService.findByIata during mapping, and deferred geocoding via searchNominatim during confirmation.
  • All source files are located in server/src/nest/booking-import/ with type definitions in kitinerary.types.ts and schemas in shared/src/reservation/reservation.schema.ts.

Frequently Asked Questions

Where does TREK look for the kitinerary-extractor binary?

TREK searches for the binary in three locations in order: the path defined by the KITINERARY_EXTRACTOR_PATH environment variable, the system library path at /usr/lib/*/libexec/kf6/kitinerary-extractor, and finally the system PATH for an executable named kitinerary-extractor. Ensure the binary is executable and accessible to the TREK server process.

What happens if KDE Itinerary cannot parse a file?

If the binary fails to extract data and you are using fallback-on-empty mode, TREK automatically routes the file to LlmParseService for AI-powered extraction. In no-ai mode, the import fails for that specific file and returns a warning in the response. In force-ai mode, the binary is never called, and the LLM handles all parsing.

Can I import multiple files at once?

Yes. The /booking and /booking/async endpoints accept multiple files in a single multipart/form-data request. Simply append multiple -F "files=@path/to/file" arguments to your curl command or form data. The service processes each file sequentially and aggregates the results into a single preview response.

How does TREK handle timezones and local times?

During the mapping phase in kitinerary-mapper.ts, TREK resolves airport IATA codes to full timezone data using airportService.findByIata. The mapper calculates local_time and local_date for each endpoint based on the airport's timezone. For non-airport locations, geocoding during the confirmation stage determines the timezone if not provided by the extraction source.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →