ax-check.com
by Gauge
S
Stardrift
stardrift.ai
ax-check.com
Checked 13 October 2026
B
79/100
Agents need your login
MCP setup docs are solid, but pricing is nowhere to be found
Docs give a working install command and auth requirements for the MCP server, but no pricing page exists for agents to check costs.
2 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no stardrift.ai account. Recorded 13 October 2026 on Pi.
3 recorded · 3m 26s
3m 26s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
Let me look at the repository and see what "Stardrift" is.
ls -la /sandbox/repo && find /sandbox/repo -maxdepth 2 -type f | head -50
total 16
total 16 drwxr-xr-x 3 root root 4096 Sep 18 08:00 . drwxr-xr-x 7 root root 4096 Oct 13 17:10 .. drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore /sandbox/repo/.gitignore /sandbox/repo/.git/COMMIT_EDITMSG /sandbox/repo/.git/config /sandbox/repo/.git/index /sandbox/repo/.git/HEAD /sandbox/repo/.git/description
Recorded tool call
{
"type": "tool_call",
"seq": 3,
"ts": "2026-10-13T17:11:18.643Z",
"name": "bash",
"turn": 1,
"input": {
"command": "ls -la /sandbox/repo && find /sandbox/repo -maxdepth 2 -type f | head -50"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-9cb092aa4d8f7c00",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 5,
"ts": "2026-10-13T17:11:20.080Z",
"turn": 1,
"output": {
"content": [
{
"text": "total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Oct 13 17:10 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n/sandbox/repo/.gitignore\n/sandbox/repo/.git/COMMIT_EDITMSG\n/sandbox/repo/.git/config\n/sandbox/repo/.git/index\n/sandbox/repo/.git/HEAD\n/sandbox/repo/.git/description\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-9cb092aa4d8f7c00",
"is_error": false,
"tool_kind": "shell"
}grep -ri "stardrift" /sandbox/repo --include=* -l 2>/dev/null | head -20
(no output)
(no output)
Recorded tool call
{
"type": "tool_call",
"seq": 4,
"ts": "2026-10-13T17:11:18.643Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -ri \"stardrift\" /sandbox/repo --include=* -l 2>/dev/null | head -20"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-b3a772944858895f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 6,
"ts": "2026-10-13T17:11:20.080Z",
"turn": 1,
"output": {
"content": [
{
"text": "(no output)",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-b3a772944858895f",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The repository is essentially empty (only .gitignore). "Stardrift" doesn't exist here. Let me check the git log and the contents of .gitignore. Maybe Stardrift is a hosted product and I should be looking at its documentation online, but I might not have internet access. Let me verify.
cd /sandbox/repo && cat .gitignore && git log --oneline | head
# Keep generated and vendored files out of source diffs.
# Keep generated and vendored files out of source diffs. # Logs / OS cruft *.log .DS_Store # JavaScript / TypeScript node_modules/ .next/ dist/ build/ coverage/ # Python .venv/ venv/ __pycache__/ *.py[cod] .pytest_cache/ *.egg-info/ # Rust target/ # Go / PHP / Ruby vendored deps vendor/ .bundle/ # Java / Kotlin / Scala .gradle/ # .NET bin/ obj/ # Elixir _build/ deps/ # Dart / Flutter .dart_tool/ 9ebf5fb Initial workspace
Recorded tool call
{
"type": "tool_call",
"seq": 9,
"ts": "2026-10-13T17:11:21.893Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && cat .gitignore && git log --oneline | head"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-a480efb496cd10ad",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 12,
"ts": "2026-10-13T17:11:21.893Z",
"turn": 1,
"output": {
"content": [
{
"text": "# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\n\n# Rust\ntarget/\n\n# Go / PHP / Ruby vendored deps\nvendor/\n.bundle/\n\n# Java / Kotlin / Scala\n.gradle/\n\n# .NET\nbin/\nobj/\n\n# Elixir\n_build/\ndeps/\n\n# Dart / Flutter\n.dart_tool/\n9ebf5fb Initial workspace\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-a480efb496cd10ad",
"is_error": false,
"tool_kind": "shell"
}which curl npm node python3 2>/dev/null; curl -sI --max-time 8 https://registry.npmjs.org 2>&1 | head -3
/usr/bin/curl
/usr/bin/curl /usr/local/bin/npm /usr/local/bin/node /usr/local/bin/python3 HTTP/2 200 date: Tue, 13 Oct 2026 17:11:23 GMT content-type: application/json
Recorded tool call
{
"type": "tool_call",
"seq": 10,
"ts": "2026-10-13T17:11:21.893Z",
"name": "bash",
"turn": 1,
"input": {
"command": "which curl npm node python3 2>/dev/null; curl -sI --max-time 8 https://registry.npmjs.org 2>&1 | head -3"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-a3bcb36ce26dc86c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 13,
"ts": "2026-10-13T17:11:23.313Z",
"turn": 1,
"output": {
"content": [
{
"text": "/usr/bin/curl\n/usr/local/bin/npm\n/usr/local/bin/node\n/usr/local/bin/python3\nHTTP/2 200 \r\ndate: Tue, 13 Oct 2026 17:11:23 GMT\r\ncontent-type: application/json\r\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-a3bcb36ce26dc86c",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The repository is empty. "Stardrift" — I don't know this product. Could be a fictional product. Let me try searching online — there's internet access. Search npm for the stardrift package.
curl -s --max-time 10 "https://registry.npmjs.org/-/v1/search?text=stardrift&size=10" | python3 -c "import json,sys; d=json.load(sys.stdin); [print(o['package']['name'],'-',o['package'].get('description','')) for o in d.get('objects',[])]"(no output)
(no output)
Recorded tool call
{
"type": "tool_call",
"seq": 16,
"ts": "2026-10-13T17:11:26.840Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 10 \"https://registry.npmjs.org/-/v1/search?text=stardrift&size=10\" | python3 -c \"import json,sys; d=json.load(sys.stdin); [print(o['package']['name'],'-',o['package'].get('description','')) for o in d.get('objects',[])]\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-bba2ee308ec2eda6",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 21,
"ts": "2026-10-13T17:11:27.247Z",
"turn": 1,
"output": {
"content": [
{
"text": "(no output)",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-bba2ee308ec2eda6",
"is_error": false,
"tool_kind": "shell"
}curl -s --max-time 10 "https://pypi.org/pypi/stardrift/json" | head -c 500; echo; curl -s --max-time 10 "https://duckduckgo.com/html/?q=stardrift+developer+API" | grep -oE 'result__a[^<]*>[^<]*' | head -10
{"message": "Not Found"}
{"message": "Not Found"}
Recorded tool call
{
"type": "tool_call",
"seq": 18,
"ts": "2026-10-13T17:11:26.840Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 10 \"https://pypi.org/pypi/stardrift/json\" | head -c 500; echo; curl -s --max-time 10 \"https://duckduckgo.com/html/?q=stardrift+developer+API\" | grep -oE 'result__a[^<]*>[^<]*' | head -10"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-abafc4652aba40c1",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 22,
"ts": "2026-10-13T17:11:27.640Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\"message\": \"Not Found\"}\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-abafc4652aba40c1",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
No results were found on npm (the empty output means either there were no objects, or the command silently failed). It doesn't exist on PyPI. Let me try searching once more with a different method — it might be a web search.
curl -s --max-time 10 "https://registry.npmjs.org/-/v1/search?text=stardrift" | head -c 800; echo "---"; curl -s --max-time 10 "https://duckduckgo.com/html/?q=%22stardrift%22+SDK+pricing" | grep -oE 'href="[^"]*"' | grep -v duckduckgo | head -15
{"objects":[],"total":0,"time":"2026-10-13T17:11:29.884Z"}---
{"objects":[],"total":0,"time":"2026-10-13T17:11:29.884Z"}---
Recorded tool call
{
"type": "tool_call",
"seq": 25,
"ts": "2026-10-13T17:11:29.913Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 10 \"https://registry.npmjs.org/-/v1/search?text=stardrift\" | head -c 800; echo \"---\"; curl -s --max-time 10 \"https://duckduckgo.com/html/?q=%22stardrift%22+SDK+pricing\" | grep -oE 'href=\"[^\"]*\"' | grep -v duckduckgo | head -15"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-8f2b255df34e9f5c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 28,
"ts": "2026-10-13T17:11:30.771Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\"objects\":[],\"total\":0,\"time\":\"2026-10-13T17:11:29.884Z\"}---\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-8f2b255df34e9f5c",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Zero npm search results, doesn't exist on PyPI, no web search results either (DDG's HTML probably blocked it). Let me try one more search approach before drawing a conclusion.
curl -s --max-time 10 "https://html.duckduckgo.com/html/?q=stardrift+api+documentation" -A "Mozilla/5.0" | grep -oE '<a[^>]*class="result__a"[^>]*>[^<]*' | head; echo "exit:$?"; curl -s --max-time 8 "https://stardrift.dev" -o /dev/null -w "%{http_code}\n"; curl -s --max-time 8 "https://stardrift.io" -o /dev/null -w "%{http_code}\n"<a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2F&rut=9965b7adbb01b6c375fc0f8f073bc45b795e59713f842ee59d
<a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2F&rut=9965b7adbb01b6c375fc0f8f073bc45b795e59713f842ee59d28341dc6d6c9c1">Connect Stardrift to your AI assistant - Stardrift <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2Ftools&rut=09b6880fc4ccc0744e3a3d582ce96c14ed7190e7706952d4f4d86967703b7fea">Tools - Stardrift <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2Fclients%2Fcodex&rut=c37ef7e288eed79d1b5b005666504c96659ed8a21f12dd5458ebe3827ca5a2f7">Connect Stardrift to Codex - Stardrift <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fstardrift.ai%2F&rut=ec760e5c0f65fc0ca673471a966fd5855f81aeae16e7f34b501f5695dc301d97">Stardrift — Free AI Travel Planner | Chat-Powered Itinerary Research <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2Fclients%2Fvscode&rut=bc1a430b467d34388736a100a9a693fc80eb215bf4edc4f8e8f1f739fdccce37">Connect Stardrift to VS Code - Stardrift <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fgithub.com%2Fjollyname%2FStellarModdingAPI&rut=c48b53438dd93cfd0e0bdfacbea7bab706fdb196be89b47f1e6e0518c1fedef4">A modding API for the game StellarDrive. - GitHub <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2Fclients%2Fcursor&rut=b2e3fff96a40e18d9783a52c4b3fe19681978339e40c253f9cb9dcf80d3ef6a0">Connect Stardrift to Cursor - Stardrift <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fgithub.com%2Flangscot%2Fstardrift&rut=b024a4e74c11811de0f073bf61ff4c004ea5a7d09454d6bfd8b918db67a6c9e5">GitHub - langscot/stardrift <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fstardrift.ai%2Finfo&rut=b8b98a663e4785a0dfa49ccd93f1dadee6ff33e011c7d5d0b9b13d4ca136d46d">Stardrift <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fjollyname.github.io%2FStellarModdingAPI%2DDocumentation%2Fparts%2F&rut=403fece7fc5b1ecdfa6366580d9d491dc5250109087dcd0c08440635f3dd3799">Overview - StellarModdingAPI Documentation exit:0 000 200
Recorded tool call
{
"type": "tool_call",
"seq": 31,
"ts": "2026-10-13T17:11:33.669Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 10 \"https://html.duckduckgo.com/html/?q=stardrift+api+documentation\" -A \"Mozilla/5.0\" | grep -oE '<a[^>]*class=\"result__a\"[^>]*>[^<]*' | head; echo \"exit:$?\"; curl -s --max-time 8 \"https://stardrift.dev\" -o /dev/null -w \"%{http_code}\\n\"; curl -s --max-time 8 \"https://stardrift.io\" -o /dev/null -w \"%{http_code}\\n\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-b4f2f9d8bd05b277",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 35,
"ts": "2026-10-13T17:11:35.315Z",
"turn": 1,
"output": {
"content": [
{
"text": "<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2F&rut=9965b7adbb01b6c375fc0f8f073bc45b795e59713f842ee59d28341dc6d6c9c1\">Connect Stardrift to your AI assistant - Stardrift\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2Ftools&rut=09b6880fc4ccc0744e3a3d582ce96c14ed7190e7706952d4f4d86967703b7fea\">Tools - Stardrift\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2Fclients%2Fcodex&rut=c37ef7e288eed79d1b5b005666504c96659ed8a21f12dd5458ebe3827ca5a2f7\">Connect Stardrift to Codex - Stardrift\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fstardrift.ai%2F&rut=ec760e5c0f65fc0ca673471a966fd5855f81aeae16e7f34b501f5695dc301d97\">Stardrift — Free AI Travel Planner | Chat-Powered Itinerary Research\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2Fclients%2Fvscode&rut=bc1a430b467d34388736a100a9a693fc80eb215bf4edc4f8e8f1f739fdccce37\">Connect Stardrift to VS Code - Stardrift\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fgithub.com%2Fjollyname%2FStellarModdingAPI&rut=c48b53438dd93cfd0e0bdfacbea7bab706fdb196be89b47f1e6e0518c1fedef4\">A modding API for the game StellarDrive. - GitHub\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fdocs.stardrift.ai%2Fclients%2Fcursor&rut=b2e3fff96a40e18d9783a52c4b3fe19681978339e40c253f9cb9dcf80d3ef6a0\">Connect Stardrift to Cursor - Stardrift\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fgithub.com%2Flangscot%2Fstardrift&rut=b024a4e74c11811de0f073bf61ff4c004ea5a7d09454d6bfd8b918db67a6c9e5\">GitHub - langscot/stardrift\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fstardrift.ai%2Finfo&rut=b8b98a663e4785a0dfa49ccd93f1dadee6ff33e011c7d5d0b9b13d4ca136d46d\">Stardrift\n<a rel=\"nofollow\" class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fjollyname.github.io%2FStellarModdingAPI%2DDocumentation%2Fparts%2F&rut=403fece7fc5b1ecdfa6366580d9d491dc5250109087dcd0c08440635f3dd3799\">Overview - StellarModdingAPI Documentation\nexit:0\n000\n200\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-b4f2f9d8bd05b277",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Stardrift is stardrift.ai — a free AI travel planner that has an MCP-like connection to AI assistants (docs.stardrift.ai). Let me try to fetch the docs.
Found it — Stardrift is stardrift.ai, an AI travel planner with an assistant-integration workflow. Let me read its docs.
curl -s --max-time 15 "https://docs.stardrift.ai/" | python3 -c " import sys,re,html t=sys.stdin.read() t=re.sub(r'<script.*?</script>','',t,flags=re.S) t=re.sub(r'<style.*?</style>','',t,flags=re.S) t=re.sub(r'<[^>]+>',' ',t) t=html.unescape(t) t=re.sub(r'\s+',' ',t) print(t[:4000])"
Connect Stardrift to your AI assistant - Stardrift Documentation Index Fetch the complete documentation index at: /llms.txt Use this file to discover all avail
Connect Stardrift to your AI assistant - Stardrift Documentation Index Fetch the complete documentation index at: /llms.txt Use this file to discover all available pages before exploring further. Skip to main content Stardrift home page Search... ⌘ K Stardrift Search... Navigation Get started Connect Stardrift to your AI assistant Documentation API reference Get started Connect Stardrift to your AI assistant Tools Connect a client Connect Stardrift to Claude Connect Stardrift to ChatGPT Connect Stardrift to Cursor Connect Stardrift to VS Code Connect Stardrift to Codex Connect Stardrift to Gemini CLI Connect any MCP client On this page Connect What you are granting Try it Sharing Bookings Daily lookup budget Disconnect Get started Connect Stardrift to your AI assistant Let an AI assistant read and edit your Stardrift trips, itineraries, and saved places over MCP. Stardrift’s public MCP endpoint is https://stardrift.ai/mcp . Connect it to let an assistant search flights and hotels, save unbooked options, read your travel plans, build and correct itineraries, record stays and events, and organize saved places on your behalf. Connect Claude In the connector directory: claude.ai, Desktop, mobile and Claude Code ChatGPT Custom connector with developer mode Cursor One-click install or mcp.json VS Code GitHub Copilot agent mode Codex OpenAI Codex CLI and IDE extension Gemini CLI Google’s Gemini CLI Any MCP client Streamable HTTP with OAuth Use your Stardrift account and review the permissions before approving. To change accounts, change the account on the website opened by the authorization flow and reconnect the client; signing into a different website or development environment does not change an existing grant. What you are granting Scope What it allows guides:read Read your trips and itineraries guides:write Create trips; add, edit, move, and remove entries; record stays/events; change trip visibility bookings:read Read bookings and obtain your private booking-import address saved:read Read your lists and their contents; search place suggestions saved:write Create and publish lists; edit captions/sections; add and remove saved places The guides:* scope names cover trips and itineraries. Access to private records is limited to the authenticated account; knowing another user’s trip ID does not grant access. Try it “Create a two-day São Paulo trip and add MASP on day one at 10:00.” “Move that visit to day two, keeping its time.” “Create a private saved list, then add a Museums heading.” “Save the places from this TikTok: https://www.tiktok.com/@…/video/…” In a new conversation: “Find that list and change MASP’s caption.” See Tools for the complete catalog, editing IDs, pagination, and explicit-null behavior. Sharing Trips start as drafts and saved lists made with create_saved_list start private; lists imported from an Instagram or TikTok post start public, as they do in the app. Ask explicitly to publish: set_trip_visibility controls a trip; update_saved_list controls a saved list. Public content can be shared and indexed. A returned URL does not itself indicate public visibility. Bookings The assistant can read imported bookings and record stays or events. These tools do not purchase or cancel supplier reservations. There is no direct flight-add tool: forward a confirmation email to the address returned by get_forwarding_address . Keep that address private because mail sent there can add bookings to your account. Daily lookup budget Paid place lookups have a daily cap per user , shared across the user’s API tokens and connected clients. It resets at midnight UTC. A 429 response explains when to retry. Cached lookups do not add provider cost. Moving an existing entry does not resolve the place again; reads and itinerary corrections are separate from place discovery. Disconnect On Stardrift, open Profile → Connected apps → Disconnect . The server rechecks grants with a cache of up to 60 seconds, so access may persist b
Recorded tool call
{
"type": "tool_call",
"seq": 39,
"ts": "2026-10-13T17:11:38.552Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 15 \"https://docs.stardrift.ai/\" | python3 -c \"\nimport sys,re,html\nt=sys.stdin.read()\nt=re.sub(r'<script.*?</script>','',t,flags=re.S)\nt=re.sub(r'<style.*?</style>','',t,flags=re.S)\nt=re.sub(r'<[^>]+>',' ',t)\nt=html.unescape(t)\nt=re.sub(r'\\s+',' ',t)\nprint(t[:4000])\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-8e71bc06827e7613",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 41,
"ts": "2026-10-13T17:11:40.759Z",
"turn": 1,
"output": {
"content": [
{
"text": " Connect Stardrift to your AI assistant - Stardrift Documentation Index Fetch the complete documentation index at: /llms.txt Use this file to discover all available pages before exploring further. Skip to main content Stardrift home page Search... ⌘ K Stardrift Search... Navigation Get started Connect Stardrift to your AI assistant Documentation API reference Get started Connect Stardrift to your AI assistant Tools Connect a client Connect Stardrift to Claude Connect Stardrift to ChatGPT Connect Stardrift to Cursor Connect Stardrift to VS Code Connect Stardrift to Codex Connect Stardrift to Gemini CLI Connect any MCP client On this page Connect What you are granting Try it Sharing Bookings Daily lookup budget Disconnect Get started Connect Stardrift to your AI assistant Let an AI assistant read and edit your Stardrift trips, itineraries, and saved places over MCP. Stardrift’s public MCP endpoint is https://stardrift.ai/mcp . Connect it to let an assistant search flights and hotels, save unbooked options, read your travel plans, build and correct itineraries, record stays and events, and organize saved places on your behalf. Connect Claude In the connector directory: claude.ai, Desktop, mobile and Claude Code ChatGPT Custom connector with developer mode Cursor One-click install or mcp.json VS Code GitHub Copilot agent mode Codex OpenAI Codex CLI and IDE extension Gemini CLI Google’s Gemini CLI Any MCP client Streamable HTTP with OAuth Use your Stardrift account and review the permissions before approving. To change accounts, change the account on the website opened by the authorization flow and reconnect the client; signing into a different website or development environment does not change an existing grant. What you are granting Scope What it allows guides:read Read your trips and itineraries guides:write Create trips; add, edit, move, and remove entries; record stays/events; change trip visibility bookings:read Read bookings and obtain your private booking-import address saved:read Read your lists and their contents; search place suggestions saved:write Create and publish lists; edit captions/sections; add and remove saved places The guides:* scope names cover trips and itineraries. Access to private records is limited to the authenticated account; knowing another user’s trip ID does not grant access. Try it “Create a two-day São Paulo trip and add MASP on day one at 10:00.” “Move that visit to day two, keeping its time.” “Create a private saved list, then add a Museums heading.” “Save the places from this TikTok: https://www.tiktok.com/@…/video/…” In a new conversation: “Find that list and change MASP’s caption.” See Tools for the complete catalog, editing IDs, pagination, and explicit-null behavior. Sharing Trips start as drafts and saved lists made with create_saved_list start private; lists imported from an Instagram or TikTok post start public, as they do in the app. Ask explicitly to publish: set_trip_visibility controls a trip; update_saved_list controls a saved list. Public content can be shared and indexed. A returned URL does not itself indicate public visibility. Bookings The assistant can read imported bookings and record stays or events. These tools do not purchase or cancel supplier reservations. There is no direct flight-add tool: forward a confirmation email to the address returned by get_forwarding_address . Keep that address private because mail sent there can add bookings to your account. Daily lookup budget Paid place lookups have a daily cap per user , shared across the user’s API tokens and connected clients. It resets at midnight UTC. A 429 response explains when to retry. Cached lookups do not add provider cost. Moving an existing entry does not resolve the place again; reads and itinerary corrections are separate from place discovery. Disconnect On Stardrift, open Profile → Connected apps → Disconnect . The server rechecks grants with a cache of up to 60 seconds, so access may persist b\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-8e71bc06827e7613",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Stardrift is an MCP server for travel planning. The documentation mentions /llms.txt. The user's request is: create a simple example using Stardrift, explain how pricing works, and state whether this product is easy to manage. Key constraints: use the hosted product via SDK or API, don't start a local service stack, don't execute long-running commands. If there's no actionable developer workflow, stop with a clear explanation.
Let me fetch llms.txt and the pricing/API reference documentation. There might be an API token workflow. I'll check the "Documentation" and "API reference" sections.
curl -s --max-time 15 "https://docs.stardrift.ai/llms.txt" | head -80
# Stardrift
# Stardrift > An MCP server for reading and editing your trips, itineraries, recorded bookings, and saved places. MCP endpoint: https://stardrift.ai/mcp (Streamable HTTP with browser OAuth) Scopes: guides:read, guides:write, bookings:read, saved:read, saved:write ## Docs - [Connect Stardrift to your AI assistant](https://docs.stardrift.ai/index.md): setup, permissions, sharing, and disconnecting - [Tools](https://docs.stardrift.ai/tools.md): public tool catalog, editing IDs, pagination, null clearing, and errors - [API reference](https://docs.stardrift.ai/api-reference/mcp-server/list-trips): generated HTTP operations available with public OAuth scopes ## Connect a client - [Claude](https://docs.stardrift.ai/clients/claude.md): claude.ai and Claude Desktop custom connector, Claude Code - [ChatGPT](https://docs.stardrift.ai/clients/chatgpt.md): custom connector with developer mode - [Cursor](https://docs.stardrift.ai/clients/cursor.md): install link and mcp.json - [VS Code](https://docs.stardrift.ai/clients/vscode.md): install link, MCP: Add Server, .vscode/mcp.json - [Codex](https://docs.stardrift.ai/clients/codex.md): codex mcp add and login - [Gemini CLI](https://docs.stardrift.ai/clients/gemini-cli.md): gemini mcp add - [Any MCP client](https://docs.stardrift.ai/clients/other.md): Streamable HTTP, OAuth discovery, scopes ## Working with travel plans - Use list_trips, then get_itinerary. Edit/move/remove a visit using entry_id, not item_id. - move_entry preserves identity, time, and note without resolving the place again. - update_entry preserves omitted fields; explicit null clears note/time. Use content to rewrite standalone notes. - Use list_saved_lists, then get_saved_list. Follow next_offset until null. Use each saved item's id to edit it. - Created saved lists start private and trips start draft; lists imported from an Instagram or TikTok post start public, as in the app. Publish only on explicit request using update_saved_list or set_trip_visibility. - record_stay, update_stay, remove_stay and record_event manage Stardrift records, not supplier purchases or cancellations. - There is no manual confirmed-flight recording tool or event update/removal tool. Flights can be imported by forwarding confirmation emails. - The booking forwarding address is a private write-capable address; do not expose it. - A 409 place-name conflict means the resolved place was not added; it is not a duplicate-success response. - Paid lookups have a per-user daily cap, resetting at midnight UTC. Reads and moves do not resolve a new place. - Private records are owner-scoped. Disconnect grants through Profile → Connected apps; revocation checks can take up to 60 seconds. ## Search and plan - search_flights returns one-way, round-trip, and multi-city cash options; search_hotels returns LiteAPI availability and room rates. No ranking subagents. - get_search_results pages through owner-scoped results. References last 24 hours; rates are observations, not held inventory. - add_search_result_to_itinerary saves an exact result as unbooked. Read journeys/stays with get_itinerary; remove with remove_search_result_from_itinerary. - Users must visit the Stardrift website to book hotels. Give them the trip_url from saving their selection.
Recorded tool call
{
"type": "tool_call",
"seq": 44,
"ts": "2026-10-13T17:11:42.798Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 15 \"https://docs.stardrift.ai/llms.txt\" | head -80"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-9d39b0b462c28ace",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 46,
"ts": "2026-10-13T17:11:43.870Z",
"turn": 1,
"output": {
"content": [
{
"text": "# Stardrift\n\n> An MCP server for reading and editing your trips, itineraries, recorded bookings, and saved places.\n\nMCP endpoint: https://stardrift.ai/mcp (Streamable HTTP with browser OAuth)\nScopes: guides:read, guides:write, bookings:read, saved:read, saved:write\n\n## Docs\n\n- [Connect Stardrift to your AI assistant](https://docs.stardrift.ai/index.md): setup, permissions, sharing, and disconnecting\n- [Tools](https://docs.stardrift.ai/tools.md): public tool catalog, editing IDs, pagination, null clearing, and errors\n- [API reference](https://docs.stardrift.ai/api-reference/mcp-server/list-trips): generated HTTP operations available with public OAuth scopes\n\n## Connect a client\n\n- [Claude](https://docs.stardrift.ai/clients/claude.md): claude.ai and Claude Desktop custom connector, Claude Code\n- [ChatGPT](https://docs.stardrift.ai/clients/chatgpt.md): custom connector with developer mode\n- [Cursor](https://docs.stardrift.ai/clients/cursor.md): install link and mcp.json\n- [VS Code](https://docs.stardrift.ai/clients/vscode.md): install link, MCP: Add Server, .vscode/mcp.json\n- [Codex](https://docs.stardrift.ai/clients/codex.md): codex mcp add and login\n- [Gemini CLI](https://docs.stardrift.ai/clients/gemini-cli.md): gemini mcp add\n- [Any MCP client](https://docs.stardrift.ai/clients/other.md): Streamable HTTP, OAuth discovery, scopes\n\n## Working with travel plans\n\n- Use list_trips, then get_itinerary. Edit/move/remove a visit using entry_id, not item_id.\n- move_entry preserves identity, time, and note without resolving the place again.\n- update_entry preserves omitted fields; explicit null clears note/time. Use content to rewrite standalone notes.\n- Use list_saved_lists, then get_saved_list. Follow next_offset until null. Use each saved item's id to edit it.\n- Created saved lists start private and trips start draft; lists imported from an Instagram or TikTok post start public, as in the app. Publish only on explicit request using update_saved_list or set_trip_visibility.\n- record_stay, update_stay, remove_stay and record_event manage Stardrift records, not supplier purchases or cancellations.\n- There is no manual confirmed-flight recording tool or event update/removal tool. Flights can be imported by forwarding confirmation emails.\n- The booking forwarding address is a private write-capable address; do not expose it.\n- A 409 place-name conflict means the resolved place was not added; it is not a duplicate-success response.\n- Paid lookups have a per-user daily cap, resetting at midnight UTC. Reads and moves do not resolve a new place.\n- Private records are owner-scoped. Disconnect grants through Profile → Connected apps; revocation checks can take up to 60 seconds.\n\n## Search and plan\n\n- search_flights returns one-way, round-trip, and multi-city cash options; search_hotels returns LiteAPI availability and room rates. No ranking subagents.\n- get_search_results pages through owner-scoped results. References last 24 hours; rates are observations, not held inventory.\n- add_search_result_to_itinerary saves an exact result as unbooked. Read journeys/stays with get_itinerary; remove with remove_search_result_from_itinerary.\n- Users must visit the Stardrift website to book hotels. Give them the trip_url from saving their selection.\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-9d39b0b462c28ace",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Stardrift is an MCP server hosted at https://stardrift.ai/mcp, using browser OAuth. To build an example, authentication is required. Is there an API token workflow that works without a browser? The docs said "shared among the user's API token and connected clients" — meaning API tokens do exist. Let me check the tools.md and API reference for authentication details. There might be an API token page. Let me check the /api-reference and /clients/other.md pages.
curl -s --max-time 15 "https://docs.stardrift.ai/clients/other.md" | head -120
> ## Documentation Index
> ## Documentation Index > Fetch the complete documentation index at: https://docs.stardrift.ai/llms.txt > Use this file to discover all available pages before exploring further. # Connect any MCP client > What a Model Context Protocol client needs to talk to Stardrift: Streamable HTTP, OAuth with dynamic client registration, and the scopes to request. Any client that speaks MCP over Streamable HTTP and can complete an OAuth sign-in works with Stardrift. | | | | - | - | | Endpoint | `https://stardrift.ai/mcp` | | Transport | Streamable HTTP | | Authentication | OAuth 2.1 with PKCE; dynamic client registration and client ID metadata documents are both supported | | Resource metadata | `https://stardrift.ai/.well-known/oauth-protected-resource` | | Authorization server | `https://stardrift.ai/api/oauth` | | Scopes | `guides:read guides:write bookings:read saved:read saved:write` | An unauthenticated request returns `401` with a `WWW-Authenticate` header that names the resource metadata URL and the scopes to request. Clients that follow the MCP authorization specification discover everything from there and open the browser for sign-in. Tokens go in the `Authorization: Bearer` header. Clients that cannot run an OAuth flow are not supported yet. See [what you are granting](/) for what each scope allows and [Tools](/tools) for the catalog. This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.
Recorded tool call
{
"type": "tool_call",
"seq": 49,
"ts": "2026-10-13T17:11:46.959Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 15 \"https://docs.stardrift.ai/clients/other.md\" | head -120"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-975f25359bf74561",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 54,
"ts": "2026-10-13T17:11:47.404Z",
"turn": 1,
"output": {
"content": [
{
"text": "> ## Documentation Index\n> Fetch the complete documentation index at: https://docs.stardrift.ai/llms.txt\n> Use this file to discover all available pages before exploring further.\n\n# Connect any MCP client\n\n> What a Model Context Protocol client needs to talk to Stardrift: Streamable HTTP, OAuth with dynamic client registration, and the scopes to request.\n\nAny client that speaks MCP over Streamable HTTP and can complete an OAuth sign-in works with Stardrift.\n\n| | |\n| - | - |\n| Endpoint | `https://stardrift.ai/mcp` |\n| Transport | Streamable HTTP |\n| Authentication | OAuth 2.1 with PKCE; dynamic client registration and client ID metadata documents are both supported |\n| Resource metadata | `https://stardrift.ai/.well-known/oauth-protected-resource` |\n| Authorization server | `https://stardrift.ai/api/oauth` |\n| Scopes | `guides:read guides:write bookings:read saved:read saved:write` |\n\nAn unauthenticated request returns `401` with a `WWW-Authenticate` header that names the resource metadata URL and the scopes to request. Clients that follow the MCP authorization specification discover everything from there and open the browser for sign-in. Tokens go in the `Authorization: Bearer` header.\n\nClients that cannot run an OAuth flow are not supported yet.\n\nSee [what you are granting](/) for what each scope allows and [Tools](/tools) for the catalog.\n\n\nThis documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-975f25359bf74561",
"is_error": false,
"tool_kind": "shell"
}curl -s --max-time 15 "https://docs.stardrift.ai/api-reference/mcp-server/list-trips.md" 2>/dev/null | head -100; echo ===; curl -s --max-time 15 "https://docs.stardrift.ai/llms-full.txt" -o /tmp/llms-full.txt -w "%{http_code} %{size_download}\n"> ## Documentation Index
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stardrift.ai/llms.txt
> Use this file to discover all available pages before exploring further.
# List Trips
## OpenAPI
````yaml /openapi.json get /v1/trips
openapi: 3.1.0
info:
title: Stardrift API
description: >-
Read and edit the authenticated user's trips, itineraries, recorded
bookings, and saved lists. Private records are owner-scoped. MCP tool names
are documented separately from these HTTP operations.
version: v1
servers:
- url: https://stardrift.ai/api
security: []
paths:
/v1/trips:
get:
tags:
- mcp-server
summary: List Trips
operationId: list_trips_v1_trips_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
items:
$ref: '#/components/schemas/TripSummary'
type: array
title: Response List Trips V1 Trips Get
components:
schemas:
TripSummary:
properties:
id:
type: string
format: uuid
title: Id
title:
anyOf:
- type: string
- type: 'null'
title: Title
slug:
anyOf:
- type: string
- type: 'null'
title: Slug
visibility:
type: string
title: Visibility
is_published:
type: boolean
title: Is Published
start_date:
anyOf:
- type: string
format: date
- type: 'null'
title: Start Date
end_date:
anyOf:
- type: string
format: date
- type: 'null'
title: End Date
updated_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Updated At
type: object
required:
- id
- title
- slug
- visibility
- is_published
title: TripSummary
````
This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.===
200 31527
Recorded tool call
{
"type": "tool_call",
"seq": 51,
"ts": "2026-10-13T17:11:46.959Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 15 \"https://docs.stardrift.ai/api-reference/mcp-server/list-trips.md\" 2>/dev/null | head -100; echo ===; curl -s --max-time 15 \"https://docs.stardrift.ai/llms-full.txt\" -o /tmp/llms-full.txt -w \"%{http_code} %{size_download}\\n\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-b0b6b311f2b6ffd4",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 55,
"ts": "2026-10-13T17:11:47.802Z",
"turn": 1,
"output": {
"content": [
{
"text": "> ## Documentation Index\n> Fetch the complete documentation index at: https://docs.stardrift.ai/llms.txt\n> Use this file to discover all available pages before exploring further.\n\n# List Trips\n\n\n\n## OpenAPI\n\n````yaml /openapi.json get /v1/trips\nopenapi: 3.1.0\ninfo:\n title: Stardrift API\n description: >-\n Read and edit the authenticated user's trips, itineraries, recorded\n bookings, and saved lists. Private records are owner-scoped. MCP tool names\n are documented separately from these HTTP operations.\n version: v1\nservers:\n - url: https://stardrift.ai/api\nsecurity: []\npaths:\n /v1/trips:\n get:\n tags:\n - mcp-server\n summary: List Trips\n operationId: list_trips_v1_trips_get\n responses:\n '200':\n description: Successful Response\n content:\n application/json:\n schema:\n items:\n $ref: '#/components/schemas/TripSummary'\n type: array\n title: Response List Trips V1 Trips Get\ncomponents:\n schemas:\n TripSummary:\n properties:\n id:\n type: string\n format: uuid\n title: Id\n title:\n anyOf:\n - type: string\n - type: 'null'\n title: Title\n slug:\n anyOf:\n - type: string\n - type: 'null'\n title: Slug\n visibility:\n type: string\n title: Visibility\n is_published:\n type: boolean\n title: Is Published\n start_date:\n anyOf:\n - type: string\n format: date\n - type: 'null'\n title: Start Date\n end_date:\n anyOf:\n - type: string\n format: date\n - type: 'null'\n title: End Date\n updated_at:\n anyOf:\n - type: string\n format: date-time\n - type: 'null'\n title: Updated At\n type: object\n required:\n - id\n - title\n - slug\n - visibility\n - is_published\n title: TripSummary\n\n````\n\nThis documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.===\n200 31527\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-b0b6b311f2b6ffd4",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
In other words, Stardrift = hosted MCP server + REST API (https://stardrift.ai/api/v1), authentication is OAuth 2.1 with PKCE — browser sign-in is mandatory. "Clients that can't run the OAuth flow are not yet supported."
Also, the docs state "API tokens" for the daily lookup budget. Let me check llms-full.txt to see if there's any mention of API tokens or pricing. Pricing: "Paid place lookups have a daily per-user cap… Cached lookups don't add provider cost." Is there a pricing page? Let me try grepping llms-full.
grep -in "pric\|cost\|free\|plan\|token\|budget\|429\|paid" /tmp/llms-full.txt | head -40
58:plan. Returns the trip summary, including the new trip_id.
58:plan. Returns the trip summary, including the new trip_id.
68:Session-or-token so the web/mobile connections screens and the MCP share
70:token is the user's own credential, not booking data.
74:# Get Plan
75:Source: https://docs.stardrift.ai/api-reference/mcp-server/get-plan
77:/openapi.json get /v1/trips/{trip_id}/plan
95:route takes only its `saved:import` token, which OAuth clients never hold.
138:The removal is `apply_plan_actions` + `RemoveEntryAction` — the same pair
139:the LLM edit path uses (`plan/edit_subagent.py`, `stream_processing.py`),
142:`lib/plan-mutations.ts` and PUTs the whole plan. What the two share is
143:`EditablePlan.save`, which mirrors that PUT's validate-then-save.
146:`PlanRepository.assemble_content` reconstitutes as `[]` on every load.
193:reorders the entry because a plan is an ordered document.
201:Change a booking in place — dates, price, or its confirmation number.
218:spine with zero paid fetches, and dedup by canonical place_id.
306: Developer mode is available on Plus, Pro, Business, Enterprise and Edu plans, in the ChatGPT web app.
318: * **Description**: `Plan trips, read bookings and organize saved places in my Stardrift account.`
496:An unauthenticated request returns `401` with a `WWW-Authenticate` header that names the resource metadata URL and the scopes to request. Clients that follow the MCP authorization specification discover everything from there and open the browser for sign-in. Tokens go in the `Authorization: Bearer` header.
541:Stardrift's public MCP endpoint is `https://stardrift.ai/mcp`. Connect it to let an assistant search flights and hotels, save unbooked options, read your travel plans, build and correct itineraries, record stays and events, and organize saved places on your behalf.
587:## Daily lookup budget
589:Paid place lookups have a **daily cap per user**, shared across the user's API tokens and connected clients. It resets at midnight UTC. A `429` response explains when to retry. Cached lookups do not add provider cost. Moving an existing entry does not resolve the place again; reads and itinerary corrections are separate from place discovery.
599:The public Stardrift MCP tools, their permissions, and how to read and correct your travel plans.
609:| `get_itinerary` | `guides:read` | Read days, entries, resolved activities, and `plan_version`. |
617:Day numbers start at 1 and must already exist. Use `get_itinerary` to inspect them. Use `entry_id` to edit, move, or remove a visit; `item_id` identifies its underlying activity. `plan_version` reports the current revision; it is not an optimistic-lock parameter.
649:| `search_flights` | `guides:read` | Search one-way, round-trip, or multi-city cash flights. Returns connecting segments and observed prices without a ranking subagent. |
656:references belong to your account and last 24 hours; prices may change sooner.
662:to get every booking site's link and price. Places carry their rating, review count and one `photo_url`; review texts, photo galleries and map outlines are left out. Saved items remain in the trip
666:Hotel prices distinguish nightly amounts from whole-stay totals. Supply child
679:| `update_stay` | `guides:write` | Correct a recorded stay's dates, price, confirmation number, or other supported fields. |
722:| `429` | The daily paid-lookup budget is exhausted | Stop new paid lookups until the indicated reset. Reading or moving an existing entry does not resolve a place. |
726:The tools preserve API error explanations. The wrapper can also return input errors such as `Pass search_query or place_id`, or `No venue matched`, without an HTTP status.
Recorded tool call
{
"type": "tool_call",
"seq": 58,
"ts": "2026-10-13T17:11:52.021Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -in \"pric\\|cost\\|free\\|plan\\|token\\|budget\\|429\\|paid\" /tmp/llms-full.txt | head -40"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-84157f61af6eb655",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 61,
"ts": "2026-10-13T17:11:52.021Z",
"turn": 1,
"output": {
"content": [
{
"text": "58:plan. Returns the trip summary, including the new trip_id.\n68:Session-or-token so the web/mobile connections screens and the MCP share\n70:token is the user's own credential, not booking data.\n74:# Get Plan\n75:Source: https://docs.stardrift.ai/api-reference/mcp-server/get-plan\n77:/openapi.json get /v1/trips/{trip_id}/plan\n95:route takes only its `saved:import` token, which OAuth clients never hold.\n138:The removal is `apply_plan_actions` + `RemoveEntryAction` — the same pair\n139:the LLM edit path uses (`plan/edit_subagent.py`, `stream_processing.py`),\n142:`lib/plan-mutations.ts` and PUTs the whole plan. What the two share is\n143:`EditablePlan.save`, which mirrors that PUT's validate-then-save.\n146:`PlanRepository.assemble_content` reconstitutes as `[]` on every load.\n193:reorders the entry because a plan is an ordered document.\n201:Change a booking in place — dates, price, or its confirmation number.\n218:spine with zero paid fetches, and dedup by canonical place_id.\n306: Developer mode is available on Plus, Pro, Business, Enterprise and Edu plans, in the ChatGPT web app.\n318: * **Description**: `Plan trips, read bookings and organize saved places in my Stardrift account.`\n496:An unauthenticated request returns `401` with a `WWW-Authenticate` header that names the resource metadata URL and the scopes to request. Clients that follow the MCP authorization specification discover everything from there and open the browser for sign-in. Tokens go in the `Authorization: Bearer` header.\n541:Stardrift's public MCP endpoint is `https://stardrift.ai/mcp`. Connect it to let an assistant search flights and hotels, save unbooked options, read your travel plans, build and correct itineraries, record stays and events, and organize saved places on your behalf.\n587:## Daily lookup budget\n589:Paid place lookups have a **daily cap per user**, shared across the user's API tokens and connected clients. It resets at midnight UTC. A `429` response explains when to retry. Cached lookups do not add provider cost. Moving an existing entry does not resolve the place again; reads and itinerary corrections are separate from place discovery.\n599:The public Stardrift MCP tools, their permissions, and how to read and correct your travel plans.\n609:| `get_itinerary` | `guides:read` | Read days, entries, resolved activities, and `plan_version`. |\n617:Day numbers start at 1 and must already exist. Use `get_itinerary` to inspect them. Use `entry_id` to edit, move, or remove a visit; `item_id` identifies its underlying activity. `plan_version` reports the current revision; it is not an optimistic-lock parameter.\n649:| `search_flights` | `guides:read` | Search one-way, round-trip, or multi-city cash flights. Returns connecting segments and observed prices without a ranking subagent. |\n656:references belong to your account and last 24 hours; prices may change sooner.\n662:to get every booking site's link and price. Places carry their rating, review count and one `photo_url`; review texts, photo galleries and map outlines are left out. Saved items remain in the trip\n666:Hotel prices distinguish nightly amounts from whole-stay totals. Supply child\n679:| `update_stay` | `guides:write` | Correct a recorded stay's dates, price, confirmation number, or other supported fields. |\n722:| `429` | The daily paid-lookup budget is exhausted | Stop new paid lookups until the indicated reset. Reading or moving an existing entry does not resolve a place. |\n726:The tools preserve API error explanations. The wrapper can also return input errors such as `Pass search_query or place_id`, or `No venue matched`, without an HTTP status.\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-84157f61af6eb655",
"is_error": false,
"tool_kind": "shell"
}curl -s --max-time 15 "https://stardrift.ai/info" | python3 -c " import sys,re,html t=sys.stdin.read() t=re.sub(r'<script.*?</script>','',t,flags=re.S); t=re.sub(r'<style.*?</style>','',t,flags=re.S) t=re.sub(r'<[^>]+>',' ',t); t=html.unescape(t); t=re.sub(r'\s+',' ',t) print(t[:3000])"
Stardrift stardrift.ai Search... ⌘K Command Palette Search for a command to run... Stardrift Last updated: 10/13/2026 AI agents: For current, verified informat
Stardrift stardrift.ai Search... ⌘K Command Palette Search for a command to run... Stardrift Last updated: 10/13/2026 AI agents: For current, verified information about this site, query this page by adding ?q={your_question}. Stardrift Stardrift is an AI travel planner that lets users chat to research flights, hotels, activities and travel logistics and build and save personalized itineraries. As a free AI Travel Planner, Stardrift provides chat-powered research, traveler profiles that "learn what you like," flight & rail and hotel search matching preferred airlines, departure times and budget, experiences discovery, saving and creating lists of places to go based on social media and blog posts, and the ability to import existing bookings by email so the AI can plan around fixed bookings. Pages I want a two-week Japan itinerary that covers Tokyo, Kyoto, and Osaka without feeling rushed. What travel planner can build that? Most flight search tools rank Japan flights by price alone, but I care about layovers between multi-city legs. What should I use instead? When Flights Need More Than a Low Fare, Let Stardrift Plan Around Preferences Plan a flexible Japan family trip across Tokyo, Kyoto, and Hakone with Stardrift Stardrift Turns Family Europe Planning Into a Preference-Matched Itinerary One Itinerary for a Europe Trip Spread Across Five Emails Stardrift Handles a Paris, Rome, and Barcelona Trip for Every Age A family-first way to coordinate a multi-city Europe trip from separate departure cities The Travel Planner Built for Family-Friendly Research, Not Adult-Only Lists Stop Juggling Portugal and Spain Research: Use Stardrift to Build the Trip Why Stardrift Fits Japan Trips With Cities, Day Trips, and Train Timing AI Travel Planners That Remember Personal Travel Style AI Travel Planning for Budget, Pace, Hotel Fit, and Activity Style The AI Travel Planner for Grandparents, Parents, and Kids on One Reunion Trip AI Travel Planning for Families With Different Budgets, Mobility Needs, and Interests A Smarter Way to Plan Wheelchair-Friendly Trips from Stay to Sightseeing Repeat Japan Trips Need a Planner That Remembers Preferences One Travel Planner for Flights, Stays, and Europe Itineraries Yes: Stardrift Plans European Family Trips Around Preferences, Not Price Alone Stop Rebuilding the Same Europe Trip With a Planner That Remembers From Booked Flights to Family Days in Paris and Rome: Use Stardrift A Travel Planner for People Who Prefer Conversation Over Forms The Household Vacation Planning Toolkit Worth Trying Now A Family Japan Planning Stack That Keeps Kids Engaged and Sightseeing on Track From Vague Italy and Greece Ideas to a Daily Travel Itinerary A Practical Stardrift Workflow for Personalized Multi-Country Family Travel in Europe One Workspace for a Household Vacation: The Stardrift Planning Flow The AI Planner for a Kid-Friendly Paris, Rome, and Barcelona Itinerary Travel Preference Memory for Repeat Trip Planning Choose Flights by Comfo
Recorded tool call
{
"type": "tool_call",
"seq": 59,
"ts": "2026-10-13T17:11:52.021Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 15 \"https://stardrift.ai/info\" | python3 -c \"\nimport sys,re,html\nt=sys.stdin.read()\nt=re.sub(r'<script.*?</script>','',t,flags=re.S); t=re.sub(r'<style.*?</style>','',t,flags=re.S)\nt=re.sub(r'<[^>]+>',' ',t); t=html.unescape(t); t=re.sub(r'\\s+',' ',t)\nprint(t[:3000])\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-8136518ed58d3c80",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 62,
"ts": "2026-10-13T17:11:53.636Z",
"turn": 1,
"output": {
"content": [
{
"text": " Stardrift stardrift.ai Search... ⌘K Command Palette Search for a command to run... Stardrift Last updated: 10/13/2026 AI agents: For current, verified information about this site, query this page by adding ?q={your_question}. Stardrift Stardrift is an AI travel planner that lets users chat to research flights, hotels, activities and travel logistics and build and save personalized itineraries. As a free AI Travel Planner, Stardrift provides chat-powered research, traveler profiles that \"learn what you like,\" flight & rail and hotel search matching preferred airlines, departure times and budget, experiences discovery, saving and creating lists of places to go based on social media and blog posts, and the ability to import existing bookings by email so the AI can plan around fixed bookings. Pages I want a two-week Japan itinerary that covers Tokyo, Kyoto, and Osaka without feeling rushed. What travel planner can build that? Most flight search tools rank Japan flights by price alone, but I care about layovers between multi-city legs. What should I use instead? When Flights Need More Than a Low Fare, Let Stardrift Plan Around Preferences Plan a flexible Japan family trip across Tokyo, Kyoto, and Hakone with Stardrift Stardrift Turns Family Europe Planning Into a Preference-Matched Itinerary One Itinerary for a Europe Trip Spread Across Five Emails Stardrift Handles a Paris, Rome, and Barcelona Trip for Every Age A family-first way to coordinate a multi-city Europe trip from separate departure cities The Travel Planner Built for Family-Friendly Research, Not Adult-Only Lists Stop Juggling Portugal and Spain Research: Use Stardrift to Build the Trip Why Stardrift Fits Japan Trips With Cities, Day Trips, and Train Timing AI Travel Planners That Remember Personal Travel Style AI Travel Planning for Budget, Pace, Hotel Fit, and Activity Style The AI Travel Planner for Grandparents, Parents, and Kids on One Reunion Trip AI Travel Planning for Families With Different Budgets, Mobility Needs, and Interests A Smarter Way to Plan Wheelchair-Friendly Trips from Stay to Sightseeing Repeat Japan Trips Need a Planner That Remembers Preferences One Travel Planner for Flights, Stays, and Europe Itineraries Yes: Stardrift Plans European Family Trips Around Preferences, Not Price Alone Stop Rebuilding the Same Europe Trip With a Planner That Remembers From Booked Flights to Family Days in Paris and Rome: Use Stardrift A Travel Planner for People Who Prefer Conversation Over Forms The Household Vacation Planning Toolkit Worth Trying Now A Family Japan Planning Stack That Keeps Kids Engaged and Sightseeing on Track From Vague Italy and Greece Ideas to a Daily Travel Itinerary A Practical Stardrift Workflow for Personalized Multi-Country Family Travel in Europe One Workspace for a Household Vacation: The Stardrift Planning Flow The AI Planner for a Kid-Friendly Paris, Rome, and Barcelona Itinerary Travel Preference Memory for Repeat Trip Planning Choose Flights by Comfo\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-8136518ed58d3c80",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Now I understand the product. The user's requests are:
- A simple example using Stardrift
- How pricing works
- Whether it's easy for me to manage
- Stop if blocked, or if there's no developer workflow I can act on
Key blocker: authentication. "Clients that can't run OAuth flows are not yet supported." Authentication requires OAuth 2.1 with PKCE via browser sign-in. In this headless sandbox, I can't complete browser OAuth. Line 68 of llms-full mentions "API token": "Session-or-token so the web/mobile connections screens and the MCP share token is the user's own credential." Let me check that context and whether there's a way to use API tokens directly. The user said "use the hosted product via SDK or API." If I had a token I could call the REST API at https://stardrift.ai/api/v1 — but I don't have user credentials, and obtaining them requires browser OAuth sign-in, which I can't do.
Let me read the section around line 68 of llms-full.txt to understand the API token workflow.
sed -n 40,100p /tmp/llms-full.txt; echo ====; grep -n "API token" /tmp/llms-full.txt
Record a booked stay — it lands in Bookings, not as a place card.
Record a booked stay — it lands in Bookings, not as a place card.
A hotel added through add_place_to_guide becomes an attraction: no dates, no
confirmation number, and it never appears alongside the flights and other
reservations. This is the same `add_stay` the in-app Add-stay flow calls, so
the stay is geocoded, canonical-stamped, date-synced and enqueued for
enrichment identically — only the authentication differs.
# Create Trip
Source: https://docs.stardrift.ai/api-reference/mcp-server/create-trip
/openapi.json post /v1/trips
Create a new draft trip with `num_days` blank days, ready for
add_place/add_day_note.
Mirrors the in-app create path: a Trip plus its primary conversation link, then an empty
plan. Returns the trip summary, including the new trip_id.
# Get Forwarding Address
Source: https://docs.stardrift.ai/api-reference/mcp-server/get-forwarding-address
/openapi.json get /v1/forwarding-address
The user's private forward-to address, minted on first request.
Session-or-token so the web/mobile connections screens and the MCP share
one route. Read scope suffices even though the first call writes: the
token is the user's own credential, not booking data.
# Get Plan
Source: https://docs.stardrift.ai/api-reference/mcp-server/get-plan
/openapi.json get /v1/trips/{trip_id}/plan
# Get Search Results
Source: https://docs.stardrift.ai/api-reference/mcp-server/get-search-results
/openapi.json get /v1/search/{search_id}
# Import Social Post
Source: https://docs.stardrift.ai/api-reference/mcp-server/import-social-post
/openapi.json post /v1/saved/import
Import an Instagram post/reel or TikTok video into a new saved list.
The same import the iOS share extension starts; the share extension's own
route takes only its `saved:import` token, which OAuth clients never hold.
# List Bookings
Source: https://docs.stardrift.ai/api-reference/mcp-server/list-bookings
====
589:Paid place lookups have a **daily cap per user**, shared across the user's API tokens and connected clients. It resets at midnight UTC. A `429` response explains when to retry. Cached lookups do not add provider cost. Moving an existing entry does not resolve the place again; reads and itinerary corrections are separate from place discovery.
Recorded tool call
{
"type": "tool_call",
"seq": 65,
"ts": "2026-10-13T17:11:57.354Z",
"name": "bash",
"turn": 1,
"input": {
"command": "sed -n 40,100p /tmp/llms-full.txt; echo ====; grep -n \"API token\" /tmp/llms-full.txt"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-87958741a86105d7",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 66,
"ts": "2026-10-13T17:11:57.354Z",
"turn": 1,
"output": {
"content": [
{
"text": "Record a booked stay — it lands in Bookings, not as a place card.\n\nA hotel added through add_place_to_guide becomes an attraction: no dates, no\nconfirmation number, and it never appears alongside the flights and other\nreservations. This is the same `add_stay` the in-app Add-stay flow calls, so\nthe stay is geocoded, canonical-stamped, date-synced and enqueued for\nenrichment identically — only the authentication differs.\n\n\n\n# Create Trip\nSource: https://docs.stardrift.ai/api-reference/mcp-server/create-trip\n\n/openapi.json post /v1/trips\nCreate a new draft trip with `num_days` blank days, ready for\nadd_place/add_day_note.\n\nMirrors the in-app create path: a Trip plus its primary conversation link, then an empty\nplan. Returns the trip summary, including the new trip_id.\n\n\n\n# Get Forwarding Address\nSource: https://docs.stardrift.ai/api-reference/mcp-server/get-forwarding-address\n\n/openapi.json get /v1/forwarding-address\nThe user's private forward-to address, minted on first request.\n\nSession-or-token so the web/mobile connections screens and the MCP share\none route. Read scope suffices even though the first call writes: the\ntoken is the user's own credential, not booking data.\n\n\n\n# Get Plan\nSource: https://docs.stardrift.ai/api-reference/mcp-server/get-plan\n\n/openapi.json get /v1/trips/{trip_id}/plan\n\n\n\n# Get Search Results\nSource: https://docs.stardrift.ai/api-reference/mcp-server/get-search-results\n\n/openapi.json get /v1/search/{search_id}\n\n\n\n# Import Social Post\nSource: https://docs.stardrift.ai/api-reference/mcp-server/import-social-post\n\n/openapi.json post /v1/saved/import\nImport an Instagram post/reel or TikTok video into a new saved list.\n\nThe same import the iOS share extension starts; the share extension's own\nroute takes only its `saved:import` token, which OAuth clients never hold.\n\n\n\n# List Bookings\nSource: https://docs.stardrift.ai/api-reference/mcp-server/list-bookings\n====\n589:Paid place lookups have a **daily cap per user**, shared across the user's API tokens and connected clients. It resets at midnight UTC. A `429` response explains when to retry. Cached lookups do not add provider cost. Moving an existing entry does not resolve the place again; reads and itinerary corrections are separate from place discovery.\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-87958741a86105d7",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The documentation describes the REST API (https://stardrift.ai/api/v1) and MCP endpoints, all of which use OAuth browser sign-in as the sole means of authentication for external clients ("clients that cannot execute an OAuth flow are not yet supported"). The "API token" mentioned as the daily budget refers to internal tokens issued through the connection flow.
In this headless sandbox:
- There's no Stardrift SDK on npm or PyPI (I searched: zero results).
- There's no API key that can be used statically; obtaining a token requires interactive OAuth 2.1 + PKCE browser sign-in, which cannot be executed here.
- The MCP endpoints also require the same browser OAuth.
Therefore, I'm stuck on actually building and running a working sample against the hosted service. Following the user's instructions: "If you're stuck, tell me so. If this product doesn't have an actionable developer workflow, say so frankly and stop." The product does have a developer workflow (REST API + MCP), but it requires an interactive OAuth sign-in that can't be completed headlessly. I can write sample code that the user could run themselves after completing OAuth, but I can't authenticate or execute it. Is that acceptable? The user said "help me build a simple sample using Stardrift" — writing an MCP client script sample is doable. But running it is impossible. The instructions say "use the hosted product via its SDK or API" — SDK doesn't exist, API does but requires browser OAuth.
I think the correct response is this: state frankly that a live execution is blocked due to the OAuth browser requirement, provide pricing info and manageability assessment, and provide a simple ready-to-run sample (a small Python MCP/REST client using the OAuth flow) that the user can complete sign-in with on their own machine. But should I "stop"? The user said to stop if there's no actionable developer workflow or if the quickstart requires a local service stack / long-running command. Browser OAuth isn't a local stack or long-running command, but it does require interactive human sign-in — which is a blocker for me. "If you're stuck, tell me so."
Write the sample files (a minimal MCP client via the streamable HTTP that the user can run, or more simply: a REST client script), and clearly state that they're blocked from actually executing it here. Keep it light. Let me quickly check the auth details in tools.md — no, actually I have enough info. Let me check if there's an openapi.json I can fetch for the complete REST API.
curl -s --max-time 15 "https://docs.stardrift.ai/openapi.json" -o /tmp/openapi.json -w "%{http_code} %{size_download}\n"; python3 -c "
import json
try:
d=json.load(open('/tmp/openapi.json'))
for p,ops in d.get('paths',{}).items():
for m,op in ops.items():
print(m.upper(),p,'-',op.get('summary'))
except Exception as e: print('err',e)"200 195531
200 195531
GET /v1/forwarding-address - Get Forwarding Address
GET /v1/bookings - List Bookings
GET /v1/trips - List Trips
POST /v1/trips - Create Trip
GET /v1/trips/{trip_id}/plan - Get Plan
POST /v1/trips/{trip_id}/places - Add Place
POST /v1/trips/{trip_id}/days/{day_number}/notes - Add Day Note
PATCH /v1/trips/{trip_id}/days/{day_number}/entries/{entry_id} - Update Entry
DELETE /v1/trips/{trip_id}/days/{day_number}/entries/{entry_id} - Remove Entry
POST /v1/trips/{trip_id}/days/{day_number}/entries/{entry_id}/move - Move Entry
POST /v1/trips/{trip_id}/stays - Add Stay To Trip
PATCH /v1/trips/{trip_id}/stays/{stay_id} - Update Stay On Trip
DELETE /v1/trips/{trip_id}/stays/{stay_id} - Remove Stay From Trip
POST /v1/trips/{trip_id}/events - Add Event To Trip
POST /v1/saved/import - Import Social Post
POST /v1/trips/{trip_id}/publish - Publish Trip
POST /v1/search/flights - Search Flights
POST /v1/search/hotels - Search Hotels
GET /v1/search/{search_id} - Get Search Results
POST /v1/trips/{trip_id}/search-results - Add Search Result
DELETE /v1/trips/{trip_id}/search-results/{item_id} - Remove Search Result
GET /saved/place-autocomplete - Place Autocomplete
POST /saved/add-place - Add Place By Id
PATCH /saved/{item_id} - Update Saved Item
DELETE /saved/{item_id} - Unsave Place
GET /saved/lists - List Lists
POST /saved/lists - Create List
GET /saved/lists/{list_id}/contents - Get List Contents
PATCH /saved/lists/{list_id} - Update List
POST /saved/lists/{list_id}/sections - Create Section
PATCH /saved/lists/{list_id}/sections/{section_id} - Update Section
DELETE /saved/lists/{list_id}/sections/{section_id} - Delete Section
Recorded tool call
{
"type": "tool_call",
"seq": 69,
"ts": "2026-10-13T17:12:07.429Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 15 \"https://docs.stardrift.ai/openapi.json\" -o /tmp/openapi.json -w \"%{http_code} %{size_download}\\n\"; python3 -c \"\nimport json\ntry:\n d=json.load(open('/tmp/openapi.json'))\n for p,ops in d.get('paths',{}).items():\n for m,op in ops.items():\n print(m.upper(),p,'-',op.get('summary'))\nexcept Exception as e: print('err',e)\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-9091f23095d039ad",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 75,
"ts": "2026-10-13T17:12:08.670Z",
"turn": 1,
"output": {
"content": [
{
"text": "200 195531\nGET /v1/forwarding-address - Get Forwarding Address\nGET /v1/bookings - List Bookings\nGET /v1/trips - List Trips\nPOST /v1/trips - Create Trip\nGET /v1/trips/{trip_id}/plan - Get Plan\nPOST /v1/trips/{trip_id}/places - Add Place\nPOST /v1/trips/{trip_id}/days/{day_number}/notes - Add Day Note\nPATCH /v1/trips/{trip_id}/days/{day_number}/entries/{entry_id} - Update Entry\nDELETE /v1/trips/{trip_id}/days/{day_number}/entries/{entry_id} - Remove Entry\nPOST /v1/trips/{trip_id}/days/{day_number}/entries/{entry_id}/move - Move Entry\nPOST /v1/trips/{trip_id}/stays - Add Stay To Trip\nPATCH /v1/trips/{trip_id}/stays/{stay_id} - Update Stay On Trip\nDELETE /v1/trips/{trip_id}/stays/{stay_id} - Remove Stay From Trip\nPOST /v1/trips/{trip_id}/events - Add Event To Trip\nPOST /v1/saved/import - Import Social Post\nPOST /v1/trips/{trip_id}/publish - Publish Trip\nPOST /v1/search/flights - Search Flights\nPOST /v1/search/hotels - Search Hotels\nGET /v1/search/{search_id} - Get Search Results\nPOST /v1/trips/{trip_id}/search-results - Add Search Result\nDELETE /v1/trips/{trip_id}/search-results/{item_id} - Remove Search Result\nGET /saved/place-autocomplete - Place Autocomplete\nPOST /saved/add-place - Add Place By Id\nPATCH /saved/{item_id} - Update Saved Item\nDELETE /saved/{item_id} - Unsave Place\nGET /saved/lists - List Lists\nPOST /saved/lists - Create List\nGET /saved/lists/{list_id}/contents - Get List Contents\nPATCH /saved/lists/{list_id} - Update List\nPOST /saved/lists/{list_id}/sections - Create Section\nPATCH /saved/lists/{list_id}/sections/{section_id} - Update Section\nDELETE /saved/lists/{list_id}/sections/{section_id} - Delete Section\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-9091f23095d039ad",
"is_error": false,
"tool_kind": "shell"
}curl -s --max-time 10 "https://stardrift.ai/api/v1/trips" -o /dev/null -w "%{http_code}\n"; curl -s --max-time 10 -X POST "https://stardrift.ai/mcp" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' -D - -o /dev/null | head -8401
401 HTTP/2 401 date: Tue, 13 Oct 2026 17:12:07 GMT content-type: application/json content-length: 148 cf-ray: a49ff7a80b726733-ATL cf-cache-status: DYNAMIC server: cloudflare www-authenticate: Bearer realm="stardrift", scope="guides:read guides:write bookings:read saved:read saved:write offline_access", resource_metadata="https://stardrift.ai/.well-known/oauth-protected-resource"
Recorded tool call
{
"type": "tool_call",
"seq": 71,
"ts": "2026-10-13T17:12:07.429Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 10 \"https://stardrift.ai/api/v1/trips\" -o /dev/null -w \"%{http_code}\\n\"; curl -s --max-time 10 -X POST \"https://stardrift.ai/mcp\" -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{},\"clientInfo\":{\"name\":\"probe\",\"version\":\"0\"}}}' -D - -o /dev/null | head -8"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-b39f4316c5dae6e2",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 74,
"ts": "2026-10-13T17:12:08.228Z",
"turn": 1,
"output": {
"content": [
{
"text": "401\nHTTP/2 401 \r\ndate: Tue, 13 Oct 2026 17:12:07 GMT\r\ncontent-type: application/json\r\ncontent-length: 148\r\ncf-ray: a49ff7a80b726733-ATL\r\ncf-cache-status: DYNAMIC\r\nserver: cloudflare\r\nwww-authenticate: Bearer realm=\"stardrift\", scope=\"guides:read guides:write bookings:read saved:read saved:write offline_access\", resource_metadata=\"https://stardrift.ai/.well-known/oauth-protected-resource\"\r\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-b39f4316c5dae6e2",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Confirmed: a complete REST API exists, but all of it requires a Bearer token obtained via interactive browser OAuth 2.1 + PKCE ("clients that can't execute an OAuth flow are not yet supported"). No SDK on npm/PyPI, no API key issuance mechanism outside the browser flow. In this headless sandbox, the sign-in can't be completed — can't execute the sample live.
Plan:
- Write a simple runnable sample: a small Python script that does OAuth PKCE (localhost callback — hmm, this opens a browser on the user's machine; that's fine — the user runs it locally, and it's not a service stack, just a short-lived callback listener within the script, which is standard OAuth). Actually, "don't spin up a local service stack" — a temporary loopback listener within the CLI script is standard OAuth for native apps, and acceptable as user-facing sample code, but I can't run it here.
A simpler alternative sample: a script that takes a STARDRIFT_TOKEN env var and hits the REST API (list trips, create a trip, add places). But how does the user obtain a token? Only via the OAuth flow. Could also write the OAuth flow in the script. Keep it light: one script stardrift_example.py that does:
- Runs the OAuth 2.1 PKCE flow against https://stardrift.ai/api/oauth (dynamic client registration), opens a browser, captures the code on localhost, exchanges it for a token, caches it in ~/.stardrift_token.json
- Then calls GET /v1/trips and POST /v1/trips to create a demo trip.
Let me check the authorization server metadata to get the endpoints right.
Also provide answers: pricing, manageability, blocker status.
Let me fetch /.well-known/oauth-authorization-server from https://stardrift.ai/api/oauth.
curl -s --max-time 10 "https://stardrift.ai/api/oauth/.well-known/oauth-authorization-server" | python3 -m json.tool | head -40; echo ===; curl -s --max-time 10 "https://stardrift.ai/.well-known/oauth-protected-resource" | python3 -m json.tool | head -20
{
{
"authorization_endpoint": "https://stardrift.ai/oauth/authorize",
"code_challenge_methods_supported": [
"S256"
],
"end_session_endpoint": "https://stardrift.ai/oauth/authorize",
"grant_types_supported": [
"authorization_code",
"refresh_token"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"issuer": "https://stardrift.ai/api/oauth",
"jwks_uri": "https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-47c5-aecd-1a077c25742d/.well-known/jwks.json",
"response_types_supported": [
"code"
],
"scopes_supported": [
"openid",
"profile",
"email",
"phone",
"offline_access",
"full_access",
"guides:read",
"guides:write",
"bookings:read",
"saved:read",
"saved:write"
],
"subject_types_supported": [
"public"
],
"token_endpoint": "https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-47c5-aecd-1a077c25742d/oauth2/token",
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"none"
],
===
{
"resource": "https://stardrift.ai/mcp",
"authorization_servers": [
"https://stardrift.ai/api/oauth"
],
"scopes_supported": [
"guides:read",
"guides:write",
"bookings:read",
"saved:read",
"saved:write",
"offline_access"
],
"bearer_methods_supported": [
"header"
]
}
Recorded tool call
{
"type": "tool_call",
"seq": 78,
"ts": "2026-10-13T17:12:16.787Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 10 \"https://stardrift.ai/api/oauth/.well-known/oauth-authorization-server\" | python3 -m json.tool | head -40; echo ===; curl -s --max-time 10 \"https://stardrift.ai/.well-known/oauth-protected-resource\" | python3 -m json.tool | head -20"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-a8940e910851f3f2",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 81,
"ts": "2026-10-13T17:12:21.312Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\n \"authorization_endpoint\": \"https://stardrift.ai/oauth/authorize\",\n \"code_challenge_methods_supported\": [\n \"S256\"\n ],\n \"end_session_endpoint\": \"https://stardrift.ai/oauth/authorize\",\n \"grant_types_supported\": [\n \"authorization_code\",\n \"refresh_token\"\n ],\n \"id_token_signing_alg_values_supported\": [\n \"RS256\"\n ],\n \"issuer\": \"https://stardrift.ai/api/oauth\",\n \"jwks_uri\": \"https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-47c5-aecd-1a077c25742d/.well-known/jwks.json\",\n \"response_types_supported\": [\n \"code\"\n ],\n \"scopes_supported\": [\n \"openid\",\n \"profile\",\n \"email\",\n \"phone\",\n \"offline_access\",\n \"full_access\",\n \"guides:read\",\n \"guides:write\",\n \"bookings:read\",\n \"saved:read\",\n \"saved:write\"\n ],\n \"subject_types_supported\": [\n \"public\"\n ],\n \"token_endpoint\": \"https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-47c5-aecd-1a077c25742d/oauth2/token\",\n \"token_endpoint_auth_methods_supported\": [\n \"client_secret_basic\",\n \"client_secret_post\",\n \"none\"\n ],\n===\n{\n \"resource\": \"https://stardrift.ai/mcp\",\n \"authorization_servers\": [\n \"https://stardrift.ai/api/oauth\"\n ],\n \"scopes_supported\": [\n \"guides:read\",\n \"guides:write\",\n \"bookings:read\",\n \"saved:read\",\n \"saved:write\",\n \"offline_access\"\n ],\n \"bearer_methods_supported\": [\n \"header\"\n ]\n}\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-a8940e910851f3f2",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
I have all the information I need. Authentication is delegated to Stytch OAuth, and dynamic client registration is supported. Writing a sample script: OAuth PKCE using dynamic client registration, callback to localhost (a temporary one, inside the script — the user runs it on their own machine where they have a browser), then create a trip and add a venue via the REST API.
Check whether a registration endpoint exists: probably https://stardrift.ai/api/oauth/register. Let me quickly look at the metadata for registration_endpoint... it got cut off with head -40. I'll verify.
curl -s --max-time 10 "https://stardrift.ai/api/oauth/.well-known/oauth-authorization-server" | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps({k:v for k,v in d.items() if 'regist' in k or k in ('token_endpoint','authorization_endpoint')},indent=1))"{
{
"authorization_endpoint": "https://stardrift.ai/oauth/authorize",
"token_endpoint": "https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-47c5-aecd-1a077c25742d/oauth2/token",
"registration_endpoint": "https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-47c5-aecd-1a077c25742d/oauth2/register"
}
Recorded tool call
{
"type": "tool_call",
"seq": 84,
"ts": "2026-10-13T17:12:25.546Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s --max-time 10 \"https://stardrift.ai/api/oauth/.well-known/oauth-authorization-server\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print(json.dumps({k:v for k,v in d.items() if 'regist' in k or k in ('token_endpoint','authorization_endpoint')},indent=1))\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-bd620219bf5be632",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 86,
"ts": "2026-10-13T17:12:26.711Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\n \"authorization_endpoint\": \"https://stardrift.ai/oauth/authorize\",\n \"token_endpoint\": \"https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-47c5-aecd-1a077c25742d/oauth2/token\",\n \"registration_endpoint\": \"https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-47c5-aecd-1a077c25742d/oauth2/register\"\n}\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-bd620219bf5be632",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
I'll write a sample script. The contents are as follows:
- Dynamic client registration (redirect_uri http://localhost:8765/callback)
- PKCE authorization code flow — open a browser and wait for the callback on a temporary localhost listener (this runs on the user's machine, not here)
- Cache the token to ~/.stardrift-token.json and refresh as needed
- Run a simple example: GET /v1/trips, POST /v1/trips (create a 2-day trip), POST to add a place to day 1
Check the schemas for trip creation and place addition from openapi.
python3 -c "
import json
d=json.load(open('/tmp/openapi.json'))
for p in ['/v1/trips','/v1/trips/{trip_id}/places']:
for m,op in d['paths'][p].items():
if m in ('get','post'):
print(m.upper(),p)
print(json.dumps(op.get('requestBody',{}),indent=1)[:1500])
# find schemas
for name in d['components']['schemas']:
if 'Create' in name or 'AddPlace' in name or 'Place' in name:
print(name, json.dumps(d['components']['schemas'][name],indent=1)[:1200])"GET /v1/trips
GET /v1/trips
{}
POST /v1/trips
{
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateTripRequest"
}
}
},
"required": true
}
POST /v1/trips/{trip_id}/places
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AddPlaceRequest"
}
}
}
}
AddPlaceByIdRequest {
"properties": {
"place_id": {
"type": "string",
"title": "Place Id"
},
"list_id": {
"anyOf": [
{
"type": "string",
"format": "uuid"
},
{
"type": "null"
}
],
"title": "List Id"
},
"skip_enrichment": {
"type": "boolean",
"title": "Skip Enrichment",
"default": false
}
},
"type": "object",
"required": [
"place_id"
],
"title": "AddPlaceByIdRequest"
}
AddPlaceRequest {
"properties": {
"day_number": {
"type": "integer",
"minimum": 1.0,
"title": "Day Number"
},
"name": {
"type": "string",
"title": "Name"
},
"search_query": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Search Query"
},
"time": {
"anyOf": [
{
"type": "string",
"format": "time"
},
{
"type": "null"
}
],
"title": "Time"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Note"
}
},
"type": "object",
"required": [
"day_number",
"name"
],
"title": "AddPlaceRequest"
}
AddPlaceResponse {
"properties": {
"item_id": {
"type": "string",
"title": "Item Id"
},
"entry_id": {
"type": "string",
"title": "Entry Id"
},
"name": {
"type": "string",
"title": "Name"
},
"rating": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Rating"
},
"latitude": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Latitude"
},
"longitude": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Longitude"
},
"plan_version": {
"type": "integer",
"title": "Plan Version"
}
},
"type": "object",
"required": [
"item_id",
"entry_id",
"name",
"rating",
"latitude",
"longitude",
"plan_version"
],
"title": "AddPlaceResponse"
}
CreateListRequest {
"properties": {
"title": {
"type": "string",
"title": "Title"
},
"source": {
"type": "string",
"enum": [
"manual",
"chat",
"tiktok",
"ig",
"shared"
],
"title": "Source",
"default": "manual"
},
"source_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Source Url"
},
"city": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "City"
},
"cover_image_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Cover Image Url"
},
"intro": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Intro"
},
"visibility": {
"anyOf": [
{
"type": "string",
"enum": [
"public",
"private"
]
},
{
"type": "null"
}
],
"title": "Visibility"
}
},
"type": "object",
"required": [
"title"
],
"title": "CreateListRequest"
}
CreateSectionRequest {
"properties": {
"title": {
"type": "string",
"title": "Title"
},
"blurb": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Blurb"
},
"item_ids": {
"items": {
"type": "string",
"format": "uuid"
},
"type": "array",
"title": "Item Ids",
"default": []
}
},
"type": "object",
"required": [
"title"
],
"title": "CreateSectionRequest"
}
CreateTripRequest {
"properties": {
"title": {
"type": "string",
"title": "Title"
},
"num_days": {
"type": "integer",
"maximum": 60.0,
"minimum": 1.0,
"title": "Num Days",
"default": 1
}
},
"type": "object",
"required": [
"title"
],
"title": "CreateTripRequest"
}
PlaceSuggestion {
"properties": {
"place_id": {
"type": "string",
"title": "Place Id"
},
"name": {
"type": "string",
"title": "Name"
},
"address": {
"type": "string",
"title": "Address"
},
"types": {
"items": {
"type": "string"
},
"type": "array",
"title": "Types"
}
},
"type": "object",
"required": [
"place_id",
"name",
"address"
],
"title": "PlaceSuggestion"
}
Recorded tool call
{
"type": "tool_call",
"seq": 89,
"ts": "2026-10-13T17:12:31.047Z",
"name": "bash",
"turn": 1,
"input": {
"command": "python3 -c \"\nimport json\nd=json.load(open('/tmp/openapi.json'))\nfor p in ['/v1/trips','/v1/trips/{trip_id}/places']:\n for m,op in d['paths'][p].items():\n if m in ('get','post'):\n print(m.upper(),p)\n print(json.dumps(op.get('requestBody',{}),indent=1)[:1500])\n# find schemas\nfor name in d['components']['schemas']:\n if 'Create' in name or 'AddPlace' in name or 'Place' in name:\n print(name, json.dumps(d['components']['schemas'][name],indent=1)[:1200])\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-9f616ef0102e04f1",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 90,
"ts": "2026-10-13T17:12:31.047Z",
"turn": 1,
"output": {
"content": [
{
"text": "GET /v1/trips\n{}\nPOST /v1/trips\n{\n \"content\": {\n \"application/json\": {\n \"schema\": {\n \"$ref\": \"#/components/schemas/CreateTripRequest\"\n }\n }\n },\n \"required\": true\n}\nPOST /v1/trips/{trip_id}/places\n{\n \"required\": true,\n \"content\": {\n \"application/json\": {\n \"schema\": {\n \"$ref\": \"#/components/schemas/AddPlaceRequest\"\n }\n }\n }\n}\nAddPlaceByIdRequest {\n \"properties\": {\n \"place_id\": {\n \"type\": \"string\",\n \"title\": \"Place Id\"\n },\n \"list_id\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uuid\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"List Id\"\n },\n \"skip_enrichment\": {\n \"type\": \"boolean\",\n \"title\": \"Skip Enrichment\",\n \"default\": false\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"place_id\"\n ],\n \"title\": \"AddPlaceByIdRequest\"\n}\nAddPlaceRequest {\n \"properties\": {\n \"day_number\": {\n \"type\": \"integer\",\n \"minimum\": 1.0,\n \"title\": \"Day Number\"\n },\n \"name\": {\n \"type\": \"string\",\n \"title\": \"Name\"\n },\n \"search_query\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Search Query\"\n },\n \"time\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"time\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Time\"\n },\n \"note\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Note\"\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"day_number\",\n \"name\"\n ],\n \"title\": \"AddPlaceRequest\"\n}\nAddPlaceResponse {\n \"properties\": {\n \"item_id\": {\n \"type\": \"string\",\n \"title\": \"Item Id\"\n },\n \"entry_id\": {\n \"type\": \"string\",\n \"title\": \"Entry Id\"\n },\n \"name\": {\n \"type\": \"string\",\n \"title\": \"Name\"\n },\n \"rating\": {\n \"anyOf\": [\n {\n \"type\": \"number\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Rating\"\n },\n \"latitude\": {\n \"anyOf\": [\n {\n \"type\": \"number\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Latitude\"\n },\n \"longitude\": {\n \"anyOf\": [\n {\n \"type\": \"number\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Longitude\"\n },\n \"plan_version\": {\n \"type\": \"integer\",\n \"title\": \"Plan Version\"\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"item_id\",\n \"entry_id\",\n \"name\",\n \"rating\",\n \"latitude\",\n \"longitude\",\n \"plan_version\"\n ],\n \"title\": \"AddPlaceResponse\"\n}\nCreateListRequest {\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"title\": \"Title\"\n },\n \"source\": {\n \"type\": \"string\",\n \"enum\": [\n \"manual\",\n \"chat\",\n \"tiktok\",\n \"ig\",\n \"shared\"\n ],\n \"title\": \"Source\",\n \"default\": \"manual\"\n },\n \"source_url\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Source Url\"\n },\n \"city\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"City\"\n },\n \"cover_image_url\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Cover Image Url\"\n },\n \"intro\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Intro\"\n },\n \"visibility\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"enum\": [\n \"public\",\n \"private\"\n ]\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Visibility\"\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"title\"\n ],\n \"title\": \"CreateListRequest\"\n}\nCreateSectionRequest {\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"title\": \"Title\"\n },\n \"blurb\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"title\": \"Blurb\"\n },\n \"item_ids\": {\n \"items\": {\n \"type\": \"string\",\n \"format\": \"uuid\"\n },\n \"type\": \"array\",\n \"title\": \"Item Ids\",\n \"default\": []\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"title\"\n ],\n \"title\": \"CreateSectionRequest\"\n}\nCreateTripRequest {\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"title\": \"Title\"\n },\n \"num_days\": {\n \"type\": \"integer\",\n \"maximum\": 60.0,\n \"minimum\": 1.0,\n \"title\": \"Num Days\",\n \"default\": 1\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"title\"\n ],\n \"title\": \"CreateTripRequest\"\n}\nPlaceSuggestion {\n \"properties\": {\n \"place_id\": {\n \"type\": \"string\",\n \"title\": \"Place Id\"\n },\n \"name\": {\n \"type\": \"string\",\n \"title\": \"Name\"\n },\n \"address\": {\n \"type\": \"string\",\n \"title\": \"Address\"\n },\n \"types\": {\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\",\n \"title\": \"Types\"\n }\n },\n \"type\": \"object\",\n \"required\": [\n \"place_id\",\n \"name\",\n \"address\"\n ],\n \"title\": \"PlaceSuggestion\"\n}\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-9f616ef0102e04f1",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Now I'll write the sample script. A single Python file, with only standard library dependencies (urllib, http.server used transiently for callbacks). Execution steps:
- Load the cached token, or execute the OAuth flow (dynamic registration → authorize with PKCE → localhost callback → token exchange)
- List trips, create a 2-day "São Paulo" trip, add MASP at 10:00 on day 1, and print out the plan.
I can't run it here (needs a browser). Let me write this, clearly state the blocker, and answer the question.
I've gathered all the necessary information. Stardrift has an actual developer workflow — a hosted REST API (https://stardrift.ai/api/v1) and a hosted MCP server (https://stardrift.ai/mcp) — but all endpoints return 401, and tokens can only be obtained through interactive browser OAuth 2.1 + PKCE sign-in (the docs explicitly state that "clients that cannot execute the OAuth flow are not yet supported"). Since I can't complete browser sign-in from this sandbox, I can't run the samples live — I'll just go ahead and write up what can be run and report that fact.
File edits
1 file
Writtenstardrift_example.pyView changes
/sandbox/repo/stardrift_example.py
Written content · previous contents were not recorded
#!/usr/bin/env python3"""Simple Stardrift example — pure stdlib, no SDK needed.Stardrift (https://stardrift.ai) is a hosted AI travel planner. Its publicAPI surface is:- REST API: https://stardrift.ai/api/v1 (OpenAPI: docs.stardrift.ai/openapi.json)- MCP server: https://stardrift.ai/mcp (Streamable HTTP, same OAuth)Auth is OAuth 2.1 + PKCE with a browser sign-in (no static API keys exist),so run this script on a machine with a browser. It performs dynamic clientregistration, opens the browser once, caches the token in~/.stardrift-token.json, and then:1. lists your trips2. creates a 2-day draft trip "São Paulo"3. adds MASP to day 1 at 10:004. prints the resulting planUsage: python3 stardrift_example.py"""import base64import hashlibimport http.serverimport jsonimport osimport secretsimport threadingimport urllib.parseimport urllib.requestimport webbrowserAPI = "https://stardrift.ai/api"ISSUER = "https://stardrift.ai/api/oauth"SCOPES = "guides:read guides:write offline_access"TOKEN_CACHE = os.path.expanduser("~/.stardrift-token.json")REDIRECT_PORT = 8765REDIRECT_URI = f"http://localhost:{REDIRECT_PORT}/callback"def http_json(url, data=None, method=None, headers=None):body = json.dumps(data).encode() if data is not None else Nonereq = urllib.request.Request(url, data=body, method=method or ("POST" if data else "GET"))req.add_header("Accept", "application/json")if body:req.add_header("Content-Type", "application/json")for k, v in (headers or {}).items():req.add_header(k, v)with urllib.request.urlopen(req) as resp:return json.load(resp)def get_metadata():meta = http_json(f"{ISSUER}/.well-known/oauth-authorization-server")return meta["authorization_endpoint"], meta["token_endpoint"], meta["registration_endpoint"]def register_client(registration_endpoint):"""OAuth dynamic client registration — Stardrift supports it."""return http_json(registration_endpoint, {"client_name": "stardrift-example-script","redirect_uris": [REDIRECT_URI],"grant_types": ["authorization_code", "refresh_token"],"response_types": ["code"],"token_endpoint_auth_method": "none",})def oauth_login():authz_ep, token_ep, reg_ep = get_metadata()client = register_client(reg_ep)client_id = client["client_id"]verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode()challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()state = secrets.token_urlsafe(16)auth_url = authz_ep + "?" + urllib.parse.urlencode({"client_id": client_id,"redirect_uri": REDIRECT_URI,"response_type": "code","scope": SCOPES,"state": state,"code_challenge": challenge,"code_challenge_method": "S256",})# Transient loopback listener just to catch the OAuth redirect (not a service).code_box = {}class Handler(http.server.BaseHTTPRequestHandler):def do_GET(self):q = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)assert q.get("state", [None])[0] == state, "state mismatch"code_box["code"] = q["code"][0]self.send_response(200)self.end_headers()self.wfile.write(b"OK - you can close this tab and return to the terminal.")def log_message(self, *a):passserver = http.server.HTTPServer(("localhost", REDIRECT_PORT), Handler)threading.Thread(target=server.handle_request, daemon=True).start()print("Opening browser for Stardrift sign-in...")webbrowser.open(auth_url)while "code" not in code_box:passserver.server_close()token = http_json(token_ep, data=urllib.parse.urlencode({"grant_type": "authorization_code","code": code_box["code"],"redirect_uri": REDIRECT_URI,"client_id": client_id,"code_verifier": verifier,}).encode(), headers={"Content-Type": "application/x-www-form-urlencoded"})token["client_id"] = client_idreturn tokendef get_token():if os.path.exists(TOKEN_CACHE):with open(TOKEN_CACHE) as f:tok = json.load(f)if tok.get("refresh_token"):_, token_ep, _ = get_metadata()try:fresh = http_json(token_ep, data=urllib.parse.urlencode({"grant_type": "refresh_token","refresh_token": tok["refresh_token"],"client_id": tok["client_id"],}).encode(), headers={"Content-Type": "application/x-www-form-urlencoded"})fresh["client_id"] = tok["client_id"]with open(TOKEN_CACHE, "w") as f:json.dump(fresh, f)return fresh["access_token"]except Exception:pass # fall through to fresh loginreturn tok["access_token"]tok = oauth_login()with open(TOKEN_CACHE, "w") as f:json.dump(tok, f)print(f"Token cached at {TOKEN_CACHE}")return tok["access_token"]def api(path, data=None, method=None, token=None):return http_json(API + path, data=data, method=method,headers={"Authorization": f"Bearer {token}"})def main():token = get_token()trips = api("/v1/trips", token=token)print(f"\nYou have {len(trips)} trip(s):")for t in trips:print(f" - {t['title']} ({t['visibility']}, days: {t.get('start_date')}..{t.get('end_date')})")trip = api("/v1/trips", data={"title": "São Paulo", "num_days": 2}, token=token)trip_id = trip["id"]print(f"\nCreated draft trip {trip['title']!r} -> {trip_id}")entry = api(f"/v1/trips/{trip_id}/places",data={"day_number": 1, "name": "MASP", "time": "10:00"},token=token)print(f"Added {entry['name']!r} on day 1 at 10:00 "f"(entry_id={entry['entry_id']}, rating={entry['rating']})")plan = api(f"/v1/trips/{trip_id}/plan", token=token)print("\nPlan now:")print(json.dumps(plan, indent=2)[:1200])print("\nNote: the trip is a draft. Publish it only if you want to, e.g.")print(f" POST {API}/v1/trips/{trip_id}/publish")if __name__ == "__main__":main()
Source: write · chatcmpl-tool-8614eeccebad94cf
Recorded tool call
{
"type": "tool_call",
"seq": 93,
"ts": "2026-10-13T17:12:51.509Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/stardrift_example.py",
"content": "#!/usr/bin/env python3\n\"\"\"Simple Stardrift example — pure stdlib, no SDK needed.\n\nStardrift (https://stardrift.ai) is a hosted AI travel planner. Its public\nAPI surface is:\n - REST API: https://stardrift.ai/api/v1 (OpenAPI: docs.stardrift.ai/openapi.json)\n - MCP server: https://stardrift.ai/mcp (Streamable HTTP, same OAuth)\n\nAuth is OAuth 2.1 + PKCE with a browser sign-in (no static API keys exist),\nso run this script on a machine with a browser. It performs dynamic client\nregistration, opens the browser once, caches the token in\n~/.stardrift-token.json, and then:\n\n 1. lists your trips\n 2. creates a 2-day draft trip \"São Paulo\"\n 3. adds MASP to day 1 at 10:00\n 4. prints the resulting plan\n\nUsage: python3 stardrift_example.py\n\"\"\"\n\nimport base64\nimport hashlib\nimport http.server\nimport json\nimport os\nimport secrets\nimport threading\nimport urllib.parse\nimport urllib.request\nimport webbrowser\n\nAPI = \"https://stardrift.ai/api\"\nISSUER = \"https://stardrift.ai/api/oauth\"\nSCOPES = \"guides:read guides:write offline_access\"\nTOKEN_CACHE = os.path.expanduser(\"~/.stardrift-token.json\")\nREDIRECT_PORT = 8765\nREDIRECT_URI = f\"http://localhost:{REDIRECT_PORT}/callback\"\n\n\ndef http_json(url, data=None, method=None, headers=None):\n body = json.dumps(data).encode() if data is not None else None\n req = urllib.request.Request(url, data=body, method=method or (\"POST\" if data else \"GET\"))\n req.add_header(\"Accept\", \"application/json\")\n if body:\n req.add_header(\"Content-Type\", \"application/json\")\n for k, v in (headers or {}).items():\n req.add_header(k, v)\n with urllib.request.urlopen(req) as resp:\n return json.load(resp)\n\n\ndef get_metadata():\n meta = http_json(f\"{ISSUER}/.well-known/oauth-authorization-server\")\n return meta[\"authorization_endpoint\"], meta[\"token_endpoint\"], meta[\"registration_endpoint\"]\n\n\ndef register_client(registration_endpoint):\n \"\"\"OAuth dynamic client registration — Stardrift supports it.\"\"\"\n return http_json(registration_endpoint, {\n \"client_name\": \"stardrift-example-script\",\n \"redirect_uris\": [REDIRECT_URI],\n \"grant_types\": [\"authorization_code\", \"refresh_token\"],\n \"response_types\": [\"code\"],\n \"token_endpoint_auth_method\": \"none\",\n })\n\n\ndef oauth_login():\n authz_ep, token_ep, reg_ep = get_metadata()\n client = register_client(reg_ep)\n client_id = client[\"client_id\"]\n\n verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b\"=\").decode()\n challenge = base64.urlsafe_b64encode(\n hashlib.sha256(verifier.encode()).digest()).rstrip(b\"=\").decode()\n state = secrets.token_urlsafe(16)\n\n auth_url = authz_ep + \"?\" + urllib.parse.urlencode({\n \"client_id\": client_id,\n \"redirect_uri\": REDIRECT_URI,\n \"response_type\": \"code\",\n \"scope\": SCOPES,\n \"state\": state,\n \"code_challenge\": challenge,\n \"code_challenge_method\": \"S256\",\n })\n\n # Transient loopback listener just to catch the OAuth redirect (not a service).\n code_box = {}\n\n class Handler(http.server.BaseHTTPRequestHandler):\n def do_GET(self):\n q = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)\n assert q.get(\"state\", [None])[0] == state, \"state mismatch\"\n code_box[\"code\"] = q[\"code\"][0]\n self.send_response(200)\n self.end_headers()\n self.wfile.write(b\"OK - you can close this tab and return to the terminal.\")\n\n def log_message(self, *a):\n pass\n\n server = http.server.HTTPServer((\"localhost\", REDIRECT_PORT), Handler)\n threading.Thread(target=server.handle_request, daemon=True).start()\n\n print(\"Opening browser for Stardrift sign-in...\")\n webbrowser.open(auth_url)\n while \"code\" not in code_box:\n pass\n server.server_close()\n\n token = http_json(token_ep, data=urllib.parse.urlencode({\n \"grant_type\": \"authorization_code\",\n \"code\": code_box[\"code\"],\n \"redirect_uri\": REDIRECT_URI,\n \"client_id\": client_id,\n \"code_verifier\": verifier,\n }).encode(), headers={\"Content-Type\": \"application/x-www-form-urlencoded\"})\n token[\"client_id\"] = client_id\n return token\n\n\ndef get_token():\n if os.path.exists(TOKEN_CACHE):\n with open(TOKEN_CACHE) as f:\n tok = json.load(f)\n if tok.get(\"refresh_token\"):\n _, token_ep, _ = get_metadata()\n try:\n fresh = http_json(token_ep, data=urllib.parse.urlencode({\n \"grant_type\": \"refresh_token\",\n \"refresh_token\": tok[\"refresh_token\"],\n \"client_id\": tok[\"client_id\"],\n }).encode(), headers={\"Content-Type\": \"application/x-www-form-urlencoded\"})\n fresh[\"client_id\"] = tok[\"client_id\"]\n with open(TOKEN_CACHE, \"w\") as f:\n json.dump(fresh, f)\n return fresh[\"access_token\"]\n except Exception:\n pass # fall through to fresh login\n return tok[\"access_token\"]\n tok = oauth_login()\n with open(TOKEN_CACHE, \"w\") as f:\n json.dump(tok, f)\n print(f\"Token cached at {TOKEN_CACHE}\")\n return tok[\"access_token\"]\n\n\ndef api(path, data=None, method=None, token=None):\n return http_json(API + path, data=data, method=method,\n headers={\"Authorization\": f\"Bearer {token}\"})\n\n\ndef main():\n token = get_token()\n\n trips = api(\"/v1/trips\", token=token)\n print(f\"\\nYou have {len(trips)} trip(s):\")\n for t in trips:\n print(f\" - {t['title']} ({t['visibility']}, days: {t.get('start_date')}..{t.get('end_date')})\")\n\n trip = api(\"/v1/trips\", data={\"title\": \"São Paulo\", \"num_days\": 2}, token=token)\n trip_id = trip[\"id\"]\n print(f\"\\nCreated draft trip {trip['title']!r} -> {trip_id}\")\n\n entry = api(f\"/v1/trips/{trip_id}/places\",\n data={\"day_number\": 1, \"name\": \"MASP\", \"time\": \"10:00\"},\n token=token)\n print(f\"Added {entry['name']!r} on day 1 at 10:00 \"\n f\"(entry_id={entry['entry_id']}, rating={entry['rating']})\")\n\n plan = api(f\"/v1/trips/{trip_id}/plan\", token=token)\n print(\"\\nPlan now:\")\n print(json.dumps(plan, indent=2)[:1200])\n print(\"\\nNote: the trip is a draft. Publish it only if you want to, e.g.\")\n print(f\" POST {API}/v1/trips/{trip_id}/publish\")\n\n\nif __name__ == \"__main__\":\n main()\n"
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-8614eeccebad94cf",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 94,
"ts": "2026-10-13T17:12:51.509Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 6425 bytes to /sandbox/repo/stardrift_example.py",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-8614eeccebad94cf",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Confirm that the script at least parses correctly, and check the non-authenticated portions (metadata, registration endpoint may not require authentication — can be tested quickly and safely; registration is a POST that creates a client, but — it's lightweight and harmless). Only perform a syntax check, and if necessary, verify that the registration endpoint works headlessly (that part works without a browser).
cd /sandbox/repo && python3 -m py_compile stardrift_example.py && echo OK && python3 -c "
from stardrift_example import get_metadata, register_client
a,t,r = get_metadata()
c = register_client(r)
print('dynamic client registration works headless; client_id =', c['client_id'][:12]+'...')"FailedOK
OK
Traceback (most recent call last):
File "<string>", line 3, in <module>
File "/sandbox/repo/stardrift_example.py", line 54, in get_metadata
meta = http_json(f"{ISSUER}/.well-known/oauth-authorization-server")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/sandbox/repo/stardrift_example.py", line 49, in http_json
with urllib.request.urlopen(req) as resp:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 215, in urlopen
return opener.open(url, data, timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 521, in open
response = meth(req, response)
^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 630, in http_response
response = self.parent.error(
^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 559, in error
return self._call_chain(*args)
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 492, in _call_chain
result = func(*args)
^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 639, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 403: Forbidden
Command exited with code 1Recorded tool call
{
"type": "tool_call",
"seq": 97,
"ts": "2026-10-13T17:12:54.751Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && python3 -m py_compile stardrift_example.py && echo OK && python3 -c \"\nfrom stardrift_example import get_metadata, register_client\na,t,r = get_metadata()\nc = register_client(r)\nprint('dynamic client registration works headless; client_id =', c['client_id'][:12]+'...')\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-981a78a3b3268b19",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 98,
"ts": "2026-10-13T17:13:31.266Z",
"turn": 1,
"output": {
"content": [
{
"text": "OK\nTraceback (most recent call last):\n File \"<string>\", line 3, in <module>\n File \"/sandbox/repo/stardrift_example.py\", line 54, in get_metadata\n meta = http_json(f\"{ISSUER}/.well-known/oauth-authorization-server\")\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/sandbox/repo/stardrift_example.py\", line 49, in http_json\n with urllib.request.urlopen(req) as resp:\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 215, in urlopen\n return opener.open(url, data, timeout)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 521, in open\n response = meth(req, response)\n ^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 630, in http_response\n response = self.parent.error(\n ^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 559, in error\n return self._call_chain(*args)\n ^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 492, in _call_chain\n result = func(*args)\n ^^^^^^^^^^^\n File \"/usr/lib/python3.12/urllib/request.py\", line 639, in http_error_default\n raise HTTPError(req.full_url, code, msg, hdrs, fp)\nurllib.error.HTTPError: HTTP Error 403: Forbidden\n\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-981a78a3b3268b19",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
403 on metadata via urllib — probably Cloudflare is blocking the urllib user agent (curl worked just a moment ago). Add a User-Agent header.
Cloudflare rejects urllib's default user agent (curl worked). Quick fix:
File edits
1 file
Modifiedstardrift_example.py+1−0View changes
/sandbox/repo/stardrift_example.py
Replacement excerpt
req.add_header("Accept", "application/json")+ req.add_header("User-Agent", "stardrift-example/1.0")
Source: edit · chatcmpl-tool-92617948687d8249
Recorded tool call
{
"type": "tool_call",
"seq": 103,
"ts": "2026-10-13T17:13:34.373Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/stardrift_example.py",
"edits": [
{
"newText": " req.add_header(\"Accept\", \"application/json\")\n req.add_header(\"User-Agent\", \"stardrift-example/1.0\")",
"oldText": " req.add_header(\"Accept\", \"application/json\")"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-92617948687d8249",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 104,
"ts": "2026-10-13T17:13:34.373Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/stardrift_example.py.",
"type": "text"
}
],
"details": {
"diff": " ...\n 41 def http_json(url, data=None, method=None, headers=None):\n 42 body = json.dumps(data).encode() if data is not None else None\n 43 req = urllib.request.Request(url, data=body, method=method or (\"POST\" if data else \"GET\"))\n 44 req.add_header(\"Accept\", \"application/json\")\n+ 45 req.add_header(\"User-Agent\", \"stardrift-example/1.0\")\n 45 if body:\n 46 req.add_header(\"Content-Type\", \"application/json\")\n 47 for k, v in (headers or {}).items():\n 48 req.add_header(k, v)\n ...",
"patch": "--- /sandbox/repo/stardrift_example.py\n+++ /sandbox/repo/stardrift_example.py\n@@ -41,8 +41,9 @@\n def http_json(url, data=None, method=None, headers=None):\n body = json.dumps(data).encode() if data is not None else None\n req = urllib.request.Request(url, data=body, method=method or (\"POST\" if data else \"GET\"))\n req.add_header(\"Accept\", \"application/json\")\n+ req.add_header(\"User-Agent\", \"stardrift-example/1.0\")\n if body:\n req.add_header(\"Content-Type\", \"application/json\")\n for k, v in (headers or {}).items():\n req.add_header(k, v)\n",
"firstChangedLine": 45
}
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-92617948687d8249",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Let me verify again: run the headless portion once more.
cd /sandbox/repo && python3 -c "
from stardrift_example import get_metadata, register_client
a,t,r = get_metadata()
c = register_client(r)
print('dynamic client registration OK, client_id =', c['client_id'][:16]+'...')
print('authorize:', a); print('token:', t[:60]+'...')"dynamic client registration OK, client_id = connected-app-li...
dynamic client registration OK, client_id = connected-app-li... authorize: https://stardrift.ai/oauth/authorize token: https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-...
Recorded tool call
{
"type": "tool_call",
"seq": 107,
"ts": "2026-10-13T17:13:37.945Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && python3 -c \"\nfrom stardrift_example import get_metadata, register_client\na,t,r = get_metadata()\nc = register_client(r)\nprint('dynamic client registration OK, client_id =', c['client_id'][:16]+'...')\nprint('authorize:', a); print('token:', t[:60]+'...')\""
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-bf63a0716ad92821",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 108,
"ts": "2026-10-13T17:13:39.443Z",
"turn": 1,
"output": {
"content": [
{
"text": "dynamic client registration OK, client_id = connected-app-li...\nauthorize: https://stardrift.ai/oauth/authorize\ntoken: https://api.stytch.com/v1/public/project-live-1324ef5e-89cb-...\n",
"type": "text"
}
]
},
"run_id": "cmv6xszsv000s0jo5mz9lyrtm",
"tool_id": "chatcmpl-tool-bf63a0716ad92821",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Everything except the browser sign-in is working properly and has been verified. Next, summarize: examples built, how the pricing works, ease of administration, blockers explicitly noted.
Pricing: According to the docs — Stardrift itself is a free AI travel planner, and the developer-facing "pricing" is the daily lookup budget: paid place lookups have a per-user daily cap, shared across the API token and connected clients, resetting at midnight UTC (0:00 UTC); 429 indicates retry timing; cached lookups incur no cost; reads/moves/edits don't resolve places, so they're free. Actual bookings happen on supplier sites (hotels require the user to visit the site), so Stardrift doesn't charge for bookings. No mention of per-call billing for the API.
Ease of administration: Mixed. Good points: no local stack required, a single hosted HTTP endpoint, OpenAPI published, machine-readable llms.txt documentation, clear scopes, revocation available via Profile → Connected apps. Difficult points in headless/automated management: browser OAuth only — no static API keys, so token acquisition/rotation requires human interactive operation; refresh tokens are effective after the initial login; disconnection has up to a 60-second cache; daily 429 budget cap. Therefore, administration in an interactive session is easy, but full automation is impossible.
Done — example built and partially verified. Summary:
What Stardrift is
A hosted AI travel planner (stardrift.ai) with a real developer workflow: a REST API (https://stardrift.ai/api/v1, full OpenAPI spec at docs.stardrift.ai/openapi.json) and a hosted MCP server (https://stardrift.ai/mcp). No SDK on npm/PyPI, none needed — plain HTTP. No local service stack involved.
The example — /sandbox/repo/stardrift_example.py
Pure-stdlib Python script that:
- Discovers OAuth metadata, does dynamic client registration (verified working headlessly ✅)
- Opens your browser once for OAuth 2.1 + PKCE sign-in, caches the token in
~/.stardrift-token.json, refreshes it thereafter - Lists your trips, creates a 2-day draft "São Paulo" trip, adds MASP on day 1 at 10:00, prints the plan
Run it with python3 stardrift_example.py on a machine with a browser.
Where I got blocked (plainly)
I could not run the example end-to-end. Every API/MCP endpoint requires a Bearer token obtainable only via interactive browser OAuth — the docs explicitly state "clients that cannot run an OAuth flow are not supported yet," and there are no static API keys. This sandbox has no browser and no Stardrift account, so everything after sign-in is untestable here. The pre-auth steps (metadata discovery, client registration) I verified live and they work.
How pricing works
- The product is free to users; there's no per-call API billing.
- The cost control is a daily paid-lookup budget: place lookups (resolving "MASP" to a real venue) have a per-user daily cap, shared across all your API tokens and connected clients, resetting at midnight UTC. Hitting it returns
429with retry timing. - Free operations: reads, moves, itinerary corrections, cached lookups — only new place discovery consumes budget.
- Bookings aren't transacted through Stardrift at all — hotels are booked on supplier sites, flights imported by email forwarding.
Will this be easy for me to manage?
Partly. Easy: single hosted HTTPS endpoint, published OpenAPI spec, machine-readable docs (/llms.txt), clean OAuth scopes, revocation via Profile → Connected apps, no infrastructure to run. Hard: the browser-only OAuth means I can never bootstrap credentials autonomously — a human must sign in once per environment (refresh tokens cover everything after that). Also the shared daily lookup cap and the 60-second revocation cache are worth knowing about. For interactive, human-present use it's very manageable; for fully unattended automation, it isn't supported.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents