kordi for Developers

Give your AI agent a memory for subscriptions. kordi exposes a user's billing state — active subs, trials, price hikes, upcoming bills — through a standard MCP server and a REST provisioning API.

MCP Streamable HTTP 30 Tools 10 Prompts 3 Resources Partner Provisioning
waving_hand

Not a developer? You don't need any of this page — the step-by-step setup guides connect kordi to Claude, ChatGPT, or Gemini in about two minutes, no code involved.

hub

MCP Server

Streamable HTTP at /mcp. Any MCP client — Claude, Raycast, Cursor — can connect with a Bearer token.

push_pin

Proactive Triggers

kordi_list_proactive_tasks returns the agent worklist: upcoming renewals, trials about to convert, and pauses due to resume — sorted most-urgent first.

person_add

Partner Provisioning

Push subscriptions for users who don't have kordi accounts yet. Shadow account + verify email. Scales with a partner key (1,000/hr).

mail

Zero-effort onboarding: let the agent read the inbox

kordi is great empty, but it's powerful full. When a user connects kordi alongside an email connector (Gmail, Superhuman, Outlook, etc.) in the same Claude session, the agent becomes the glue: it scans the inbox for subscription receipts, renewal notices, and "your trial is ending" emails, then imports the whole list into kordi in one call via kordi_import_subscriptions with source set to the connector name (e.g., source:"gmail", source:"superhuman", source:"outlook") — amount, billing date, and trial end date pulled straight from the message. One call keeps the whole import inside the per-session rate limit (no stalling mid-list), dedupes by name, and isolates bad rows. No manual entry, no card-statement hunting. (Each source tag also lets us measure which email connector drives the best adoption.)

It reconciles before it writes — server-side: a signup or trial-start email means a subscription began, not that it's active today. The agent tags each find with evidence_type + evidence_date (the most recent billing-relevant email it saw) and submits everything; kordi imports the clearly-active ones (fresh charges/renewals, still-running trials) and hands the rest back in needs_confirmation with per-item reasons — a dead free trial, a cancellation, a service that hasn't billed in months — nothing written until the user confirms (user_confirmed: true on re-submit). A stale trial can't become a live bill even if the client model skips the instructions.

Just tell Claude: "Scan my email for subscriptions and add them to kordi." The two connectors never talk to each other directly — the model orchestrates both tool sets in sequence.

kordi's server instructions and the empty-account getting_started hints already nudge capable agents toward this flow, so it works the moment both connectors are present.

Step 1 — Get a token

Individual users generate a long-lived token at /token. Partners provisioning multiple users use guest-ingest — tokens are returned in the response.

Step 2 — Call the MCP endpoint

# Preferred: Authorization header (token never hits logs/Referer)
curl -X POST https://kordiapp.com/mcp \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Legacy fallback (back-compat only — prefer the header above)
curl -X POST "https://kordiapp.com/mcp?token=<your-token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Step 3 — Configure your MCP client

Transport

Streamable HTTP

Server URL

https://kordiapp.com/mcp

Auth header

Authorization: Bearer <token>

Server name

kordi-mcp-server

Gemini CLI connects to kordi over Streamable HTTP with a Bearer token. Grab a token at /token, then either run the add command or edit your settings file.

One-line add

gemini mcp add --transport http \
  --header "Authorization: Bearer <your-token>" \
  kordi https://kordiapp.com/mcp

Or edit ~/.gemini/settings.json

{
  "mcpServers": {
    "kordi": {
      "httpUrl": "https://kordiapp.com/mcp",
      "headers": { "Authorization": "Bearer <your-token>" }
    }
  }
}

Then ask Gemini "What subscriptions am I paying for?" and it calls kordi_list_subscriptions. Use httpUrl (not url) — url is for SSE; kordi speaks Streamable HTTP. ChatGPT: its custom connectors use OAuth sign-in (no pasted-token option) — see the ChatGPT setup guide.

Every remote-MCP client connects the same way: point it at https://kordiapp.com/mcp with your token as an Authorization: Bearer header — kordi speaks Streamable HTTP. Only the config key differs per client. Step-by-step visual guides for each live on the connect page.

Gemini CLI httpUrlGuide →
Cursor urlGuide →
Windsurf serverUrlGuide →
VS Code (Copilot, Agent mode) servers · type:httpGuide →
Continue.dev type:streamable-httpGuide →
Zed context_serversGuide →

Claude and ChatGPT use OAuth sign-in instead of a pasted token — see Claude and ChatGPT.

