openapi: 3.2.0 info: title: Lokki Cart Suggestion API description: The main API powering the Lokki Dashboard and Online Store version: '2.0' contact: {} servers: [] security: - x-access-token: [] tags: - name: Cart Suggestion paths: /v2/cart-suggestion: post: operationId: suggestCarts parameters: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuggestCartRequestDto' responses: '200': description: '' content: application/json: schema: $ref: '#/components/schemas/SuggestCartResponseDto' tags: - Cart Suggestion /v2/cart-suggestion/precheck: post: operationId: precheckCartSuggestion parameters: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuggestCartRequestDto' responses: '200': description: '' content: application/json: schema: $ref: '#/components/schemas/PrecheckCartResponseDto' tags: - Cart Suggestion /v2/cart-suggestion/admin/search: post: operationId: searchCartsForAdmin parameters: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuggestCartRequestDto' responses: '200': description: '' content: application/json: schema: $ref: '#/components/schemas/AdminSearchCartResponseDto' tags: - Cart Suggestion /v2/cart-suggestion/{id}: get: operationId: getCartSuggestion parameters: - name: id required: true in: path schema: type: string responses: '200': description: '' content: application/json: schema: $ref: '#/components/schemas/SuggestCartResponseDto' tags: - Cart Suggestion components: schemas: SuggestCartParticipantDto: type: object properties: count: type: number minimum: 1 size: type: string audience: enum: - adult - child type: string description: Rider type (the funnel size encodes adult vs child) — stamped on the agency quote required: - count OpeningTimeFrame: type: object properties: id: type: string day: type: string enum: - monday - tuesday - wednesday - thursday - friday - saturday - sunday - ALL isOpen: type: boolean from: type: string to: type: string required: - id - day - isOpen SuggestCartDatesDto: type: object properties: startDate: format: date-time type: string endDate: format: date-time type: string required: - startDate - endDate SearchEngineLocationDto: type: object properties: point: $ref: '#/components/schemas/GeoPoint' address: type: - string - 'null' addressComponents: allOf: - $ref: '#/components/schemas/SearchEngineAddressComponentsDto' required: - point - address CompanyCategoryDiscount: type: object properties: categoryIds: type: array items: type: string ranges: type: array items: $ref: '#/components/schemas/CompanyCategoryDiscountRange' required: - categoryIds - ranges SuggestCartGeoLocDto: type: object properties: address: type: string locality: type: string description: City name from the address autocomplete — round-tripped so a search restored from a persisted suggestion keeps the real city instead of the full street address geoPoint: $ref: '#/components/schemas/SuggestCartGeoPointDto' required: - address - geoPoint DiscountFlat: type: object properties: percentage: type: number required: - percentage SuggestCartCategoryResultDto: type: object properties: categoryId: type: string description: ProductVerticaleCategory ObjectId items: type: array items: $ref: '#/components/schemas/SuggestCartItemDto' required: - categoryId - items SuggestCartEnrichedSuggestionDto: type: object properties: company: $ref: '#/components/schemas/ItemCompanyDetailsDto' rentalPeriod: description: 'The bookable rental window for THIS offer: the requested dates shifted by any flexible-day fallback and clamped into the store''s opening hours. Use it for both display and cart creation — the requested dates may fall outside this store''s hours (±4h tolerance match).' allOf: - $ref: '#/components/schemas/SuggestCartRentalPeriodDto' distanceInMeters: type: number isDeliveryOnly: type: boolean description: True when company is only reachable via delivery (>10km but within delivery range) matchWarnings: type: array items: $ref: '#/components/schemas/QuickCartMismatchReason' categories: type: array items: $ref: '#/components/schemas/SuggestCartCategoryResultDto' derived: description: Server-computed derived values for this offer (total, recommended flag). allOf: - $ref: '#/components/schemas/SuggestCartDerivedDto' required: - company - rentalPeriod - distanceInMeters - isDeliveryOnly - matchWarnings - categories - derived SuggestCartResponseDto: type: object properties: id: type: string description: Unique identifier for this suggestion set request: $ref: '#/components/schemas/SuggestCartRequestDto' suggestions: description: Up to 3 matching rental companies, each with server-computed derived values type: array items: $ref: '#/components/schemas/SuggestCartEnrichedSuggestionDto' quoteRequestId: type: string description: Id of the agency QuoteRequest created for this Quick Cart (logged-in users only). Carried by the frontend onto the cart so the quote can be cancelled on self-checkout. quoteRequestReused: type: boolean description: True when the quote request was deduplicated onto an existing recent identical Quick Cart quote — no new lead was created and no recap email was sent. Only set on the POST response. createdAt: type: string description: ISO timestamp of the submission — the funnel compares it to its stored state's lastUpdate to decide whether restoring this search would overwrite fresher user edits. required: - id - request - suggestions SuggestCartDerivedDto: type: object properties: totalTTC: type: - number - 'null' description: Offer total TTC over its counted lines (Product/Service/Pack), or null when any counted line is unpriced. The single source of truth for the total — consumers must not recompute it. isRecommended: type: boolean description: 'True for the recommended offer: the nearest bookable (priced, total > 0) suggestion. Computed server-side so the frontend and the suggestion email agree on the badge.' required: - totalTTC - isRecommended SuggestCartRentalPeriodDto: type: object properties: startDate: format: date-time type: string description: Effective rental start — clamped to the store's opening hours endDate: format: date-time type: string description: Effective rental end — clamped to the store's opening hours required: - startDate - endDate StoreInsuranceTulipDetails: type: object properties: provider: type: string enum: - TULIP status: enum: - 'off' - on_inclusion_hidden - on_inclusion - on_option - with_assurance type: string description: Tulip insurance status for the store example: on_inclusion required: - provider - status SearchEngineAddressComponents: type: object properties: streetAddress: type: - string - 'null' addressLocality: type: - string - 'null' postalCode: type: - string - 'null' addressCountry: type: - string - 'null' ClosedDate: type: object properties: startDate: format: date-time type: string endDate: format: date-time type: string required: - startDate - endDate SuggestCartGeoPointDto: type: object properties: latitude: type: number longitude: type: number required: - latitude - longitude SuggestCartItemType: type: string enum: - product - service - pack Discount: type: object properties: flatDiscounts: type: array items: $ref: '#/components/schemas/DiscountFlat' periodicDiscountsByCurrentDate: type: array items: $ref: '#/components/schemas/DiscountPeriodic' periodicDiscountsByStartDate: type: array items: $ref: '#/components/schemas/DiscountPeriodic' required: - flatDiscounts - periodicDiscountsByCurrentDate - periodicDiscountsByStartDate LowSeasonDate: type: object properties: from: format: date-time type: string to: format: date-time type: string required: - from - to DiscountPeriodic: type: object properties: percentage: type: number startDate: format: date-time type: string endDate: format: date-time type: string required: - percentage - startDate - endDate SuggestCartPackCategoryDto: type: object properties: id: type: string description: Pack option group id (PackOption.id) value: type: string description: Selected component product/service id for this group required: - id - value QuickCartMismatchReason: type: string description: The ways this reference is an approximate match for the request (wrong size, opposite bike family, …). Empty when the reference matches the request exactly. The frontend flags each reason to the user; a new dimension is added as an enum member, not a new field. enum: - SIZE_APPROXIMATE - CATEGORY_FALLBACK - GEO_OUTSIDE_NOMINAL_RADIUS - HOURS_APPROXIMATE - DATES_SHIFTED PrecheckCartResponseDto: type: object properties: hasResults: type: boolean description: Whether the search would yield at least one suggestion. Anonymous dry-run — deliberately reveals nothing else, so no renter data reaches unauthenticated clients. required: - hasResults SuggestCartItemDto: type: object properties: id: type: string productId: type: string description: Catalogue product id (search index externalId) used to create a cart from this suggestion type: $ref: '#/components/schemas/SuggestCartItemType' name: $ref: '#/components/schemas/LocalizedContent' description: $ref: '#/components/schemas/LocalizedContent' imageUrls: type: array items: type: string availableQuantity: type: number quantity: type: number description: Quantity of this reference allocated to fulfil the requested count for its category (the requested count is spread across references by availability) price: type: - number - 'null' description: Unit price for the rental duration, after discount matchWarnings: type: array items: $ref: '#/components/schemas/QuickCartMismatchReason' packCategories: description: 'PACK only: the resolved component chosen per pack option group (one entry per group). Fed verbatim into the cart''s pack line `categories[]` so the offer is bookable without a further composition step.' type: array items: $ref: '#/components/schemas/SuggestCartPackCategoryDto' packIncludedItems: description: 'PACK only: localized names of the components included beyond the requested bike (e.g. a helmet, a lock) — drives the ''Inclut un casque'' highlight on the offer card.' type: array items: $ref: '#/components/schemas/LocalizedContent' required: - id - productId - type - name - description - imageUrls - availableQuantity - quantity - price - matchWarnings SuggestCartRequestDto: type: object properties: dates: $ref: '#/components/schemas/SuggestCartDatesDto' geoLoc: $ref: '#/components/schemas/SuggestCartGeoLocDto' query: type: array items: $ref: '#/components/schemas/SuggestCartQueryEntryDto' rentalPlaceUserId: type: string description: RentalPlaceUser ObjectId — triggers email delivery when provided lang: enum: - fr - en - es - pt - it - pl - de - nl type: string description: Language for the cart suggestion email flexibility: $ref: '#/components/schemas/QuoteFlexibility' isDateFlexible: type: boolean deprecated: true description: Deprecated. Use `flexibility` instead. `true` retains the legacy ±3-day behavior. required: - dates - geoLoc - query ItemCompanyInsurance: type: object properties: enabled: type: boolean details: $ref: '#/components/schemas/StoreInsuranceTulipDetails' required: - enabled - details CompanyCurrency: type: object properties: isoName: type: string symbol: type: string required: - isoName - symbol LocalizedContent: type: object additionalProperties: type: string ItemCompanyDetailsDto: type: object properties: allCompanySubCategories: type: array items: type: string closestLocation: allOf: - $ref: '#/components/schemas/SearchEngineLocationDto' id: type: string name: type: string geopoint: $ref: '#/components/schemas/GeoPoint' deliveryEnabled: type: boolean deliveryOnly: type: boolean deliveryMaxRange: type: number displayAddressOnlineStore: type: boolean directOrderLCD: type: boolean directOrderLLD: type: boolean insurance: $ref: '#/components/schemas/ItemCompanyInsurance' googleRatingsCount: type: number googleRatingsScore: type: number logo: type: string coverImages: type: array items: type: string slug: type: string currency: $ref: '#/components/schemas/CompanyCurrency' address: type: string freeCancelation: type: boolean lowSeasonDates: type: array items: $ref: '#/components/schemas/LowSeasonDate' minutesBeforeBooking: type: number openingHours: type: array items: $ref: '#/components/schemas/OpeningTimeFrame' openingHoursLowSeason: type: array items: $ref: '#/components/schemas/OpeningTimeFrame' discount: $ref: '#/components/schemas/Discount' categoryDiscounts: type: array items: $ref: '#/components/schemas/CompanyCategoryDiscount' timezone: type: string locations: type: array items: $ref: '#/components/schemas/SearchEngineLocation' phoneNumber: type: - string - 'null' closedDates: type: array items: $ref: '#/components/schemas/ClosedDate' createdAt: format: date-time type: string deliveryMinPrice: type: - number - 'null' required: - allCompanySubCategories - closestLocation - id - name - geopoint - deliveryEnabled - deliveryOnly - deliveryMaxRange - displayAddressOnlineStore - directOrderLCD - directOrderLLD - insurance - logo - coverImages - slug - currency - address - freeCancelation - lowSeasonDates - minutesBeforeBooking - openingHours - openingHoursLowSeason - discount - categoryDiscounts - timezone - locations - phoneNumber - closedDates SearchEngineLocation: type: object properties: point: $ref: '#/components/schemas/GeoPoint' address: type: - string - 'null' addressComponents: allOf: - $ref: '#/components/schemas/SearchEngineAddressComponents' required: - point - address AdminSearchCartResponseDto: type: object properties: request: description: The demand that was matched allOf: - $ref: '#/components/schemas/SuggestCartRequestDto' suggestions: description: 'Matching rental companies with the same shape as the Quick Cart suggestions, nearest first. Nothing is persisted: no suggestion document, no quote request, no email.' type: array items: $ref: '#/components/schemas/SuggestCartEnrichedSuggestionDto' required: - request - suggestions QuoteFlexibility: type: string description: How flexible the user's rental dates are — persisted as-is on the agency QuoteRequest and surfaced in the suggestion email. Defaults to EXACT_DATES when omitted. enum: - exact_dates - more_less_1_day - more_less_2_days - more_less_3_days - more_less_7_days SuggestCartQueryEntryDto: type: object properties: categoryId: type: string description: ProductVerticaleCategory ObjectId — the group leader for merged tiles mergedCategoryIds: description: Additional category IDs that form an OR group with categoryId. Items from any of these categories satisfy this demand — the requested quantity is allocated from the combined pool, not separately per category. type: array items: type: string fallbackCategoryIds: description: 'Lower-priority fallback category IDs: the opposite bike family (électrique ↔ classique) for the essentials tiles. The preferred pool (categoryId + mergedCategoryIds) is allocated first; only the remaining shortfall is filled from these categories.' type: array items: type: string categoryName: description: Localized category name — used in email template when provided allOf: - $ref: '#/components/schemas/LocalizedContent' participants: type: array items: $ref: '#/components/schemas/SuggestCartParticipantDto' required: - categoryId - participants CompanyCategoryDiscountRange: type: object properties: discount: type: number description: Discount value for the range discountType: enum: - AMOUNT - PERCENTAGE type: string description: Type of discount applied (amount or percentage) rangeFrom: type: - number - 'null' description: Minimum value of the range (Quantity of items) rangeTo: type: - number - 'null' description: Maximum value of the range (Quantity of items) infiniteTo: type: boolean description: Indicates if the range has no upper limit required: - discount - discountType - rangeFrom - rangeTo - infiniteTo SearchEngineAddressComponentsDto: type: object properties: streetAddress: type: - string - 'null' addressLocality: type: - string - 'null' postalCode: type: - string - 'null' addressCountry: type: - string - 'null' GeoPoint: type: object properties: latitude: type: number longitude: type: number required: - latitude - longitude securitySchemes: x-access-token: scheme: bearer bearerFormat: JWT type: apiKey in: header name: x-access-token