Your itinerary.
In your tools, too.
Read trips, add trip items and connect an AI chat to Triivl. You choose which trips it can access and what it can do.
https://api.triivl.app/v1https://api.triivl.app/mcpiCloud is the primary store. The API reads a private, synchronized copy of your selected trips. Changes are sent to the app, which checks and saves them. Keep the app open to process new changes.
From settings to your first request
- Enable API access in Triivl
Open Settings → API and MCP. Enable API access, select your own trips and choose the permissions you want. Save your settings.
- Create an API key
Name the connection and copy the key when it appears. The key is shown only once and expires within 90 days. The new settings screen must be installed in your app.
- Let the app synchronize, then read your trips
Keep the app open while your selected trips synchronize. Send the key as a Bearer token with your request.
# Set the key locally. Do not put it in code or Git.
export TRIIVL_API_KEY='YOUR_API_KEY'
curl 'https://api.triivl.app/v1/trips' \
--header "Authorization: Bearer $TRIIVL_API_KEY"{
"trips": [
{
"id": "e27a8846-c0c9-42e5-a308-d35c060e336e",
"title": "Weekend in Ålesund",
"kind": "personal",
"startDay": "2026-11-06",
"endDay": "2026-11-08",
"deletedAt": null,
"revision": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"syncedAt": "2026-10-14T19:10:00.000Z"
}
]
}All IDs, dates and revisions on this page are examples. Use the ID and revision from your own response.
You choose the permissions
API and MCP have separate settings. Both are off by default. An API key grants REST access, while an approved MCP connection grants MCP access. The app's sign-in token cannot be used as an integration key.
| Permission | Scope | Allows |
|---|---|---|
| Read | trips:read | Read selected trips and trip items. |
| Create | trips:create | Create trips and trip items. |
| Update | trips:update | Update selected trips and items. |
| Delete and restore | trips:delete | Soft delete and restore within 30 days. |
| Read booking references | trips:bookings | Read booking and confirmation codes. Also requires read access. |
This permission is off by default. Without it, bookingReference and confirmationCode are omitted from read responses. A connection with create/update permission can still enter a reference without being allowed to read existing codes.
Changing settings does not automatically expand an existing key or connection. Create a new key or approve the MCP connection again to grant additional permissions. Access is checked on every request and can be revoked in the app.
Documents, photos, receipts, private notes, seats, precise addresses, coordinates and packing lists are excluded from this API.
Trips
All REST requests below require Authorization: Bearer YOUR_API_KEY. {tripId} is the ID from the trip response.
/v1/tripstrips:readList active trips selected for API access. Use ?deleted=true to list recently deleted trips. The response is { "trips": [...] }, with ID, title, dates, revision and last synchronization time.
/v1/trips/{tripId}trips:readRead the full trip, including ideas and deleted items. The response contains trip, revision, syncedAt and bookingReferencesIncluded. Items are in trip.items.
export TRIP_ID='ID_FROM_TRIP_LIST'
curl "https://api.triivl.app/v1/trips/$TRIP_ID" \
--header "Authorization: Bearer $TRIIVL_API_KEY"/v1/tripstrips:createCreate a trip. requestId also becomes the trip's ID. A title is required. kind is personal or work, and defaults to personal. Dates are optional.
REQUEST_ID="$(uuidgen)"
curl --request POST 'https://api.triivl.app/v1/trips' \
--header "Authorization: Bearer $TRIIVL_API_KEY" \
--header 'Content-Type: application/json' \
--data "{
\"requestId\": \"$REQUEST_ID\",
\"trip\": {
\"title\": \"Weekend in Ålesund\",
\"kind\": \"personal\",
\"startDay\": \"2026-11-06\",
\"endDay\": \"2026-11-08\"
}
}"/v1/trips/{tripId}trips:updateInclude only the fields you want to change in trip. Read the trip first, then copy its latest revision to expectedRevision.
export REVISION='REVISION_FROM_GET_TRIP'
REQUEST_ID="$(uuidgen)"
curl --request PATCH "https://api.triivl.app/v1/trips/$TRIP_ID" \
--header "Authorization: Bearer $TRIIVL_API_KEY" \
--header 'Content-Type: application/json' \
--data "{
\"requestId\": \"$REQUEST_ID\",
\"expectedRevision\": \"$REVISION\",
\"trip\": { \"title\": \"Long weekend in Ålesund\" }
}"/v1/trips/{tripId}trips:deleteMove the trip to Recently Deleted. Send JSON with requestId and expectedRevision, without trip or item.
REQUEST_ID="$(uuidgen)"
curl --request DELETE "https://api.triivl.app/v1/trips/$TRIP_ID" \
--header "Authorization: Bearer $TRIIVL_API_KEY" \
--header 'Content-Type: application/json' \
--data "{
\"requestId\": \"$REQUEST_ID\",
\"expectedRevision\": \"$REVISION\"
}"/v1/trips/{tripId}/restoretrips:deleteRestore a deleted trip within 30 days. Use the same JSON fields as for deletion, with a new requestId and the revision of the deleted trip.
Trip items
Trip items are flights, hotels, rental cars and other activities in a trip. New items default to ideas. Set planningState: "confirmed" to add an item to the itinerary. The API does not make bookings or purchases.
/v1/trips/{tripId}/itemstrips:createAdd an item. type and title are required. requestId becomes the item's ID. expectedRevision is the trip's latest revision.
REQUEST_ID="$(uuidgen)"
curl --request POST "https://api.triivl.app/v1/trips/$TRIP_ID/items" \
--header "Authorization: Bearer $TRIIVL_API_KEY" \
--header 'Content-Type: application/json' \
--data "{
\"requestId\": \"$REQUEST_ID\",
\"expectedRevision\": \"$REVISION\",
\"item\": {
\"type\": \"lodging\",
\"title\": \"Hotel in Ålesund\",
\"city\": \"Ålesund\",
\"planningState\": \"idea\",
\"startDate\": \"2026-11-06T15:00:00+01:00\",
\"endDate\": \"2026-11-08T11:00:00+01:00\",
\"startTimeZoneID\": \"Europe/Oslo\",
\"endTimeZoneID\": \"Europe/Oslo\"
}
}"/v1/trips/{tripId}/items/{itemId}trips:updateUpdate an existing item with requestId, expectedRevision and an item containing the changed fields. An existing item's type cannot be changed. Optional text/date fields that accept null can be cleared with null.
{
"requestId": "c90d02d6-dcd4-479a-8cd3-d02c8df1dcf1",
"expectedRevision": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"item": { "title": "Hotel Brosundet", "planningState": "confirmed" }
}/v1/trips/{tripId}/items/{itemId}trips:deleteDelete the item, with requestId and the trip's expectedRevision in the JSON body. The item can be restored within 30 days.
/v1/trips/{tripId}/items/{itemId}/restoretrips:deleteRestore the item with a new requestId and the trip's latest expectedRevision. The parent trip must be active.
Follow the change to completion
A write response with 202 Accepted means the request is queued. It is saved in the app only when its status is applied. The app checks current permissions and revision before saving.
/v1/trip-commands/{requestId}Read status with the same key that sent the change. The connection must still have the required permission and trip access. id in the write response is the request's requestId.
export REQUEST_ID='ID_FROM_WRITE_RESPONSE'
curl "https://api.triivl.app/v1/trip-commands/$REQUEST_ID" \
--header "Authorization: Bearer $TRIIVL_API_KEY"| Status | Meaning |
|---|---|
pending | Waiting for the app. Keep it open. |
applying | One installation has claimed the change. Wait for the result. |
applied | The app has saved the change. |
rejected | The change was not saved. Read resultCode and update the trip/revision if needed. |
cancelled | A pending request has been withdrawn. |
unconfirmed | A started change has no confirmed result. Read the current trip before retrying. |
Safe retries
Generate a new UUID for each new change, for example with uuidgen. After a network failure, repeat the same request with the same requestId, body and key. Do not generate a new ID for that retry: keep REQUEST_ID and repeat only the curl call, not the uuidgen line. Reusing an ID with a different body or connection is rejected.
Only one queued/in-progress change is allowed per trip. Wait for the result and read the trip's new revision before the next change. Pending requests expire after 24 hours. Status records are retained for up to 90 days.
All changes except creating a new trip require expectedRevision. It must match the latest 64-character revision from the API. If the trip has changed in the meantime, the request is rejected with revision_conflict.
Fields and formats
Trip · trip
| Field | Format | Details |
|---|---|---|
title | Text | Required when creating, 1–500 characters. |
kind | personal / work | Default: personal. |
startDay, endDay | YYYY-MM-DD or null | Optional valid calendar dates. End must be on or after start. |
Trip item · item
| Field | Format / allowed values |
|---|---|
type | Required when creating: flight, train, bus, transit, ferry, privateCar, taxi, carRental, lodging, restaurant, attraction, stop, generic. |
title | Required when creating, 1–500 characters. |
subtitle, city, origin, destination, flightNumber | Optional text, up to 500 characters, or null. |
planningState | idea (default) or confirmed. |
startDate, endDate | Optional ISO 8601 timestamps with an offset, e.g. 2026-11-06T15:00:00+01:00, or null. End must be at or after start. |
startTimeZoneID, endTimeZoneID | Optional valid IANA time zones, e.g. Europe/Oslo. |
allDay | Optional boolean: true or false. |
bookingReference, confirmationCode | Optional text, up to 200 characters, or null. Reading requires a separate scope. |
Unknown fields are rejected. IDs are UUIDs. revision is 64 hexadecimal characters. syncedAt shows when the backend last received the copy; it does not guarantee that a closed app has sent its latest changes.
Talk to Triivl from an AI chat
Use a client that supports MCP with Streamable HTTP and OAuth. Add Triivl's server URL, then approve access in the app.
https://api.triivl.app/mcp- Enable MCP in the app
Enable MCP and choose permissions under Settings → API and MCP. “All my trips” is the default and includes future trips. Choose “Select individual trips” to restrict access. API access does not need to be enabled. Booking references require a separate choice.
- Add the server in your AI client
Choose an external MCP server, enter the URL above and start the client's OAuth sign-in.
- Approve the connection in Triivl
The approval page opens the app. Check the client's name and permissions before approving. If the link does not open the app, enter the request code from the page manually in settings. The request lasts five minutes.
Ongoing trips and individual items
Use get_agenda for “what is next?” and “tomorrow”. One call returns the ongoing trip, next activity and confirmed items for the date, including unassigned items. Timestamps include local time zones. An 11:00 flight arrival can yield an 11:30 taxi suggestion; this is a minimum 30-minute planning buffer, rounded up to the next quarter-hour, not a booking.
create_item accepts an omitted tripId. The item is created as an idea without an artificial parent trip. assign_item attaches it later, or unassigns it with destinationTripId: null. Both sides require update permission and current revisions. “All my trips” includes unassigned items; individual selection offers a separate “Items without a trip” choice.
Smarter reading and writing
The tools compute their answers deterministically from the synchronized copies. No AI model is used, no new data is uploaded, and every tool follows the permissions and trip selection you chose. Booking references appear only with their own opt-in; seats, addresses and notes are never included.
get_agendanow also returnsderivedper activity: minutes until start (startsInMinutes), trip day (“Day 2 of 3”),departsInMinutesandarrivesInMinutesfor flights, and check-in/check-out context for lodging.conflictswarns about overlaps and tight connections. Existing fields are unchanged. Live flight status is not included.search_itemssearches title, subtitle, city, origin, destination and flight number, with an optional date range, type and trip. Results are ranked and bounded bylimit.check_planreviews one trip: nights without lodging, overlapping items, tight connections, transport that ends somewhere other than where the next item starts (only when both places are known and comparable), items without a time and the number of unconfirmed ideas.summarize_tripgives a short overview with days, cities in order and counts per type.create_itemandassign_itemreturnhintsnext to the queued change:possibleDuplicatesand, without a trip,suggestedTripswhose dates contain the item or are one day away. These are suggestions. Nothing is assigned or changed automatically. Without read access,hintsis onlychecked: false.parse_booking_textreads pasted confirmation text with Triivl’s existing rule-based import reader and proposes items shaped likecreate_item. It saves and queues nothing; the chat shows the proposals and callscreate_itemafter you agree. Flights use the airports’ time zones; passtimeZonefor other places.
“Show my trips and add a hotel idea in Ålesund to my November trip.”
Requires read and create access to the selected trip.| Tool | Scope |
|---|---|
get_agenda, list_items, get_item, list_trips, get_trip, search_items, check_plan, summarize_trip | trips:read |
create_trip, create_item, parse_booking_text | trips:create |
update_trip, update_item, assign_item | trips:update |
delete_trip, delete_item, restore_trip, restore_item | trips:delete |
get_change_status | The same permission and connection as the change. |
The client is offered only the tools it is allowed to use. Write tools use the same queue and revision checks as REST. The chat client must use get_change_status to confirm the change was saved.
Connecting an external AI client means that client receives the data you permit. You can disconnect it in the app at any time.
OAuth for MCP clients
Triivl supports public clients without a client secret, exact redirect URIs and Authorization Code with PKCE S256. Clients should read discovery metadata instead of hardcoding their configuration.
| Method | URL | Purpose |
|---|---|---|
| GET | /.well-known/oauth-protected-resource/mcp | MCP resource and authorization server. |
| GET | /.well-known/oauth-authorization-server | Discovery: scopes, PKCE and endpoints. |
| POST | /oauth/register | Register a client name and redirect URIs. |
| GET | /oauth/authorize | Start approval in the app. |
| POST | /oauth/token | Exchange a one-time code or rotate a refresh token. |
| POST | /oauth/revoke | Revoke a connection. |
Parameters and token lifetimes
Registration requires client_name and redirect_uris. Redirects must use HTTPS, or HTTP on loopback 127.0.0.1 / [::1], without fragments or user information.
Authorization requires response_type=code, client_id, redirect_uri, state, code_challenge, code_challenge_method=S256 and resource=https://api.triivl.app/mcp. scope is a space-separated list, defaulting to trips:read.
Metadata advertises all supported scopes. The app shows and approves only the requested subset that the user has enabled. The client is not granted permissions beyond the approval shown in the app.
The one-time code lasts 60 seconds. Access tokens last 15 minutes. Refresh tokens rotate on use and have an absolute lifetime of 30 days. The token endpoint accepts JSON or URL-encoded form data. See OpenAPI for all request and response fields.
MCP uses stateless HTTP POST with JSON responses. GET and DELETE on /mcp return 405; the server does not use SSE or persistent MCP sessions. Unauthorized POST returns 401 with a WWW-Authenticate header pointing to resource metadata.
Errors and limits
| HTTP | Common cause | What to do |
|---|---|---|
400 | Invalid body, date, UUID or unknown field (invalid_request). | The message names each field and what was expected; error.details carries the same per field. Values you sent are never echoed. |
401 | Missing, expired, invalid or revoked token. | Use a valid integration key or complete OAuth. |
403 | Missing permission or trip access. | Check settings and connection scopes. |
404 | The trip has not synchronized, or the request does not exist for this connection. | Keep the app open and check the ID. |
409 | revision_conflict, or an ID reused with different data. | Read the latest trip/status. Do not overwrite using an old revision. Several changes may wait on one trip; the app applies them in order, each on the revision the one before produced. |
429 | Too many requests: 300 a minute per key or connection (rate_limited). | Wait before retrying. Honor Retry-After when provided. |
500 | Server error. | Retry write requests with the same requestId and body. |
{
"error": {
"code": "revision_conflict",
"message": "Trip has changed. Read its current revision."
}
}Individual selection allows up to 1,000 selected trips per channel. MCP with “All my trips” has no selection-count limit. Up to 1,000 items per trip. Key/client names may contain up to 100 characters. Private integration responses use Cache-Control: no-store. Read responses are synchronized copies; check syncedAt.
Lists are paged: GET /v1/items and list_items answer at most 100 items (limit, max 500) from offset, ordered by start, with total, truncated and nextOffset; a trip's items are paged the same way with itemLimit and itemOffset. Everything MCP can read is a REST route too: GET /v1/items/search, GET /v1/trips/{id}/plan, GET /v1/trips/{id}/summary and POST /v1/booking-text, with the same permissions as the tools.
The full contract, in OpenAPI
Download the OpenAPI 3.1 specification for complete schemas, security configuration and endpoints. It also includes the app's own backend endpoints, which require app sign-in and are not accessible with an integration key.
Download OpenAPI YAML