Identity is resolved at the edge — the MCP Durable Object never sees an unauthenticated request. The edge strips any client-supplied identity headers and re-sets them from the server-resolved values.

Preferred

Authorization: Bearer <token>

Does not appear in request logs, Referer headers, or browser history.

Legacy (back-compat)

?token=<token>

Supported for clients that cannot set headers. Treat any query-param token as short-TTL.

OAuth 2.1 (Claude & ChatGPT)

Claude.ai and ChatGPT connect through kordi's OAuth front door instead of a pasted token — discovery at /.well-known/oauth-authorization-server, dynamic client registration, PKCE S256 required. Access tokens live ~1 hour and refresh automatically (rotating refresh tokens). See the Claude and ChatGPT guides.

Active sessions are re-validated against the token store every 5 minutes, so a revoked token stops working mid-session. Tokens from /token TTL 90 days; partner-provisioned tokens TTL 30 days. To revoke: regenerate your token on the /token page (the old one is invalidated immediately), or disconnect an OAuth client under "Connected AI Apps" on the same page.

31 tools across six areas. Every tool carries accurate annotations (readOnlyHint / destructiveHint) and user-friendly titles. One additional tool, kordi_get_analytics, is admin-gated and only appears in tools/list for allowlisted accounts.

Subscriptions — read

kordi_find_retention_offers read

Searches kordi’s observed save-desk history for the user’s active paid subscriptions and returns only services with a possible offer. Candidates are ranked by known fixed-dollar savings then monthly cost, with eligible after-tax monthly totals alongside the unchanged base amount, but are always unconfirmed — the agent asks which one to check first, works one at a time, and never promises or totals the offers.

kordi_list_subscriptions read

Paginated subscription list. Params: offset, limit, include_paused. Multi-currency aware: returns display_currency, totals_by_currency (exact per-currency totals), and an FX-converted total_monthly. For eligible US catalog prices it adds estimated_monthly_tax and estimated_monthly_total without changing stored/base amounts; unmatched charged totals remain untouched. Subscriptions with price: 0 and guest_access: true are bundled/guest access — intentional, not missing data. Sensitive card fields stripped. Truncates at 25k chars.

kordi_analyze_billing_health read

Price-hike detection, dormant service list, billing cluster analysis, and additive estimated-tax spend/savings totals. Returns health_score 0–100.

kordi_get_server_info read

Live server build metadata — version, git_sha, built_at. Runs server-side, so it always reflects the deployed version. Use it to detect a stale connection: if the reported version is newer than your cached tool schemas, reconnect to refetch tools/list.

kordi_list_proactive_tasks read

The agent worklist — time-sensitive actions: upcoming renewals (renewal_upcoming), trials about to convert (trial_ending), elapsed pauses (resume_due), and the user's locked rotation schedule advancing to a new month (rotation_due, with to_pause/to_resume service lists). Eligible items carry additive monthly and per-charge estimated tax/total fields. Sorted most-urgent first. Params: trial_horizon_days and renewal_horizon_days (each 1–30, default 7). The primary tool for scheduled / autonomous check-ins — pair with the kordi_scheduled_checkin prompt.

Subscriptions — actions

kordi_add_subscription write

Add a subscription the user names directly ("I pay $15.99/mo for Netflix"). Dedupes by name; re-call updates amount/bill date. Handles any cadence via billing_cyclemonthly (default), annual (amount is the full yearly price, bill_date the renewal date), or custom for weekly / every-N-weeks (billing_interval_days + billing_anchor_date). Optional trial_end_date.

kordi_import_subscriptions write

Bulk import a list in one call — the onboarding tool (e.g. after an email scan). One call writes the whole batch (stays within the per-session rate limit), dedupes by name, validates each item independently (bad rows return in failed, never block the rest), and logs a discover event per service. Set source to the connector name (gmail/superhuman/outlook). Email finds carry per-item evidence_type + evidence_date — kordi reconciles "started" vs "still active" server-side and returns uncertain items (dead trials, cancellations, stale charges) in needs_confirmation instead of writing them; re-submit user-confirmed ones with user_confirmed: true. Per-item billing_cycle (annual/custom) imports yearly receipts at the full yearly price, normalized in totals.

kordi_update_subscription write

Patch an existing subscription — price, bill date, trial end date, billing cadence, or the user's explicit tax_handling override (auto/pre_tax/included). A typed price resets catalog provenance so auto does not add tax to a charged total. Only provided fields change. service_id from kordi_list_subscriptions.

kordi_pause_subscription write

