Connect your AI assistant to Spendrein
Spendrein speaks Model Context Protocol over HTTP, so any MCP-compatible assistant — Claude, ChatGPT, Cursor, Claude Code, VS Code, Windsurf — can read your audits, look up vendor benchmarks, and (on paid plans) trigger cancellations and generate negotiation scripts directly from chat. No background process, no SDK, no custom code.
Read your audits
Fetch the latest audit, its subscription list, AI recommendations, and confidence scores.
Look up vendor benchmarks
Pull Spendrein's typical-spend range for any vendor — useful for 'am I overpaying' questions.
Trigger cancellations
Queue managed cancellation flows for specific subscriptions. Paid plans only.
Generate negotiation scripts
Produce a 3-talking-point script for a target price. Persisted to negotiation history. Paid plans only.
Quickstart
The fastest path from zero to a working chat-driven audit. Allow about five minutes end to end.
Issue a credential
Open Settings and find AI assistant integration. Click Issue credentialand copy the value — it's shown once. Spendrein only stores a SHA-256 hash, so the cleartext can't be re-displayed later.
Add Spendrein to your assistant
Add a new remote HTTP MCP server in your assistant's connector / MCP settings. Configuration is two values: an endpoint and a Bearer credential.
MCP_ENDPOINT=https://spendrein.com/api/mcp MCP_AUTH_HEADER="Authorization: Bearer spr_mcp_REPLACE_WITH_YOUR_CREDENTIAL"Verify the connection
Send a JSON-RPC
tools/listrequest — every MCP server must return its registered tools. A 200 response listing four tools means you're wired up.curl -s https://spendrein.com/api/mcp \ -H "Authorization: Bearer $SPR_MCP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' | jq '.result.tools[].name' # expected: # "audit.query" # "benchmark.lookup" # "subscription.cancel" # "subscription.negotiate"Ask your assistant a question
In your assistant chat, ask something only Spendrein could answer. The assistant will pick the right tool, request authorization, and call it.
Architecture
A request from your assistant takes a single hop to Spendrein. There's no proxy, no message broker, no background service — the assistant is the JSON-RPC client and /api/mcp is the JSON-RPC server.
┌────────────────┐ ┌──────────────────┐ ┌─────────────────────────┐
│ Your assistant │───▶│ Bearer-auth gate │───▶│ Tool dispatcher │
│ (Claude / GPT │ │ /api/mcp │ │ • audit.query │
│ Cursor / ...) │ │ │ │ • benchmark.lookup │
└────────────────┘ └──────────────────┘ │ • subscription.cancel* │
▲ │ │ • subscription.negotiate*│
│ ▼ └────────────┬────────────┘
│ ┌──────────────────┐ │
│ │ Rate limiter │ ▼
│ │ (Upstash Redis) │ ┌─────────────────────┐
│ └──────────────────┘ │ Supabase Postgres │
│ │ (RLS-scoped query) │
│ └────────────┬────────┘
│ │
│ ▼
│ ┌─────────────────────┐
└───────────────────────────────────────│ Audit log │
│ (tool_call_events) │
└─────────────────────┘
* Paid-plan onlyJSON-RPC 2.0 over HTTPS
Standard MCP transport. Methods we implement: initialize, tools/list, and tools/call. Notifications are accepted but produce no response.
Auth → rate-limit → dispatch → audit
Every request runs through the same pipeline: HTTPS check, Bearer credential lookup, atomic rate-limit decrement, tool dispatch, and a synchronous write to the tool-call audit log. Rejected calls still consume rate-limit budget.
Authentication
The endpoint accepts exactly one authentication mechanism: a Bearer credential in the Authorization header. URL-parameter tokens are explicitly rejected to keep credentials out of access logs and browser history.
POST /api/mcp HTTP/1.1
Host: spendrein.com
Authorization: Bearer spr_mcp_4Bx9Lq7nJ8fKpMnVgT2sRxYz0WqEvN3uPAaBcDeFgHiJ
Content-Type: application/json
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }PrefixRequiredspr_mcp_- Reserved namespace. Lets GitHub secret scanners and other tooling identify a leaked credential without inspecting any account-bound state.
BodyRequired43 chars · base64url- 256 bits of entropy from crypto.randomBytes(32). Treat the full string as a password equivalent — it can read your audit history and, on paid plans, trigger cancellations.
Header formRequiredAuthorization: Bearer …- RFC 6750. The Bearer keyword is case-insensitive; everything after the single space is the credential.
Tools
Spendrein exposes four tools. Two are read-only and available to every account; two are write actions reserved for paid plans. All inputs and outputs are validated against strict Zod schemas — extra properties are rejected.
Returns the most recent completed audit for the caller's workspace, or a specific audit by ID. Includes the parsed subscription list with vendor name, monthly cost, billing cycle, AI confidence, and recommendation rationale.
audit_idOptionaluuid- Specific audit to fetch. Omit to return the most recent completed audit in the workspace.
e.g. 9c9f4f7e-8b13-4f31-9aa8-2e0a1c8b2d3a
auditobject- id, created_at, total_spend, total_bleed, currency, subscription_count.
subscriptions[]object[]- Vendor name, category, monthly cost, billing cycle, status, AI confidence (0–100), and recommendation reasoning. Includes metered-spend metadata when the vendor is metered (charge_model = 'metered' or 'hybrid').
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "audit.query",
"arguments": {}
}
}Returns Spendrein's typical-spend range for a vendor — price_low, price_typical, price_high — so the assistant can tell the user whether they're paying above or below market.
vendor_nameRequiredstring- Free-form vendor name. Spendrein normalizes the input before lookup, so 'Notion', 'Notion Labs Inc.', and 'NOTION' all resolve to the same record.
e.g. Notion
vendorobject- normalized_name (the lookup key) + display_name (the verbatim input).
benchmarkobject | null- When data is on file: plan_tier, price_low, price_typical, price_high, company_size_range, last_refreshed_at. NULL when Spendrein has nothing for the vendor — the response is still HTTP 200, not an error.
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "benchmark.lookup",
"arguments": { "vendor_name": "Notion" }
}
}Queues Spendrein's managed cancellation flow for a specific subscription. The assistant must identify the subscription unambiguously — Spendrein never auto-substitutes similar vendor names.
subscription_idOptionaluuid- Preferred — guarantees an unambiguous match. Always available from a prior audit.query response.
vendor_nameOptionalstring- Fallback. Returns 404 with a candidates list when more than one subscription matches; the assistant must then re-call with subscription_id.
confirmation_phraseOptionalstring- Optional friction-layer for assistant frameworks that pass through user confirmation. Echoed back in the audit log when set; not validated.
cancellationobject- id, subscription_id, status ('queued' | 'already_queued'), queued_at, estimated_completion_at (~24h after queue time), confirmation_email_sent_to ('on_file').
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "subscription.cancel",
"arguments": {
"subscription_id": "8d2e..."
}
}
}Generates a negotiation script for a vendor — three talking points plus a complete script the user can read or send. Persists to the user's negotiation history so the assistant can re-fetch it later.
subscription_idRequireduuid- The subscription to generate a script for.
target_priceOptionalnumber- Monthly price the user wants to negotiate to. When omitted Spendrein returns 422 with no_target_price — call benchmark.lookup first to get a sensible target.
context_hintOptionalstring · max 500 chars- Optional extra context the assistant can pass through ("team is shrinking from 12 to 6", "competitor offer at $X"). Used verbatim in the generated script.
negotiationobject- id, subscription_id, vendor_name, current_cost, target_price, script_text, talking_points[3], generated_at.
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "subscription.negotiate",
"arguments": {
"subscription_id": "8d2e...",
"target_price": 12,
"context_hint": "Team shrinking from 12 to 6 seats."
}
}
}Client setup
Configuration snippets for the most common MCP clients. The values are universal: the endpoint is https://spendrein.com/api/mcp and the auth header is Authorization: Bearer spr_mcp_…. Everything else is client-specific JSON.
Server name: Spendrein
URL: https://spendrein.com/api/mcp
Auth: Bearer token
Token: spr_mcp_REPLACE_WITH_YOUR_CREDENTIAL
— No local install required. Spendrein runs as a remote server, not a
local Claude Desktop / ChatGPT-Desktop process.Example prompts
Three end-to-end conversations that exercise the toolset. Drop them into any connected assistant — the wording isn't load-bearing, but the verbs ("query", "benchmark", "cancel", "negotiate") help the assistant pick the right tool on the first try.
Find waste in the latest audit
audit.query“Pull my latest Spendrein audit. Group the subscriptions by AI recommendation (cancel, downgrade, keep) and show the monthly total per group.”
- Calls audit.query with no arguments → most recent completed audit.
- Buckets subscriptions by recommendation and sums monthly_cost per bucket.
- Renders a 3-row table client-side. No follow-up tool call needed.
Decide whether you're overpaying
audit.query → benchmark.lookup“Look up market benchmarks for my three most expensive subscriptions. Tell me which ones are above the typical-spend range.”
- Calls audit.query, sorts by monthly_cost desc, takes top 3.
- For each, calls benchmark.lookup with the vendor_name.
- Compares user_cost vs benchmark.price_high → flags anything above price_high as 'overpaying'.
Cancel + negotiate, end to end
audit.query → subscription.cancel + subscription.negotiatePaid plan“From my latest audit, cancel anything you'd recommend cancelling. For the ones flagged as 'downgrade', generate negotiation scripts targeting 30% lower.”
- audit.query → filter to ai_recommendation = 'cancel' or 'downgrade'.
- For each cancel-flagged sub, call subscription.cancel with subscription_id.
- For each downgrade-flagged sub, compute target_price = monthly_cost × 0.7 and call subscription.negotiate.
- Returns: a list of queued cancellations + a set of generated negotiation scripts the user can review.
Security
Credential storage
Spendrein stores a SHA-256 hash of the credential — never the cleartext. The original is shown once at issuance and never again. Database compromise does not yield usable credentials.
Audit log of every call
Every tools/call is persisted to tool_call_events (tool name, outcome, rejection reason if any, duration, remaining rate-limit budget). Visible in Settings; CSV export carries a SHA-256 checksum line.
Payload privacy
Tool responses never include raw bank statements, transaction descriptions, payment instruments, customer email addresses, Stripe IDs, or workspace IDs. Each tool has a strict output schema; extra fields are rejected at the response boundary.
Instant revocation
Revoke from Settings. The next request — whether already in flight or freshly issued — receives HTTP 401 with invalid_credential. Revocation is hash-level, so re-issuing produces a fresh credential with no shared state.
Rate limits
Limits are enforced per credential, per tool class, on a rolling minute window via Upstash Redis. Rejected calls still decrement the budget — that closes the "trigger errors to starve the limiter" loophole.
Read tools60 / minute / credential- Applies to
audit.queryandbenchmark.lookup. Generous enough for interactive chat; tight enough to block scrape loops. Write tools10 / minute / credential- Applies to
subscription.cancelandsubscription.negotiate. Write actions can't reasonably need more — every one represents a real user decision. Credential lifecycle5 / hour / account- Issuance + revocation actions on the credential itself. Stops accidental loops in Settings UIs from tripping the security mailer.
Error reference
Errors return JSON-RPC envelopes with a stable error discriminator and a human-readable message. Tool-specific extras (the candidates list on subscription_not_found_by_name, the retry_after_seconds field on rate-limit rejections) are documented inline.
invalid_credentialHTTP 401- Bearer header missing, malformed, or hash didn't match an active credential. Re-issue the credential and update the client config.
invalid_inputHTTP 400- Input failed Zod validation (missing required field, extra property, wrong type). The message names the offending field.
tier_gateHTTP 403- Account is on the free tier and the call hit a paid-only tool (subscription.cancel or subscription.negotiate). Upgrade to unblock.
rate_limitHTTP 429 · Retry-After- Per-credential per-tool-class budget exhausted. The Retry-After header carries the wait time in seconds.
audit_not_foundHTTP 404- audit.query was passed an audit_id that doesn't exist or doesn't belong to the caller's workspace.
subscription_not_foundHTTP 404- subscription.cancel / subscription.negotiate received a subscription_id that doesn't exist or doesn't belong to the caller's workspace.
subscription_not_found_by_nameHTTP 404 · candidates[]- subscription.cancel was given a vendor_name with multiple matches. The response includes a candidates array of {subscription_id, vendor_name}; re-call with the chosen subscription_id.
no_target_priceHTTP 422- subscription.negotiate needs a target_price. Call benchmark.lookup first and pass benchmark.price_low or .price_typical.
tool_errorHTTP 500- Unexpected internal error. Spendrein captures these with full traces — the assistant should surface a friendly message and offer to retry.
Spendrein owns the integration end-to-end. If a client is misbehaving, your assistant can't see the tools, or a response shape isn't what you expected, send a ticket — we answer same-business-day.