Skip to main content

Preferences

© 2026 Triplist, Inc.·Privacy·Terms·Developers

Triplist Search API

Comprehensive API reference for developers and AI agents.

Overview

The Triplist Search API lets you search for curated travel triplists — AI-generated or human-created travel plans with flights, hotels, apartments, car rentals, transfers, and trains.

Base URL: https://triplist.com/api/v1

Authentication: Bearer token in the Authorization header.

Authorization: Bearer YOUR_API_KEY

Search Endpoint

GET /api/v1/triplists/search

Returns paginated search results with optional facets.

Query Parameters

ParameterTypeDefaultDescription
qstring (1–200)Free-text search. Matches trip title, description, and place names with typo tolerance.
travelersinteger (≥ 1)Filter by traveler count.
dateStartISO 8601 dateTriplists with travel start date ≥ this value.
dateEndISO 8601 dateTriplists with travel end date ≤ this value.
priceMaxnumber (> 0)Maximum total cost in USD.
stylescomma-separatedTrip style tags, e.g. "ski,adventure".
sortenum"relevance""relevance", "price", "recent", "date"
cursorISO 8601 timestampPagination cursor from previous response.
limitinteger (1–100)20Results per page.
generatebooleanfalseAuto-create AI triplists for matching trips that don't have one.
itemTypescomma-separatedflight, hotel, apartment, car_rental, transfer, train
itemFiltersURL-encoded JSONPer-type filter object. See "Item Types & Filters" below.
searchModeenum"full_trip""full_trip" or "single_item"

At least one of q, travelers, dateStart, priceMax, styles, or itemTypes is required.

Example

curl "https://triplist.com/api/v1/triplists/search?q=paris+romantic&travelers=2&dateStart=2026-06-01&dateEnd=2026-06-14&sort=price&limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response Format

{
  "data": [
    {
      "kind": "triplist_card",
      "triplistId": "uuid",
      "parentTripId": "uuid",
      "travelerCount": 2,
      "travelerOrigin": { "city": "New York", "country": "US" },
      "travelerDates": { "start": "2026-06-01", "end": "2026-06-14" },
      "totalCost": "2450.00",
      "totalCostCurrency": "USD",
      "generationStatus": "complete",
      "score": 92.5,
      "createdAt": "2026-02-20T10:00:00Z",
      "updatedAt": "2026-02-20T12:30:00Z",
      "tripTitle": "Romantic Paris Getaway",
      "coverImageUrl": "https://...",
      "mainPlaces": [{ "name": "Paris", "country": "France" }],
      "tripDatesRange": { "start": "2026-06-01", "end": "2026-06-14" },
      "estimatedCost": "2000-3000",
      "styles": ["romantic", "cultural"]
    }
  ],
  "meta": {
    "cursor": "2026-02-20T10:00:00Z",
    "hasMore": true
  },
  "facets": {
    "styles": [{ "value": "romantic", "count": 45 }],
    "destination_names": [{ "value": "Paris", "count": 18 }],
    "destination_countries": [{ "value": "France", "count": 42 }]
  }
}

Field Reference

FieldTypeDescription
kind"triplist_card"Discriminator for polymorphic feeds.
triplistIdstring (UUID)Unique triplist identifier.
parentTripIdstring (UUID)Parent trip this triplist was generated from.
travelerCountnumberNumber of travelers.
travelerOriginobject | nullOrigin location of travelers.
travelerDates{ start, end } | nullRequested travel dates (ISO 8601).
totalCoststring | nullComputed total cost as decimal string (e.g. "1234.50").
totalCostCurrencystring | nullISO 4217 currency code (e.g. "USD").
generationStatusenum"pending", "in_progress", "complete", or "failed".
scorenumberRelevance/ranking score.
createdAtstringISO 8601 timestamp.
updatedAtstringISO 8601 timestamp.
tripTitlestring | nullDisplay title from the parent trip.
coverImageUrlstring | nullCover image URL.
mainPlacesarrayArray of { name, country } objects.
tripDatesRangeobject{ start, end } date range of the trip.
estimatedCoststring | nullFallback cost range when totalCost is null.
stylesstring[]Style/category tags (e.g. ["ski", "adventure"]).