Mark a subscription paused in kordi's tracking. A plain pause does not stop provider billing — the response says so loudly: billing_status: "STILL_CHARGING" plus a next_action pointing at kordi_get_cancellation_route, alongside billing_note + cancellation_url. Optional resume_date (ISO) arms kordi's reactivation reminder; billing_stopped: true (only after an executed cancellation route or native pause) records a confirmed provider-side stop and returns billing_status: "STOPPED_CONFIRMED".

kordi_resume_subscription write

Mark a paused (or previously cancelled) subscription active again in kordi's tracking — also the write-back for kordi_get_resubscribe_route after a provider-side restart. service_id from kordi_list_subscriptions (with include_paused).

kordi_mark_cancelled write

Record a confirmed provider cancellation — sets state CLOSED and logs the churn outcome. Returns cadence-aware base savings plus eligible estimated tax/after-tax savings. Call only after the user confirms (or the agent completed) the cancel.

kordi_delete_subscription write

Permanently stop tracking a subscription. Cannot be undone — use pause for a temporary stop. service_id from kordi_list_subscriptions.

kordi_get_cancellation_route write-ish

Returns the execution runbook for cancelling/pausing at the provider — an agent_playbook with deep link, ordered steps, known retention traps, an on-page success indicator, and the exact write-back tool call. Curated playbooks for 33 services — every major streamer plus common SaaS/AI/lifestyle subs (Adobe, Audible, ChatGPT, Claude, iCloud+, gyms), with their contract-fee and credit-forfeiture traps. For anything else kordi checks a self-growing library (~50 more services, hand-audited or web-researched) then, on a genuine miss, researches the provider's current cancel flow live (Gemini + Google Search grounding) and saves the result — responses carry last_verified, and force_refresh re-researches + updates the library after UI drift — falling back to a generic runbook. A browser-capable agent can pass agent_can_browse: true to skip that paid research step on a miss and research the live flow itself instead (see kordi_contribute_playbook below to save it back for free). intent defaults to "pause" (kordi prioritizes rotation over churn — reserve "cancel" for an unambiguous "leaving for good"): pause wires write-back to kordi_pause_subscription and prefers native_pause where the provider has a real pause feature; cancel wires it to kordi_mark_cancelled. The playbook also carries retention_intel: any observed D1 offer becomes a clearly unconfirmed potential_offer, so the agent asks whether to check for it or continue cancelling before opening the provider flow; known fixed-dollar offers can be phrased as “up to $X.” It also carries broader crowd intel plus a negotiation script (see kordi_log_retention_offer). Records cancellation intent. service_id from kordi_list_subscriptions.

kordi_contribute_playbook write

Report back a runbook a browser-capable agent researched and successfully executed itself for a service kordi had no playbook for (agent_playbook.source was "generic"). Call only after confirming the outcome on-page. Saves permanently to kordi's shared library — a curated or existing library entry always wins, so this only ever fills a genuine gap; the next request for that service, from anyone, is then served free instead of researched again.

kordi_get_resubscribe_route write-ish

