Travel Companions API and MCP server
One set of tools, two interfaces: a remote MCP server for AI assistants and a REST API described by OpenAPI 3.1. Both are generated from the same schemas, so they never drift apart.
Quickstart
Search without an account
curl "https://travel.coticsy.com/v1/trips?to.city=Lisbon&to.country=PT&startDate=2027-03-01&endDate=2027-03-10"
Call any tool
curl -X POST https://travel.coticsy.com/v1/tools/search_travelers \
-H 'content-type: application/json' \
-d '{"to":{"city":"Lisbon","country":"PT"},"languages":["pt"]}'
Connect an MCP client
Endpoint https://travel.coticsy.com/mcp (Streamable HTTP, stateless, JSON responses). Public tools work without auth; account tools prompt the user to sign in. Clients that only start OAuth when challenged at connect time should use https://travel.coticsy.com/mcp/auth.
{
"mcpServers": {
"travel-companions": {
"type": "http",
"url": "https://travel.coticsy.com/mcp"
}
}
}
Authentication
- OAuth 2.1 + PKCE with dynamic client registration. Metadata: /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource. Sign-in is passwordless (email link).
- API keys: send
X-API-Key: tc_live_…. Signed-in users create keys withPOST /v1/api-keys.
Trust ladder
- Public: search and browse masked trips and travelers.
- Account: profile, post trips, personal matches, webhooks.
- Terms + 18+ (
accept_terms): connection requests, relayed messages, reviews. - ID verified (
start_id_verification): verified badge and contact exchange.
When a call needs a higher level, the error says exactly what to do next:
{"error":{"code":"TRUST_REQUIRED","message":"This action requires trust level 2 (current 1).","nextStep":"Ask the user to confirm they are 18+ and agree to the Terms (link: https://travel.coticsy.com/terms), then call accept_terms and retry.","requiredTrustLevel":2}}
Tools
| Tool | Access | What it does |
|---|---|---|
search_trips | Public | Find travelers looking for companions. Works without an account. Filter by destination, origin, dates, needs (share_room, split_costs...), languages, sex, age range. Free text `q` matches plans and interests semantically. Results are ranked by compatibility and include masked traveler cards (first name + initial, age range, city). |
search_travelers | Public | Browse masked traveler cards (not trip posts). Filter by destination of their open trips, languages, age, nationality, interests, or free text. Useful when the user asks who is going somewhere rather than for a specific date window. |
get_trip | Public | Get the public view of one trip by id, including the masked traveler card. |
get_traveler_card | Public | Get the public, masked card of a traveler by their public id. |
list_popular_destinations | Public | Destinations with the most open trips right now. Useful to suggest where companions are easiest to find. |
list_needs_taxonomy | Public | Suggested `needs` tags. Any snake_case tag is accepted, but these are the common ones. |
get_my_status | Public | Tells the agent what the current user can do: trust level, plan, whether a profile exists, and the next steps to unlock more (create profile, accept terms, verify ID). |
create_or_update_profile | Account | Create the traveler profile on first call (all required fields) or update parts of it later. Birth date and postal code stay private; others see an age range and city only. |
get_my_profile | Account | Return the full profile of the authenticated user. |
post_trip | Account | Publish a trip to find companions. kind=solo (default) for a traveler seeking companions, kind=group to organize a group with N spots, kind=meetup when already at the destination. Requires a profile. |
update_trip | Account | Update fields of one of my trips. |
close_trip | Account | Close or cancel one of my trips so it no longer appears in search. |
list_my_trips | Account | List the authenticated user's trips, optionally filtered by status. |
get_my_matches | Account | Ranked compatible travelers for one of my trips, with reasons. Uses my profile preferences and the other side's preferences. |
create_group_trip | Account | Create a group trip with multiple open spots. Same fields as post_trip; kind is forced to group. |
meet_at_destination | Account | Post a meetup for someone already at (or about to arrive at) a destination. kind is forced to meetup. |
join_group_trip | Terms + 18+ | Request to join an open group trip. Requires terms acceptance. Owner must accept via respond_to_connection. |
accept_terms | Account | Record that the user is 18+ and accepts the Terms of Service. Required before connecting with other travelers. Send them {base}/terms first. |
request_connection | Terms + 18+ | Send a connection request to the owner of a trip. Both sides must accept before contact is shared. Requires accept_terms first. |
respond_to_connection | Terms + 18+ | Accept or decline an incoming connection request. |
list_connections | Terms + 18+ | List incoming and outgoing connection requests and accepted connections. |
send_message | Terms + 18+ | Send a relayed message on an accepted connection. Emails stay private. |
get_messages | Terms + 18+ | List relayed messages on an accepted connection. |
reveal_contact | ID verified | Reveal the counterpart's email (and phone if set) after both sides accept. Requires ID verification (trust level 3). |
leave_review | Terms + 18+ | Leave a 1-5 star review of a companion from an accepted connection, once the trip is over (from the day after its end date). One review per traveler per trip. |
subscribe_match_webhook | Account | Register an HTTPS URL that receives signed POSTs when new matches or connection events fire for this user. Agents use this to proactively notify travelers. The signing secret is returned once. |
report_user | Account | Report a traveler for spam, scam, harassment, fake profile or safety concerns. |
block_user | Account | Block a traveler. Neither side will see the other in search or be able to connect. |
export_my_data | Account | Return everything stored about the authenticated user (GDPR/CCPA export). |
delete_my_account | Account | Permanently delete the account, profile, trips and messages. Irreversible. The agent must confirm with the user first. |
start_id_verification | Terms + 18+ | Create a Stripe Identity verification session. Send the returned URL to the user. Unlocks the verified badge and contact reveal. |
start_premium_checkout | Account | Create a Stripe Checkout (Link-compatible) session for Premium. Send the returned URL to the user. |
get_subscription | Account | Return the current plan, status and usage limits. |
find_shared_stay | Terms + 18+ | Affiliate search for hotels/villas near the destination that two matched travelers can share. |
find_flights_together | Terms + 18+ | Affiliate flight search for matched travelers sharing an itinerary. |
find_travel_insurance | Account | Affiliate link for travel insurance suitable for companion trips. |
find_esim | Account | Affiliate link for destination eSIMs. |
Machine-readable: /v1/tools (with input schemas) · llms-full.txt
HTTP semantics
- Pagination: pass
cursorfromnextCursor. - Caching: anonymous GETs return
Cache-Control: publicand anETag; sendIf-None-Matchto get304. - Retries: send
Idempotency-Keyon writes. A successful response is replayed for 24 hours; the same key with a different body returns422. - Rate limits: 60/min per IP anonymous, 600/min per user, 3000/min premium.
429responses includeRetry-After.
Webhooks
subscribe_match_webhook registers an HTTPS URL for match.new, connection.requested, connection.accepted and message.received. The signing secret is returned once. Each POST carries X-TC-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + body); reject timestamps older than 5 minutes.
Discovery
- llms.txt · llms-full.txt
- MCP server card · A2A agent card (JSON-RPC at
/a2a) - API catalog (RFC 9727) · sitemap