Pagination

Use cursor-based pagination. Pass meta.cursor from the previous response as the cursor query parameter. When meta.hasMore is false, you've reached the end.

Streaming Endpoint

GET /api/v1/triplists/search/stream

Server-Sent Events (SSE) endpoint for real-time search results and AI generation progress. Accepts the same query parameters as the standard search endpoint.

  • Content-Type: text/event-stream
  • Heartbeat comment every 15 seconds to keep the connection alive
  • Supports reconnection via Last-Event-ID header

Event Types

initial-results

First batch of existing results (up to 20 items).

{ "cards": [ /* FeedTriplistCard[] */ ], "count": 20 }

new-trip

A single newly AI-generated triplist card.

{ "card": { /* FeedTriplistCard */ } }

generation-status

Status update for a triplist being generated. Polled every ~2 seconds.

{
  "triplistId": "uuid",
  "generationStatus": "in_progress",
  "totalCost": "1234.50",
  "totalCostCurrency": "USD",
  "score": 85.5
}

image-update

Cover image became available for a triplist.

{ "triplistId": "uuid", "coverImageUrl": "https://..." }

done

Stream is complete. Close the connection.

{ "totalCount": 25 }

error

An error occurred. The stream will close.

{ "message": "An unexpected error occurred." }

Item Types & Filters

Valid item types: flight, hotel, apartment, car_rental, transfer, train

Flight Filters

FilterTypeNotes
maxStopsnumber0 = direct, 1 = max 1 stop, 2 = max 2 stops
airlinesstring[]IATA codes, e.g. ["BA", "FR"]
maxPricenumberUSD
maxDurationMinutesnumberMax flight duration
cabinClassenum"economy", "premium_economy", "business", "first"
roundTripboolean

Hotel Filters

FilterTypeNotes
minStarsnumber1–5
maxPricenumberPer night, USD
amenitiesstring[]"pool", "wifi", "parking", "gym", "breakfast"
freeCancellationboolean

Apartment Filters

FilterTypeNotes
minBedroomsnumber
amenitiesstring[]"wifi", "kitchen", "washer", "parking"
maxPricenumberUSD
minNightsnumber
maxNightsnumber

Car Rental Filters

FilterTypeNotes
categoryenum"economy", "compact", "midsize", "suv", "van", "luxury"
transmissionenum"automatic", "manual"
minSeatsnumber
maxPricenumberUSD

Transfer Filters

FilterTypeNotes
vehicleTypeenum"shared", "private", "luxury"
minCapacitynumber
maxPricenumberUSD

Train Filters

FilterTypeNotes
classenum"standard", "first"
seatPreferenceenum"window", "aisle"
maxStopsnumber
maxPricenumberUSD

Passing Filters

Serialize the filter object as JSON and URL-encode it in the itemFilters parameter:

itemFilters={"flight":{"maxStops":1,"cabinClass":"business"},"hotel":{"minStars":4}}

URL-encoded:
itemFilters=%7B%22flight%22%3A%7B%22maxStops%22%3A1%2C%22cabinClass%22%3A%22business%22%7D%2C%22hotel%22%3A%7B%22minStars%22%3A4%7D%7D

Natural Language Query Parsing

The q parameter supports natural language. The API extracts structured criteria from free text:

InputParsed As
"paris for 2 adults"destination: Paris, travelers: 2
"ski trip in march"styles: ski, dateStart: March 1, dateEnd: March 31
"family beach vacation under $5000"styles: beach, priceMax: 5000
"tokyo 3 nights in april"destination: Tokyo, 3-night window in April
"romantic weekend getaway from NYC"styles: romantic, origin: New York City

Structured parameters (travelers, dateStart, etc.) take precedence over values parsed from q.

Error Handling

All errors follow this format:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description.",
    "details": {}
  }
}

Common Error Codes

CodeStatusDescription
INVALID_REQUEST400Missing required params or validation failure.
UNAUTHORIZED401Missing or invalid Bearer token.
FORBIDDEN403Insufficient permissions.
NOT_FOUND404Resource not found.
RATE_LIMITED429Too many requests. See rate limit headers.
INTERNAL_ERROR500Server error. Retry with backoff.
PROVIDER_ERROR502Upstream provider failure.
PROVIDER_UNAVAILABLE503Upstream provider temporarily unavailable.

Rate Limiting

The search endpoint uses the FEED rate-limit tier: 120 requests per 60-second sliding window.

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the window.
X-RateLimit-RemainingRequests remaining in the current window.
X-RateLimit-ResetUnix timestamp (seconds) when the window resets.

When rate-limited, you'll receive a 429 response. Wait until X-RateLimit-Reset before retrying.

Example: Full AI Agent Flow

Here's a step-by-step example of how an AI agent can use this API to answer "Find me a romantic trip to Paris for 2 people in June under $3000":

Step 1: Parse the user request

Extract: destination = Paris, travelers = 2, dates = June 2026, budget = $3000, style = romantic.

Step 2: Call the search API

curl "https://triplist.com/api/v1/triplists/search?q=paris+romantic&travelers=2&dateStart=2026-06-01&dateEnd=2026-06-30&priceMax=3000&styles=romantic&sort=price&limit=5&generate=true" \
  -H "Authorization: Bearer YOUR_API_KEY"

Step 3: Interpret results

  • Check the data array for results. Each card has totalCost, tripTitle, mainPlaces, and styles.
  • If generationStatus is "pending" or "in_progress", the AI is still building the triplist — connect to the streaming endpoint to watch progress.
  • Use meta.hasMore and meta.cursor to paginate if needed.

Step 4: Stream for real-time updates (optional)

const eventSource = new EventSource(
  "https://triplist.com/api/v1/triplists/search/stream" +
  "?q=paris+romantic&travelers=2&dateStart=2026-06-01" +
  "&dateEnd=2026-06-30&priceMax=3000&styles=romantic&generate=true"
);

eventSource.addEventListener("initial-results", (e) => {
  const { cards, count } = JSON.parse(e.data);
  // Display initial results immediately
});

eventSource.addEventListener("new-trip", (e) => {
  const { card } = JSON.parse(e.data);
  // Append newly generated triplist
});

eventSource.addEventListener("generation-status", (e) => {
  const { triplistId, generationStatus, totalCost } = JSON.parse(e.data);
  // Update progress indicator
});

eventSource.addEventListener("done", () => {
  eventSource.close();
});

Step 5: Present to user

Summarize the top results: trip title, destination, dates, total cost, and a link to the full triplist on Triplist.

Panel Component System

Triplist uses a shared panel system for filter dropdowns and configuration sheets. On desktop, panels render as positioned dropdowns; on mobile, they render as bottom sheets. Both share a single background color and footer component.

--panel-bg CSS variable

A CSS custom property defines the panel surface color for both light and dark mode:

  • Light: #ffffff
  • Dark: #2a2a2a

Use the Tailwind class bg-panel-bg to apply it.

ResponsivePanel

Wraps Dropdown (desktop) and BottomSheet (mobile) behind a single interface.

PropTypeDescription
isOpenbooleanWhether the panel is visible.
onClose() => voidClose handler.
titlestringTitle shown in the mobile bottom sheet header.
anchorRefRefObjectElement the desktop dropdown positions against.
childrenReactNodePanel content.

FilterPanelFooter

Shared footer used inside every filter panel. Layout: left-aligned text link (“Clear”) and right-aligned primary button (“Done” or “Apply”), separated by a top border.

PropTypeDefaultDescription
onClear() => voidReset / clear handler (left link).
onAction() => voidPrimary action handler (right button).
actionLabel“Apply” | “Done”“Done”Button label. Use “Apply” for deferred-apply panels, “Done” for immediate-apply.
actionDisabledbooleanfalseDisables the action button (e.g. incomplete date range).
clearLabelstring“Clear”Override the clear link text (e.g. “Clear all”).

Footer UX pattern

  • Immediate-apply panels (item type picker, capacity): changes take effect as the user interacts; the footer button is labeled “Done” and simply closes the panel.
  • Deferred-apply panels (date range): the footer button is labeled “Apply” and commits the selection on click.