The mirror image of the cancellation route — the execution runbook for restarting a paused or cancelled service. Returns an agent_playbook with the restart deep link, ordered steps, restart-specific traps (lost promo pricing, reactivate-don't-recreate the account, app-store billing), an on-page success indicator, and a billing_check warning that restarting charges the current price immediately. Write-back is kordi_resume_subscription. Curated restart playbooks for the major streaming services and common SaaS subs, generic runbook otherwise. service_id from kordi_list_subscriptions (include_paused: true).

kordi_ingest_subscription write

Discovery-source ingest (Qira / Screenpipe / email connectors — set source to the connector name). Dedupes by normalized name (re-call updates amount/bill date). Optional: trial_end_date (ISO YYYY-MM-DD) lights up the proactive-trial loop; billing_cycle (annual/custom) for yearly or every-N-days receipts; currency (ISO 4217, when the receipt shows it — "€12" → "EUR"); card_network (visa | mastercard | amex | discover) feeds the card-network proof layer — only pass when explicitly known, never guessed. Email finds carry evidence_type + evidence_date — non-fresh evidence returns status: "needs_confirmation" (nothing written) until re-called with user_confirmed: true. All enrich-only on re-ingest. Returns a signed 7-day pause link.

Agent execution — telemetry & escalation

kordi_log_retention_offer write

Record a save-desk offer the provider made mid-cancellation ("wait — 50% off"), accepted or declined. Aggregated across users (non-PII: service + terms + outcome only) and fed back into kordi_get_cancellation_route's retention_intel so the next user knows what to ask for. If the user accepts a new price, also call kordi_update_subscription.

kordi_log_agent_telemetry write

Report UI drift, missing buttons, or broken flows hit while executing a playbook (service_id, step, error_type, error_message) — flags the playbook for engineering instead of failing silently.

kordi_request_human_handoff write

Escalate a blocker the agent can't solve — blocker_type ∈ captcha, 2fa, login_wall, biller_mismatch, other. kordi holds a short grace period before emailing (a user actively watching the agent usually clears the prompt themselves in seconds — the write-back tools mark it resolved and no email fires). If it's still unresolved after that, kordi emails the user with blocker-specific instructions and a provider deep link, so the handoff reaches them even when the agent runs in the background.

Savings intelligence — read

kordi_get_rotation_plan read

The hero tool. Crosses subscriptions × watchlist × real show air dates to recommend keep / pause-until / cancel per service, with unchanged base savings and eligible estimated tax/after-tax savings. Flags watchlist shows on services the user doesn't pay for.

kordi_get_savings_summary read

What kordi has saved the user: vault credits, harvestable pending, current paused-monthly, and a lifetime estimate from pause history. Components reported separately, never summed.

kordi_compare_plans read

Checks each active subscription against current market tiers (weekly price crawl) and surfaces cheaper tiers — e.g. ad-supported plans — with base and eligible after-tax savings plus the trade-off.

Shows & watchlist

kordi_search_shows read

Search TV/movies by name — kordi's DB first, then TVMaze fallback. Use to confirm an exact title before rating or adding.

kordi_rate_show write

Record a like/dislike to train the user's taste profile — the same signal powering recommendations. Optional genres, network. Idempotent: re-stating the same opinion is skipped; a flipped opinion records.

kordi_rate_shows write

Bulk rating — seed a whole taste profile in one call. Ask "what are your all-time favorites?" and pass everything named as shows[] (1–50, each defaults to liked). One rate-limit unit, one atomic batch write, one profile rebuild — use this instead of looping kordi_rate_show whenever the user lists more than one title.

kordi_recommend read

"What should I watch?" — the same ranked picks as kordi's Vibe/Discover feed (genre, vibe-vector, collaborative, trending, mood, gateway scoring). Params: limit (1–20, default 8), optional mood (escape / engage / unwind / feel). Thin or no taste profile falls back to trending with personalized: false — seed taste via kordi_rate_shows first.

kordi_add_to_watchlist write

Add one or more shows kordi should monitor (new seasons, air dates, streaming availability). Additive — re-adding is a no-op. Feeds the rotation plan.

kordi_list_watchlist read

List the shows kordi is monitoring for the user. No arguments.

kordi_remove_from_watchlist write

Stop tracking one or more shows (case-insensitive). Call only on explicit user request.

Platform

kordi_get_top_shows read

Cross-user show-popularity leaderboard. Aggregate only, no PII.

kordi_privacy_optout write

Opt the user out of AI taste profiling (Privacy Policy §11) — call only on a clear, explicit request. Ratings are still recorded as stated opinions, but no profile is derived and recommendations fall back to trending. Permanent, applies web + agent; confirmation emailed.

kordi_get_analytics read admin

Whole-DB aggregates: users, swipes, like-rate, discovery volume by source. Admin-gated — hidden from tools/list for non-admin accounts.

kordi://server-info read

Live server build metadata — version, git_sha, built_at — readable without a tool call (also available as the kordi_get_server_info tool). Because the handler runs server-side, it always reflects the deployed version; compare it against your cached tool schemas to detect a stale connection.

kordi://subscription-pulse read

Ambient billing snapshot for feeds and background polling. Pull this once per session instead of chaining multiple tool calls — it returns everything an agent needs to decide what to do next.

proactive_tasks — same worklist as the MCP tool
expiring_trials — real trial cards with trialEndDate
upcoming_bills — ordinary bills in next 7 days
next_bill — next chronological bill
price_alerts — detected streaming price changes
savings_potential — lowest active subscription cost
estimated_monthly_total — eligible spend including estimated tax
kordi://watchlist read

The user's current watchlist — the shows kordi is monitoring for new seasons, air dates, and streaming availability — readable without spending a tool call. Handy context to attach before recommendation or rotation-plan conversations.

For server-side integrations pushing subscriptions on behalf of users who may not have kordi accounts yet. A valid x-partner-key unlocks a 1,000/hr budget and source-labeled onboarding emails. An invalid key is a hard 401 — there's no silent fallback.

POST /api/guest-ingest

curl -X POST https://kordiapp.com/api/guest-ingest \
  -H "x-partner-key: <your-partner-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "source": "your-source",
    "subscriptions": [
      {
        "name": "Netflix",
        "amount": 15.99,
        "bill_date": 14,
        "trial_end_date": "2026-07-15",
        "card_network": "visa"
      }
    ]
  }'

