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_KEYSearch Endpoint
GET /api/v1/triplists/searchReturns paginated search results with optional facets.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string (1–200) | — | Free-text search. Matches trip title, description, and place names with typo tolerance. |
travelers | integer (≥ 1) | — | Filter by traveler count. |
dateStart | ISO 8601 date | — | Triplists with travel start date ≥ this value. |
dateEnd | ISO 8601 date | — | Triplists with travel end date ≤ this value. |
priceMax | number (> 0) | — | Maximum total cost in USD. |
styles | comma-separated | — | Trip style tags, e.g. "ski,adventure". |
sort | enum | "relevance" | "relevance", "price", "recent", "date" |
cursor | ISO 8601 timestamp | — | Pagination cursor from previous response. |
limit | integer (1–100) | 20 | Results per page. |
generate | boolean | false | Auto-create AI triplists for matching trips that don't have one. |
itemTypes | comma-separated | — | flight, hotel, apartment, car_rental, transfer, train |
itemFilters | URL-encoded JSON | — | Per-type filter object. See "Item Types & Filters" below. |
searchMode | enum | "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
| Field | Type | Description |
|---|---|---|
kind | "triplist_card" | Discriminator for polymorphic feeds. |
triplistId | string (UUID) | Unique triplist identifier. |
parentTripId | string (UUID) | Parent trip this triplist was generated from. |
travelerCount | number | Number of travelers. |
travelerOrigin | object | null | Origin location of travelers. |
travelerDates | { start, end } | null | Requested travel dates (ISO 8601). |
totalCost | string | null | Computed total cost as decimal string (e.g. "1234.50"). |
totalCostCurrency | string | null | ISO 4217 currency code (e.g. "USD"). |
generationStatus | enum | "pending", "in_progress", "complete", or "failed". |
score | number | Relevance/ranking score. |
createdAt | string | ISO 8601 timestamp. |
updatedAt | string | ISO 8601 timestamp. |
tripTitle | string | null | Display title from the parent trip. |
coverImageUrl | string | null | Cover image URL. |
mainPlaces | array | Array of { name, country } objects. |
tripDatesRange | object | { start, end } date range of the trip. |
estimatedCost | string | null | Fallback cost range when totalCost is null. |
styles | string[] | 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/streamServer-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-IDheader
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
| Filter | Type | Notes |
|---|---|---|
maxStops | number | 0 = direct, 1 = max 1 stop, 2 = max 2 stops |
airlines | string[] | IATA codes, e.g. ["BA", "FR"] |
maxPrice | number | USD |
maxDurationMinutes | number | Max flight duration |
cabinClass | enum | "economy", "premium_economy", "business", "first" |
roundTrip | boolean |
Hotel Filters
| Filter | Type | Notes |
|---|---|---|
minStars | number | 1–5 |
maxPrice | number | Per night, USD |
amenities | string[] | "pool", "wifi", "parking", "gym", "breakfast" |
freeCancellation | boolean |
Apartment Filters
| Filter | Type | Notes |
|---|---|---|
minBedrooms | number | |
amenities | string[] | "wifi", "kitchen", "washer", "parking" |
maxPrice | number | USD |
minNights | number | |
maxNights | number |
Car Rental Filters
| Filter | Type | Notes |
|---|---|---|
category | enum | "economy", "compact", "midsize", "suv", "van", "luxury" |
transmission | enum | "automatic", "manual" |
minSeats | number | |
maxPrice | number | USD |
Transfer Filters
| Filter | Type | Notes |
|---|---|---|
vehicleType | enum | "shared", "private", "luxury" |
minCapacity | number | |
maxPrice | number | USD |
Train Filters
| Filter | Type | Notes |
|---|---|---|
class | enum | "standard", "first" |
seatPreference | enum | "window", "aisle" |
maxStops | number | |
maxPrice | number | USD |
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%7DNatural Language Query Parsing
The q parameter supports natural language. The API extracts structured criteria from free text:
| Input | Parsed 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
| Code | Status | Description |
|---|---|---|
INVALID_REQUEST | 400 | Missing required params or validation failure. |
UNAUTHORIZED | 401 | Missing or invalid Bearer token. |
FORBIDDEN | 403 | Insufficient permissions. |
NOT_FOUND | 404 | Resource not found. |
RATE_LIMITED | 429 | Too many requests. See rate limit headers. |
INTERNAL_ERROR | 500 | Server error. Retry with backoff. |
PROVIDER_ERROR | 502 | Upstream provider failure. |
PROVIDER_UNAVAILABLE | 503 | Upstream provider temporarily unavailable. |
Rate Limiting
The search endpoint uses the FEED rate-limit tier: 120 requests per 60-second sliding window.
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window. |
X-RateLimit-Remaining | Requests remaining in the current window. |
X-RateLimit-Reset | Unix 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
dataarray for results. Each card hastotalCost,tripTitle,mainPlaces, andstyles. - If
generationStatusis"pending"or"in_progress", the AI is still building the triplist — connect to the streaming endpoint to watch progress. - Use
meta.hasMoreandmeta.cursorto 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.
| Prop | Type | Description |
|---|---|---|
isOpen | boolean | Whether the panel is visible. |
onClose | () => void | Close handler. |
title | string | Title shown in the mobile bottom sheet header. |
anchorRef | RefObject | Element the desktop dropdown positions against. |
children | ReactNode | Panel 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.
| Prop | Type | Default | Description |
|---|---|---|---|
onClear | () => void | — | Reset / clear handler (left link). |
onAction | () => void | — | Primary action handler (right button). |
actionLabel | “Apply” | “Done” | “Done” | Button label. Use “Apply” for deferred-apply panels, “Done” for immediate-apply. |
actionDisabled | boolean | false | Disables the action button (e.g. incomplete date range). |
clearLabel | string | “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.