Developer docs
Kairotope plans a person's errands and commitments against real opening hours and live traffic, and remembers their places (home, their pharmacy, their kids' schools) and preferences. AI assistants reach it through an MCP server; other software can use the equivalent REST API. Both call the same planning engine.
- Connecting
- Authentication
- Tools
- Responses
- Example
- Guidance for assistants
- Plans, limits and coverage
- REST API
- Data handling
Connecting
| Endpoint | https://kairotope.com/mcp |
|---|---|
| Transport | MCP streamable HTTP (stateless; every request is a POST with a JSON response, no session to keep) |
| Protocol | Model Context Protocol, server name kairotope. Tools only (no resources or prompts). The server sends usage instructions in its initialize result. |
| Auth | Authorization: Bearer kt_… on every request (see below) |
Authentication
Every call is tied to one person's account (their household's saved places, preferences and plans) by a token. There are two ways to get one, and both reach the same account.
Sign in with Kairotope (OAuth 2.1)
For assistants that connect by OAuth, such as Meta Muse, ChatGPT and Claude. Point the assistant at https://kairotope.com/mcp; it discovers everything else from the first 401.
| Protected resource metadata | /.well-known/oauth-protected-resource/mcp (RFC 9728) |
|---|---|
| Authorization server metadata | /.well-known/oauth-authorization-server (RFC 8414); issuer https://kairotope.com |
| Client registration | A client metadata document URL as client_id, or dynamic registration at /oauth/register (RFC 7591) |
| Grant | Authorization code with PKCE (S256 only); the response carries iss. Scope kairotope. |
| Tokens | Access tokens last 1 hour. Refresh tokens rotate on every use; an old one stops working. |
The person signs in with their email and presses Allow. The connection appears in their account under Assistants, where they can disconnect it, which also ends its refresh token.
Connector token
For assistants that take a pasted credential, such as Meta Muse, and for the REST API.
- The person signs in at kairotope.com with their email (a one-time link; no password). The same link creates a free account.
- In their account, under Assistants, they create a token. It starts with
kt_and is shown once. Kairotope stores only a hash of it. - They give it to their assistant through its secure credential prompt. The assistant sends it as a bearer token.
Connector tokens don't expire, and can be revoked at any time in the account, under Assistants. On /mcp, a missing, invalid, expired or revoked token gets HTTP 401 with WWW-Authenticate: Bearer resource_metadata="https://kairotope.com/.well-known/oauth-protected-resource/mcp", scope="kairotope", which is where OAuth clients start.
Tools
Every tool returns the response envelope as structuredContent, with its message also as text content.
plan_errands
Turn a request like "pick up my prescription, get groceries and be at dinner by 7" into an ordered, traffic-aware plan with a leave-by time. Call once per request.
| Parameter | Type | Description |
|---|---|---|
intention required | string | The user's request in their own words. Saved places can be named ("my pharmacy", "home"). |
date | string | YYYY-MM-DD if known; otherwise inferred from the request. |
start_location | string | Only when starting somewhere other than the saved home. |
commitments | array | Fixed calendar events the plan must respect: { title, start, end?, location? }. start/end are ISO 8601 or HH:MM local. |
revise_plan
Apply a change to an earlier plan ("skip the shoe return", "we're leaving 20 minutes late"). Returns a new plan with a new planId.
| Parameter | Type | Description |
|---|---|---|
plan_id required | string | The plan to change. |
change required | string | What to change, in the user's words. |
confirm_plan
The user explicitly accepted the plan ("yes", "confirm", "sounds good"). Never call it on silence or a topic change. Marks it as the one they're following and schedules a one-tap "how did it go?" email for that evening. Every plan's message ends by inviting the user to confirm.
| Parameter | Type | Description |
|---|---|---|
plan_id | string | The plan to confirm. Leave out to confirm the household's latest plan (made in the last 12 hours). |
list_plans
The user's upcoming plans from Kairotope's records: one per day, marked confirmed (locked in) or draft (planned, never confirmed), with the leave time and stops. Use it for "what are my plans?" or "what's on tomorrow?" rather than your own memory. No parameters. Also GET /api/v1/plans. On the day, today's plan also carries latestAlert: the newest traffic alert Kairotope emailed for a drive still to come.
check_plan
Whether a plan still holds: the drives still to come, each with its planned leave time and its latest live-traffic drive time, and any traffic alert. It answers from Kairotope's own live-traffic checks, which start an hour before each drive on a plan's day (trial and Plus). It makes no new plan and uses none of the allowance, so call it for "am I still on time?" and for any check you schedule before the user leaves. Never re-plan just to re-check. Returns ok with check: planId, date, status, liveTraffic, drives and, when there is one, alert. From a scheduled check, message the user only when check.alert is set. Also GET /api/v1/plans/check.
| Parameter | Type | Description |
|---|---|---|
date | string | YYYY-MM-DD. Leave it out for today's plan, else the next upcoming one. |
cancel_plan
The user dropped a plan before its day ("cancel Saturday", "never mind tomorrow"). Cancelled plans leave list_plans and get no check-in email. Also POST /api/v1/plans/{planId}/cancel.
| Parameter | Type | Description |
|---|---|---|
plan_id | string | The plan to cancel (from list_plans or a plan result), with its revisions. |
date | string | YYYY-MM-DD: cancel every plan for that day. With neither, cancels the only upcoming plan, or asks which. |
report_outcome
How the plan went, when the user says so.
| Parameter | Type | Description |
|---|---|---|
plan_id required | string | The plan. |
status required | string | completed, partial, abandoned (the day didn't go to plan) or cancelled (dropped before the day; same as cancel_plan). |
stop_seq | integer | A single stop this is about. |
rating | integer | 1–5. |
note | string | Anything else the user said. |
remember
Save a place or preference for future plans. Nothing is saved without the user saying so. Labels are the user's own names for places: "my gym" saved at 123 Main St means "stop at the gym" plans to 123 Main St. "my gym", "our gym", "the gym" and "gym" are the same place, and "home", "my house" or "our home" all mean the household's one home.
Every assistant a person connects shares one account. If something different is already saved under that name (a place more than 200 m away, or a different preference value), maybe by another assistant, nothing changes: the result is confirm_replace with a question saying what's saved, where it came from and what would replace it. Ask it; only on a yes, call again with replace: true and the result's confirm_token (it expires after 10 min). replace: true without a valid token is ignored and brings the question back. An assistant changing a place it saved itself isn't asked: that's the user correcting it there. Preferences always ask, since they don't record which assistant set them.
| Parameter | Type | Description |
|---|---|---|
type required | enum | place or preference. |
label | string | Place: the user's name for it ("home", "my pharmacy", "Sam's school"). |
where | string | Place: an address or business name ("CVS on Parmer Ln"). |
kind | string | Place: home, pharmacy, grocery, school, work, gym, doctor, restaurant or other. |
key | string | Preference name, e.g. max_drive_minutes. |
value | string, number or boolean | Preference value. |
replace | boolean | Only after the user said yes to a confirm_replace question: replace what's saved. Never on the first call. |
confirm_token | string | With replace: the confirm_token from the confirm_replace result the user said yes to. |
forget
Delete a saved place or preference.
| Parameter | Type | Description |
|---|---|---|
name required | string | The place label or preference name. |
get_household
List the user's saved places and preferences. No parameters.
ping
Connectivity and latency check. Doesn't plan anything or count against limits.
| Parameter | Type | Description |
|---|---|---|
sleep_ms | integer | Wait this long before answering (0–25,000). |
Responses
Every tool answers with the same envelope. kind says what happened:
kind | Meaning | What to do |
|---|---|---|
plan | A plan, in plan, with its planId. | Relay it. Offer any suggestions. |
clarify | Something needs the user's answer, in questions. | Ask, then call plan_errands again with the answers. |
infeasible | It can't all fit; reasons says why. | Explain, and offer to drop or move something. |
limit | The household's allowance is used up for now, or a Free household tried to change a plan. | Relay message: it says what to do and when. |
not_found | No such plan, place or preference. | Check the id or name. |
confirm_replace | Something different is already saved under that name; nothing changed. question says what's saved and what would replace it (REST: HTTP 409). | Ask the user question. On a yes, call remember again with replace: true and its confirm_token. |
ok | Done (saved, forgotten, confirmed, recorded, or the household summary). | Confirm back to the user. |
probe | A connectivity test was recognized as one and not planned. | Use ping for connectivity checks. |
error | Something went wrong; message says what. The tool result is marked isError. | Relay message. |
Other envelope fields: localNow and today (the household's current local time and date, on every result), plans (from list_plans), message (a short summary that's safe to relay as is), suggestions (places worth saving, each with a question to ask), reused (true when an identical request made moments earlier was answered from the stored plan), mapUrl (on plans, confirm_plan, check_plan and each of list_plans's plans, when plan pages are on for the account: a private link to the plan on a map, with each stop's times and directions; give it to the user as is; it works without signing in until 12 hours after the plan's last stop, and is also in message), and nextStep (an instruction for the assistant, not to read out: on plans, offer to lock the plan in and call confirm_plan if the user agrees). Over MCP, nextStep also arrives as a second text block.
Notices. Assistants can't receive a push from a connector, so Kairotope emails traffic alerts on the day of a plan, and any result may also carry notices: things the user should hear before your answer. A traffic_alert notice is an alert Kairotope emailed (with sentAt) that the user hasn't yet seen through an assistant, while its drive is still to come. A checkin notice asks how a finished day went, for up to 3 days after it: ask the user, then call report_outcome with its planId. Each notice has a message to relay and a nextStep for you, and comes once per account, on whichever assistant calls first. Results with kind error, not_found, limit or probe carry none. Over MCP, notices arrive as the first text block.
The plan object
| Field | Description |
|---|---|
status | on_track, or at_risk when a buffer is thin. |
leaveBy | When to leave the start location (ISO 8601 with offset). |
stops | In order: seq, label, placeName, address, arriveAt, departAt, legMinutes (drive from the previous stop), freeMinutesBefore, isFixed (a commitment that can't move), note. |
totalDriveMinutes | All driving in the plan. |
minutesSavedVsNaive | Driving saved compared with doing the errands in the order listed. |
unscheduled | Errands that couldn't fit, each with a reason. Always tell the user. |
warnings | Assumptions and cautions, e.g. which branch was chosen or rain on the drive. |
explanation | A plain-language summary. Every time and number in it comes from the fields above. |
Example
Request (MCP tools/call):
{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {
"name": "plan_errands",
"arguments": {
"intention": "Pick up my prescription, get groceries and be at dinner at The Oasis by 7",
"date": "2026-10-03"
}
}
}
Result (structuredContent, abridged):
{
"kind": "plan",
"planId": "3f6c…",
"message": "Leave at 5:12 PM. Walgreens first, since it closes at 6:00 PM… Say “confirm” to lock this plan in, and I'll check in that evening to see how it went.",
"plan": {
"planId": "3f6c…",
"status": "on_track",
"leaveBy": "2026-10-03T17:12:00-05:00",
"stops": [
{ "seq": 1, "label": "Pick up prescription", "placeName": "Walgreens",
"arriveAt": "2026-10-03T17:21:00-05:00", "departAt": "2026-10-03T17:31:00-05:00",
"legMinutes": 9, "isFixed": false, "note": "Pharmacy closes at 6:00 PM." },
{ "seq": 2, "label": "Get groceries", "placeName": "H-E-B",
"arriveAt": "2026-10-03T17:39:00-05:00", "departAt": "2026-10-03T18:09:00-05:00",
"legMinutes": 8, "isFixed": false },
{ "seq": 3, "label": "Dinner", "placeName": "The Oasis",
"arriveAt": "2026-10-03T18:50:00-05:00", "departAt": "2026-10-03T20:20:00-05:00",
"legMinutes": 31, "isFixed": true }
],
"totalDriveMinutes": 48,
"minutesSavedVsNaive": 6,
"unscheduled": [],
"warnings": ["I assumed your prescription is at Walgreens (1505 W Whitestone Blvd)."],
"explanation": "Leave at 5:12 PM. Walgreens first, since it closes at 6:00 PM…"
},
"suggestions": [
{ "label": "my pharmacy", "where": "Walgreens, 1505 W Whitestone Blvd", "kind": "pharmacy",
"question": "Is the Walgreens on W Whitestone Blvd your usual pharmacy?" }
]
}
Guidance for assistants
The server sends these rules in its instructions. They're what makes plans correct across conversations:
- Call
plan_errandsonce per request. For changes, userevise_planrather than re-sending the request. - When the user says where they live, or which pharmacy, store or school they use, call
remember. Don't keep it only in your own memory or paste addresses into requests. - If a result has
suggestions, ask eachquestion, and callrememberonly when the user says yes. - Relay times, stop order, warnings and
unscheduledexactly as returned. Don't recompute or round them. - Plans are drafts until confirmed. After presenting one, ask the user explicitly whether to lock it in, and call
confirm_plan(no arguments needed) only on an explicit yes. - To drop a plan, call
cancel_plan. - If a result's
kindiserrorornot_found, it did not happen: tell the user, never say it worked. - Use
pingfor connectivity checks, neverplan_errands. - If a result's
kindislimit, relay itsmessageand don't retry. - For "what are my plans?", call
list_plansand answer from it: it knows which plans are confirmed and which are only drafts. - Use
localNow(every result has it) for "today", "tomorrow" and how far away things are. - If a result has
notices, pass them on before your answer: alerts with their times exactly as written; for a check-in, ask how the day went and callreport_outcome. - To see whether a plan still holds, including from a check you schedule yourself, call
check_plan, neverplan_errandsorrevise_plan. From a scheduled check, message the user only when it returns an alert.
Plans, limits and coverage
Every new account starts with a free trial of the full product. Each plan and each revision counts toward a household's allowance. Repeated identical requests, connectivity checks and ping don't.
| Plan | Allowance | What's included |
|---|---|---|
| Free trial | 20 plans over 30 days (at most 5 plans in a day) | Everything: live traffic at your departure times, rush-hour and weather adjustments, plan changes, and traffic alerts by email on the day of a plan. |
| Free | 4 plans a month, from when the trial ends | Plans with typical traffic (not checked against live traffic) and no weather adjustments. Plans can't be changed with revise_plan; ask for a new plan instead. No traffic alerts on the day. |
| Plus ($6.99 a month or $69.90 a year, pre-launch pricing) | Unlimited, with fair use of 10 plans in a day and 60 plans a month | Everything in the trial, for one person. |
Prices and what each tier includes are on the pricing page. When an allowance runs out, calls return kind: "limit" with a message to relay, saying when more plans are available. Plans made on Free say in their message and warnings that they use typical traffic. get_household includes the household's tier (in plan) and what's left of its allowance.
- An identical request with unchanged saved places within 10 minutes returns the stored plan (
reused: true) and doesn't count. - At times of very high demand, trial and free requests may be asked to try again later.
- Plans typically take a few seconds; complex days with many stops can take longer.
- Coverage: United States. Kairotope is being tested in Central Texas first.
- Driving only, for now. Times are in the household's time zone, set from the browser at sign-up and from the saved home address (saving a home in another zone switches it, and the result's
messagesays so).
REST API
The same operations are available over HTTPS at https://kairotope.com/api/v1 with the same bearer token, for software that doesn't speak MCP. The OpenAPI 3.1 description is at /api/v1/openapi.json.
Data handling
- Saved addresses and locations are encrypted at rest. Tokens are stored only as hashes.
- People can see, download and delete everything Kairotope holds in their account.
- Map data from third parties is kept no longer than their terms allow; after 30 days stored plans keep only times and labels.
- Full details: Privacy Policy and Terms.
Questions or problems: hello@kairotope.com