triivl/ developers
API v1OpenAPI
TRIIVL DOCUMENTATION v1.0

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.

REST · BASE URLhttps://api.triivl.app/v1
MCP · STREAMABLE HTTPhttps://api.triivl.app/mcp
Your itinerary stays in the app.

iCloud 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.

01

From settings to your first request

  1. 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.

  2. 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.

  3. 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.

Terminal · your first API 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"
200 OK · example response
{
  "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.

02

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.

Available permissions
PermissionScopeAllows
Readtrips:readRead selected trips and trip items.
Createtrips:createCreate trips and trip items.
Updatetrips:updateUpdate selected trips and items.
Delete and restoretrips:deleteSoft delete and restore within 30 days.
Read booking referencestrips:bookingsRead booking and confirmation codes. Also requires read access.
Booking references have a separate permission.

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.

03

Trips

All REST requests below require Authorization: Bearer YOUR_API_KEY. {tripId} is the ID from the trip response.

GET/v1/tripstrips:read

List 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.

GET/v1/trips/{tripId}trips:read

Read the full trip, including ideas and deleted items. The response contains trip, revision, syncedAt and bookingReferencesIncluded. Items are in trip.items.

Read a selected trip
export TRIP_ID='ID_FROM_TRIP_LIST'

curl "https://api.triivl.app/v1/trips/$TRIP_ID" \
  --header "Authorization: Bearer $TRIIVL_API_KEY"
POST/v1/tripstrips:create

Create 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.

Create a trip · 202 Accepted
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\"
    }
  }"
PATCH/v1/trips/{tripId}trips:update

Include only the fields you want to change in trip. Read the trip first, then copy its latest revision to expectedRevision.

Update a title
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\" }
  }"
DELETE/v1/trips/{tripId}trips:delete

Move the trip to Recently Deleted. Send JSON with requestId and expectedRevision, without trip or item.

Delete a trip · latest revision required
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\"
  }"
POST/v1/trips/{tripId}/restoretrips:delete

Restore 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.

04

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.

POST/v1/trips/{tripId}/itemstrips:create

Add an item. type and title are required. requestId becomes the item's ID. expectedRevision is the trip's latest revision.

Add a hotel idea
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\"
    }
  }"
PATCH/v1/trips/{tripId}/items/{itemId}trips:update

Update 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.

Example JSON body · use your own IDs and latest revision
{
  "requestId": "c90d02d6-dcd4-479a-8cd3-d02c8df1dcf1",
  "expectedRevision": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "item": { "title": "Hotel Brosundet", "planningState": "confirmed" }
}
DELETE/v1/trips/{tripId}/items/{itemId}trips:delete

Delete the item, with requestId and the trip's expectedRevision in the JSON body. The item can be restored within 30 days.

POST/v1/trips/{tripId}/items/{itemId}/restoretrips:delete

Restore the item with a new requestId and the trip's latest expectedRevision. The parent trip must be active.

05

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.

1Send the change202 · pending
2The app checksapplying
3Saved in the appapplied
GET/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.

Check a change
export REQUEST_ID='ID_FROM_WRITE_RESPONSE'

curl "https://api.triivl.app/v1/trip-commands/$REQUEST_ID" \
  --header "Authorization: Bearer $TRIIVL_API_KEY"
Possible change statuses
StatusMeaning
pendingWaiting for the app. Keep it open.
applyingOne installation has claimed the change. Wait for the result.
appliedThe app has saved the change.
rejectedThe change was not saved. Read resultCode and update the trip/revision if needed.
cancelledA pending request has been withdrawn.
unconfirmedA 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.

Read the latest revision before changing a trip.

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.

06

Fields and formats

Trip · trip

Writable trip fields
FieldFormatDetails
titleTextRequired when creating, 1–500 characters.
kindpersonal / workDefault: personal.
startDay, endDayYYYY-MM-DD or nullOptional valid calendar dates. End must be on or after start.

Trip item · item

Writable trip item fields
FieldFormat / allowed values
typeRequired when creating: flight, train, bus, transit, ferry, privateCar, taxi, carRental, lodging, restaurant, attraction, stop, generic.
titleRequired when creating, 1–500 characters.
subtitle, city, origin, destination, flightNumberOptional text, up to 500 characters, or null.
planningStateidea (default) or confirmed.
startDate, endDateOptional 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, endTimeZoneIDOptional valid IANA time zones, e.g. Europe/Oslo.
allDayOptional boolean: true or false.
bookingReference, confirmationCodeOptional 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.

07

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.

MCP server URL
https://api.triivl.app/mcp
  1. 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.

  2. Add the server in your AI client

    Choose an external MCP server, enter the URL above and start the client's OAuth sign-in.

  3. 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_agenda now also returns derived per activity: minutes until start (startsInMinutes), trip day (“Day 2 of 3”), departsInMinutes and arrivesInMinutes for flights, and check-in/check-out context for lodging. conflicts warns about overlaps and tight connections. Existing fields are unchanged. Live flight status is not included.
  • search_items searches title, subtitle, city, origin, destination and flight number, with an optional date range, type and trip. Results are ranked and bounded by limit.
  • check_plan reviews 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_trip gives a short overview with days, cities in order and counts per type.
  • create_item and assign_item return hints next to the queued change: possibleDuplicates and, without a trip, suggestedTrips whose dates contain the item or are one day away. These are suggestions. Nothing is assigned or changed automatically. Without read access, hints is only checked: false.
  • parse_booking_text reads pasted confirmation text with Triivl’s existing rule-based import reader and proposes items shaped like create_item. It saves and queues nothing; the chat shows the proposals and calls create_item after you agree. Flights use the airports’ time zones; pass timeZone for other places.
TRY IT IN CHAT

“Show my trips and add a hotel idea in Ålesund to my November trip.”

Requires read and create access to the selected trip.
MCP tools and permissions
ToolScope
get_agenda, list_items, get_item, list_trips, get_trip, search_items, check_plan, summarize_triptrips:read
create_trip, create_item, parse_booking_texttrips:create
update_trip, update_item, assign_itemtrips:update
delete_trip, delete_item, restore_trip, restore_itemtrips:delete
get_change_statusThe 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.

08

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.

OAuth endpoints
MethodURLPurpose
GET/.well-known/oauth-protected-resource/mcpMCP resource and authorization server.
GET/.well-known/oauth-authorization-serverDiscovery: scopes, PKCE and endpoints.
POST/oauth/registerRegister a client name and redirect URIs.
GET/oauth/authorizeStart approval in the app.
POST/oauth/tokenExchange a one-time code or rotate a refresh token.
POST/oauth/revokeRevoke 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.

09

Errors and limits

HTTP errors and recommended handling
HTTPCommon causeWhat to do
400Invalid 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.
401Missing, expired, invalid or revoked token.Use a valid integration key or complete OAuth.
403Missing permission or trip access.Check settings and connection scopes.
404The trip has not synchronized, or the request does not exist for this connection.Keep the app open and check the ID.
409revision_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.
429Too many requests: 300 a minute per key or connection (rate_limited).Wait before retrying. Honor Retry-After when provided.
500Server error.Retry write requests with the same requestId and body.
Example REST error · 409 Conflict
{
  "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.

10

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