Response — new account

"status": "created",
"imported": 1,
"total_monthly": 15.99,
"token": "clerk_reg_...",          // send as Authorization: Bearer <token>
"mcp_url": "https://kordiapp.com/mcp"

Response — existing account

"status": "existing",
"imported": 1,
"total_monthly": 15.99
// no token — anti-takeover invariant. subs are still imported.

Subscription fields

name string, required
amount number, required (monthly)
bill_date 1–31 or string, optional
trial_end_date YYYY-MM-DD, optional
currency ISO 4217 (e.g. EUR), optional
card_network visa|mastercard|amex|discover

trial_end_date activates proactive cancel reminders (3d + 1d email), dashboard badge, and surfaces the trial in kordi_list_proactive_tasks. card_network feeds the card-network attribution proof layer — pass it only when the source explicitly knows it (e.g. from a receipt), never guessed. currency is the code the service actually bills in — pass it when the receipt shows one; when absent, kordi falls back to the user's stored currency. All are enrich-only on re-ingest: an absent value never clears what's already stored.

Security invariants

  • Token is never returned for an email that already has an account.
  • A partner key only authenticates the source it's paired with — one partner's leaked key cannot impersonate another source.
  • tier is always server-set to free; it is never read from the request.
  • The user receives a source-labeled email to claim their account on kordiapp.com.
MCP tool calls per session 20 / 60s
Partner provisioning (with x-partner-key) 1,000 / hr per source
Guest-ingest without partner key 5 / hr per IP
List response character cap 25 000 chars (truncated: true)

Over the MCP rate limit, tools return { isError: true } with a back-off message. Page large account lists with offset / limit rather than relying on a single call.

Connection fails / "Could not connect" in Claude.ai

Add the connector with the exact URL https://kordiapp.com/mcp (no trailing slash, no ?token=). Claude.ai discovers the OAuth flow from the /.well-known/oauth-authorization-server endpoint and opens kordi's consent page — log in (or sign up) there, approve, and you'll be returned to Claude with kordi connected. If the window doesn't redirect back, disable pop-up blocking for claude.ai and retry.

401 / "Unauthorized" or "Session expired"

Your token was revoked or expired (OAuth access tokens live ~1 hour and refresh automatically; manual /token tokens TTL 90 days). Sessions are re-validated against KV every 5 minutes, so a revoked token stops working mid-session. Reconnect via the OAuth flow, or for direct API use mint a fresh token at /token. Prefer the Authorization: Bearer header — a stale or truncated ?token= query param is the most common cause.

Tools return empty / "getting_started" instead of data

The account has no subscriptions or watchlist yet — this is expected on a fresh account, not an error. Add a subscription ("I pay $15.99/mo for Netflix") and a show or two ("add Severance to my watchlist"), then re-run kordi_get_rotation_plan. The richer the watchlist, the better the savings advice.

"Rate limit reached" mid-conversation

Each session is capped at 20 tool calls / 60s to protect the backend from retry loops. Wait one minute and continue. For large accounts, page lists with offset / limit instead of repeatedly re-listing.

A pause didn't stop my charges

By design. A plain kordi_pause_subscription only updates kordi's tracking — the provider keeps billing until you actually cancel with them. Use kordi_get_cancellation_route for the executable runbook (deep link, steps, traps, success indicator), complete the cancellation in the user's session, then make the returned after_success write-back: kordi_mark_cancelled for a permanent cancel, or kordi_pause_subscription with billing_stopped + resume_date for a rotation pause. App-store subscriptions (Apple / Google) can only be cancelled in device subscription settings.

Guest-ingest returns no token for a known user

Intended anti-takeover behavior — a token is never returned for an email that already has a kordi account. The subscriptions are still imported and the account owner is emailed. See Partner Provisioning.

Still stuck?

Email hello@kordiapp.com with the tool name, the request, and the response you received.

Partner or integration inquiry

To get a partner key, discuss an integration, or report an issue with the API.

hello@kordiapp.com

Individual users

Connect kordi to Claude, Raycast, Cursor, or any MCP-compatible client. Claude users: see the visual setup guide.

Get Your Token →