Troxy is the proxy you trust — a policy enforcement layer that sits between your AI agents and your payment methods, making sure agents only spend what you actually authorized.
The Cloudflare Analogy
The clearest way to explain Troxy is through an analogy that resonates immediately with both technical and non-technical audiences:
Cloudflare doesn't replace the internet. It sits in front of your website and makes sure only the right traffic gets through. It filters, it enforces, it logs — invisibly, in the background.
Troxy doesn't replace your bank or your cards. It sits in front of your payments and makes sure your agents only spend what you actually authorized. Infrastructure you trust, invisibly working in the background.
The Core Insight
AI agents are being given access to real money with no guard rails. The problem isn't the AI companies — it's that there's no layer between the agent and the payment. Troxy is that layer.
Agent has direct access to your payment method. No limits, no checks, no audit trail. Agent hallucinates, gets prompt-injected, or loops — your money is gone.
Every payment goes through your rules first. Budget envelopes, merchant filters, approval flows. Full audit trail. You stay in control without lifting a finger.
What Troxy Is NOT
This is important to be clear about — especially when talking to investors or enterprise customers:
- Not a payment processor — Troxy never touches card numbers or moves money
- Not a bank or fintech — no financial license required
- Not trying to compete with Visa, Stripe, or PayPal — we sit on top of them
- Not an AI company — we enforce policy, we don't replace the agent
Troxy is a software policy engine. This keeps us out of financial regulation and lets us move fast. The moment we touch money directly, everything changes. We never do that.
An agent misreads a task and places 200 identical orders. Or loops on a procurement workflow draining your account overnight. No hard stop exists.
A malicious website embeds hidden instructions. Your agent reads it, and is hijacked to redirect payments or exfiltrate card data. OWASP's #1 LLM threat in 2025.
Your AI spent $4,800 last month. On what? Which agent? Authorized by whom? Nobody knows. Finance teams are flying blind on agentic spend.
Real Scenarios That Motivated This
Scenario 1 — The Over-Spending Agent
User tells Claude: "Book me a hotel in Amsterdam for next week, budget $200." Agent finds a hotel at $220 — just 10% over. Without Troxy, it charges the card. With Troxy: agent hits the budget envelope limit, user gets a push notification: "Your travel agent wants to charge $220 to Marriott — $20 over your budget. Approve?" User taps yes. Done.
Scenario 2 — Wrong Card for Wrong Purpose
Gilad has a business Amex and a personal Visa. He asks his agent to buy groceries. Without Troxy, the agent uses whichever card is configured — probably the wrong one. With Troxy: the card alias "Gilad's Groceries" (Visa ···1234) is mapped to the grocery category. Agent automatically uses the right card. No confusion, clean expense reports.
Scenario 3 — Romi's Fruits (Multi-User)
Romi uses a separate agent to buy fruits. Gilad wants to give her a $50/month budget, on the Mastercard ending in 5678, for groceries only. Troxy makes this trivial: create alias "Romi's Fruits", assign Mastercard ···5678, set $50 envelope, restrict to grocery merchants, assign to Romi Agent. That's it. Every purchase Romi's agent makes goes through this policy.
Troxy intercepts every payment tool call an agent makes, evaluates it against the user's policies, and either passes it through, blocks it, or escalates it to the human for approval. The agent never knows Troxy is in the middle.
The Budget Envelope Feature
One of the most powerful and intuitive features. Troxy lets you treat any credit card like a smart prepaid debit card — without changing anything about the actual card.
# Your rule, in plain English:
Card ···1234 → $200 budget for travel agent this month
Card ···5678 → $50/week for shopping, groceries only
Card ···9012 → blocked from all agents until manually unlocked
# Agent tries to exceed budget:
Agent: "charge $240 to card ···1234"
Troxy: envelope check → $200 limit → BLOCK
Troxy: sends push notification to user
User: approves or declines within 10 min
# No response → auto-decline
The credit card behind it does whatever it does. Troxy doesn't care. It's a policy gate that enforces the envelope before the real charge ever fires. The user experiences it as a debit card. The bank sees a normal credit card charge.
Why Troxy
The name went through an extensive search process. Key constraints: must work in English and Hebrew, easy to pronounce globally, available as a .ai domain, and the meaning must be embedded in the name itself.
The two core concepts of the entire platform compressed into one five-letter word. Trust — what users need to feel about their money. Proxy — what Troxy technically is, sitting between the agent and the payment. Short, punchy, easy to say in any language, and immediately legible to anyone in the developer or fintech world.
troxy.ai — confirmed available as of March 2026. Register immediately before squatters pick it up.
"The proxy you trust." — covers both halves of the name and the core value proposition in four words.
Brand Principles
- Professional but not boring — infrastructure credibility without the enterprise stiffness
- Developer-first aesthetics — clean, functional, no unnecessary decoration
- Trust signals everywhere — the product is literally about trust, the brand must reflect it
- Color: teal (#0a9e88) — stands out from the sea of blue fintech brands
The Difference Between Chat and Agents
You type → Claude responds → done. No action in the world. Output is text only.
Claude is given tools — functions it can actually call. It can search the web, send emails, book flights, charge cards. Real actions, real consequences.
How Agents Call Tools — MCP
MCP (Model Context Protocol) is the standard Anthropic created for how AI agents discover and call tools. Think of it like USB — before USB, every device had a different connector. MCP is USB for AI tools.
Any service that wants to be usable by agents publishes an MCP server. When an agent is configured to use Stripe's MCP server, it can call create_payment() directly. No human confirmation needed.
# Agent config (Claude Desktop, Cursor, etc.)
{
"mcpServers": {
"stripe": { "url": "https://mcp.stripe.com", "apiKey": "sk_live_..." },
"gmail": { "url": "https://gmail.mcp.google.com", "token": "ya29..." }
}
}
Four Categories of Agents
| Category | Examples | Troxy relevance |
|---|---|---|
| Consumer agents | Claude, ChatGPT, Perplexity, Devin, AutoGPT | Free tier users, individual consumers |
| Developer frameworks | LangChain, CrewAI, AutoGen, LlamaIndex | Developers building their own agents |
| Internal company agents | Procurement bots, finance automation, HR agents | Biggest enterprise opportunity — run 24/7 with no human watching |
| Custom Claude agents | Anyone with Claude Projects or API access | Zero barrier to create — anyone is a potential user |
Internal company agents are the most dangerous and the most underserved. They run 24/7, nobody is watching, and they have real budget access. This is the strongest enterprise pitch.
Anyone Can Create an Agent Today
This is important to understand — the barrier to creating an agent is essentially zero. Opening Claude and typing this creates an agent:
# This is all it takes to create an agent:
"You are a travel booking agent. When I ask you to book travel,
use the Stripe MCP to pay, the Gmail MCP to send confirmations,
and Google Calendar MCP to add it to my calendar."
# Claude will now call real tools, with real consequences,
# every time the user asks it to book something.
Troxy intercepts any purchase made through an MCP payment tool, regardless of which AI agent makes it or which payment provider receives it. We're betting that MCP becomes the standard protocol for AI agent commerce. That's it.
What this means in practice
| Agent | Payment MCP | Troxy works? |
|---|---|---|
| Claude Desktop | Stripe MCP | ✅ Yes |
| Cursor | Stripe MCP | ✅ Yes |
| Custom GPT-4 agent | Stripe MCP | ✅ Yes |
| OpenAI agent | PayPal MCP | ✅ Yes |
| Any agent | Shopify MCP | ✅ Yes |
| Browser agent clicking "Buy" | No MCP — direct browser | ❌ Not in scope |
| Agent calling Stripe REST API directly | No MCP — raw API | ❌ Not in scope |
The payment provider must have an MCP server. Today that means primarily Stripe — which is why MVP targets Stripe. PayPal, Shopify, and Square MCP servers are coming but not all production-ready yet. We add them as they mature. This is not a permanent limitation — it's a timing one.
Why Troxy is agent-agnostic
The daemon intercepts at the MCP config layer — not at the agent layer. It rewrites the config file that tells the agent where to find payment tools. The agent reads the config, calls what it thinks is Stripe, and hits Troxy instead. The agent never knows.
# The agent's MCP config (after troxy init): { "mcpServers": { "stripe": { "url": "http://localhost:4242/stripe" ← Troxy daemon } } } # The agent thinks this IS Stripe. # It doesn't matter if the agent is Claude, GPT-4, Gemini, # a LangChain script, or anything else. # Whatever calls this URL — Troxy catches it.
The pitch framing
"Troxy works with any AI agent that uses MCP for payments. We're not betting on Claude winning or GPT-4 winning. We're betting on MCP winning as the standard protocol — which Anthropic created, OpenAI supports, and every major agent framework is adopting. As MCP adoption grows, every new agent automatically becomes a potential Troxy user. We don't chase agents. Agents come to us."
What's out of scope — and the future path
| Scenario | Why out of scope now | Future path |
|---|---|---|
| Browser agents clicking "Buy" | Different architecture entirely — needs browser extension, not MCP proxy | Chrome extension (in More Ideas) |
| Agents calling payment APIs directly | No MCP layer to intercept — would need SDK-level integration | Payment provider partnership (V4) |
| PayPal / Shopify / Square | MCP servers not fully production-ready yet | Add as each provider's MCP server matures — same daemon code, new entry in payment_mcps.go |
The Config Rewrite
When Troxy installs, it rewrites the MCP config files so all payment tool calls go to Troxy first instead of directly to the payment provider:
# Before Troxy install:
{ "stripe": { "url": "https://mcp.stripe.com" } }
# After npx troxy init:
{ "stripe": { "url": "http://localhost:4242/stripe" } }
# Agent thinks it's talking to Stripe.
# It's actually talking to Troxy on localhost.
# Troxy decides pass/block, then forwards if approved.
Full Transaction Flow
1 Agent calls stripe.create_payment(amount=299, merchant="Ahrefs")
2 Request hits Troxy daemon on localhost:4242
3 Troxy enriches with context: which agent, which user, which session
4 Policy engine evaluates: amount vs envelope, merchant vs allowlist, card routing
5a ALLOW → forwards to real Stripe MCP → charge happens → logged
5b BLOCK → request dies → user notified with reason → logged
5c ESCALATE → request held → push notification sent → user has 10 min to approve
The agent has zero awareness that Troxy is in the middle. It calls what it thinks is Stripe. This means zero integration effort for the end user — just point the config at Troxy and everything works.
The Daemon Process
Troxy runs as a background process on the user's machine — exactly like Dropbox, antivirus software, or Cloudflare WARP. It starts automatically on login, runs silently, and does its job without the user thinking about it.
# CLI commands:
troxy daemon start # start (auto on boot)
troxy daemon stop # stop
troxy daemon status # check if running
troxy logs # tail local logs
troxy policy list # view active rules
troxy update # update to latest version
Card Aliases — The Real Number Stays Hidden
Users never see card numbers in Troxy. They create aliases that map to real cards stored with their payment provider:
| Alias | Real card (never shown) | Purpose |
|---|---|---|
| Gilad's Groceries | Visa ···1234 | Personal grocery shopping |
| Romi's Fruits | Mastercard ···5678 | Romi's grocery agent, $50/mo limit |
| Work Expenses | Amex ···9012 | SaaS, travel, office supplies only |
If the user gets a new card, they update the alias once. All policies attached to the alias continue working unchanged.
IF/AND/THEN rule system. Set rules per agent, per card alias, per merchant category. Amount limits, time windows, merchant allowlists. No code required — pure UI.
Virtual spending limits per card alias. Monthly, weekly, or per-task budgets. When envelope runs out, agent is blocked. Resets automatically on schedule.
Multiple cards, intelligent routing. Business purchases go to Amex, groceries go to Visa, Romi's purchases go to her card. Automatic, based on policy rules.
Anything exceeding thresholds gets paused. Push notification, email, or Slack. User has configurable window (5min–1hr) to approve or block. No response = auto-decline.
Every transaction, every policy decision, every block — logged with full context. Which agent, which task, which card, which policy triggered. Exportable for accounting.
Malicious websites trying to hijack agent payments hit the policy wall first. Even if the agent is compromised, the policy layer enforces the rules regardless.
The "Notify Before Declining" Feature
This is one of the most important and differentiating features. Instead of a hard block, users can set a policy that says: "if the expense is greater than X% over budget, notify me before auto-declining."
How it works
Policy: "If amount exceeds budget by more than 10% — notify me, wait 10 minutes for approval, then auto-decline."
Agent tries to charge $220 against a $200 envelope (10% over):
- Troxy intercepts — amount exceeds envelope by exactly 10%
- Policy match: "notify before declining"
- Push notification sent: "Travel agent wants $220 for Marriott — $20 over budget. Approve?"
- User has 10 minutes to tap Approve or Block
- No response → auto-decline, agent gets a rejection
This turns Troxy from a rigid blocker into a smart assistant. The agent doesn't just get rejected — the human gets a chance to override for legitimate one-off cases.
Future Features (Roadmap)
| Feature | Description | Priority |
|---|---|---|
| Time-based rules | No spending after midnight, weekend limits | V2 |
| Velocity rules | Max 3 transactions per hour per agent | V2 |
| Multi-currency envelopes | Budget in USD but card charges in EUR | V3 |
| Family/team plans | Give Romi her own login with limited permissions | V2 |
| Anomaly detection | ML-based: this doesn't look like this agent's normal behavior | V3 |
| LLM intent verification | "Does this charge semantically match the original task?" | V3 |
| Monthly spend reports | Auto-emailed PDF with all agent spending | V2 |
| Payment provider integrations | Stripe/PayPal require Troxy token — cryptographic guarantee | V4 |
The dashboard is Cloudflare-style — clean, information-dense, immediately actionable. Every screen answers one question in under 3 seconds.
| Page | Question it answers | Key elements |
|---|---|---|
| Overview | Is everything okay right now? | Spend summary, recent activity, pending approvals, blocked count |
| Cards | How much budget do I have left? | Card aliases, budget progress bars, agent assignments |
| Policies | What are my rules? | IF/AND/THEN rule list, color-coded by type (allow/notify/block) |
| Agents | What can each agent do? | Registered agents, permissions, spend history per agent |
| Activity | What happened and why? | Full audit log, filter by agent/card/date, receipt per transaction |
Approval Notification
When a transaction needs approval, the user gets a push notification/email with one-tap actions. No need to open the dashboard:
Tech Stack for Dashboard
| Component | Technology | Why |
|---|---|---|
| Framework | Next.js | AWS CloudFront + S3. Static export. All data fetching client-side. Fully managed by Terraform. |
| Hosting | S3 + CloudFront | Already in AWS, Terraform managed, GitHub Actions deploys on push, free at MVP scale |
| Auth | Auth0 or Cognito | OAuth, Google login, token management |
| Real-time | WebSocket | Live approval queue, transaction feed |
| Domain | dashboard.troxy.ai | Clean subdomain, DNS already on Cloudflare, CloudFront handles SSL automatically |
The policy engine is the brain of Troxy. Every transaction is evaluated against a user's rules before being passed, blocked, or escalated.
Rule Structure
Every policy is an IF/AND/THEN statement. No code required — pure UI builder:
IF agent is [Romi Agent] AND card is [Romi's Fruits] AND category is [Groceries] AND amount is [under $50] THEN [Allow automatically]
Policy Types
| Type | Color | Behavior | Example |
|---|---|---|---|
| Allow | Green | Auto-approve, no notification | Romi groceries under $50 |
| Notify | Amber | Send push/email before proceeding or declining | Any charge over $200 |
| Block | Red | Hard stop, log reason, notify user | Electronics category always blocked |
Available Conditions
- Agent — specific agent, or any agent
- Card alias — specific card or any card
- Merchant category (MCC) — groceries, electronics, travel, SaaS, etc.
- Amount — under/over absolute value, or exceeds budget by X%
- Time of day / day of week — (V2) no spending after midnight
- Velocity — (V2) max N transactions per time period
Policy Evaluation Order
Policies are evaluated top-to-bottom. First match wins. This is the same model as firewall rules — familiar to any developer or IT person.
Start with deterministic JSON-based rules — no AI in the loop for V1. Sub-50ms evaluation is critical. Add LLM semantic checking later (V3) only for escalation decisions, not the happy path.
Five Core Components
1. MCP Server (the entry point)
TypeScript/Node.js service that exposes the same tools as the payment MCP it wraps. Runs on localhost:4242. Agent calls it thinking it's talking to Stripe. Troxy receives, enriches with context, evaluates, then forwards or blocks.
2. Policy Engine (the brain)
Pure evaluation service. Receives a transaction request, returns ALLOW, BLOCK, or ESCALATE. V1: deterministic JSON rules, sub-50ms. V3: LLM semantic intent check for escalations. Tech: Lambda + DynamoDB for rule storage.
3. Budget Envelope Service
Stateful real-time tracking of spending against budgets. Must be fast and consistent — no race conditions. Tech: Redis (ElastiCache) for atomic real-time state. DynamoDB as persistent source of truth. EventBridge for scheduled resets.
4. Notification + Approval Flow
When ESCALATE: transaction held, user notified via SNS (push/email/Slack), signed approval token generated. User taps approve → webhook releases transaction. Configurable timeout: no response = auto-decline. WebSocket for real-time dashboard updates.
5. Audit + Event Store
Every event logged — transactions, evaluations, escalations, approvals, blocks. Full context: agent, task, card, policy that triggered. Tech: Postgres RDS for structured queries + S3 for raw event archival. EventBridge as the event bus.
AWS Infrastructure
Route53 (DNS)
↓
CloudFront (CDN + DDoS)
↓
API Gateway
↓
Lambda — MCP Server
Lambda — Policy Engine DynamoDB (policies, rules)
Lambda — Approval Handler → ElastiCache Redis (envelopes)
Lambda — Audit Logger RDS Postgres (audit trail)
↓ S3 (event archive)
EventBridge (event bus)
SNS (push notifications)
SES (email)
URL Structure
| Subdomain | Purpose | Hosting |
|---|---|---|
troxy.ai | Marketing site | S3 + CloudFront |
dashboard.troxy.ai | User console | S3 + CloudFront |
install.troxy.ai | Install shell script | S3 + CloudFront |
releases.troxy.ai | Daemon binaries by version | S3 + CloudFront |
api.troxy.ai | Backend REST API | AWS Lambda + API Gateway |
status.troxy.ai | Uptime status page | Statuspage.io |
Early Stage Cost Estimate
| Service | Monthly cost |
|---|---|
| S3 + CloudFront (binaries + install script) | ~$5 |
| Lambda (scales to zero) | ~$0 early stage |
| RDS Postgres (t3.micro) | ~$20 |
| ElastiCache Redis (t3.micro) | ~$15 |
| S3 + CloudFront (frontend) | ~$0 (free at MVP scale) |
| Total early stage | ~$40–50/month |
A user installs Troxy in 2 minutes, connects their agent, sets a few rules, and from that moment — every payment their agent tries to make goes through Troxy first. They see it all in the dashboard. They approve or block from their email. That's the whole product. Simple, complete, demonstrable.
What a user can do with the MVP — the complete list
| Feature | What the user actually does | In MVP |
|---|---|---|
| Install | Run npx troxy init, paste API token, done. Daemon starts and auto-connects to Claude Desktop / Cursor. No config beyond the token. |
✅ Yes |
| Add a card alias | Give a card a nickname ("Gilad's Groceries"), enter last 4 digits, set an optional monthly budget. The real card number is never stored — Troxy only needs the alias to track spending and apply policies. | ✅ Yes |
| Create a policy rule | Pick a condition (category = electronics, amount > $200, etc.) and an action (ALLOW / BLOCK / ESCALATE). Give it a priority. Rules evaluate top-to-bottom, first match wins. User can have 10+ rules. | ✅ Yes |
| Set a monthly budget | Attach a monthly spending limit to any card alias. When the agent tries to spend beyond it, Troxy automatically escalates instead of allowing. Budget resets on the 1st of each month. | ✅ Yes |
| See the activity log | Dashboard shows every transaction attempt — allowed, blocked, or escalated — with the merchant name, amount, which policy fired, and the timestamp. Immutable, append-only. Exportable later. | ✅ Yes |
| Approve or decline from email | When a transaction is escalated, user gets an email with the merchant, amount, and reason. Two buttons: Approve or Decline. One click. Expires in 10 minutes. If expired, request is automatically declined. | ✅ Yes |
| See budget usage | Dashboard overview shows each card alias with a progress bar: how much of the monthly budget has been used and how much remains. | ✅ Yes |
| Revoke a token | If a machine is lost or compromised, user revokes the token from the dashboard. The daemon on that machine stops processing all calls immediately. | ✅ Yes |
| Register an agent | Give each agent a name (e.g. "Shopping Agent", "Travel Agent"). Policies can target a specific agent or apply to all agents. For MVP: one agent per user is fine. | ✅ Yes |
The 5-minute user journey — from install to first blocked payment
Minute 0: Run: npx troxy init → Binary downloads, daemon starts → Browser opens dashboard.troxy.ai/connect Minute 1: Sign up with email → Create API token ("My MacBook") → Paste token in terminal → "✅ Connected" Minute 2: Add a card alias → Name: "Main Card", Last 4: 1234, Budget: $500/mo → Click save Minute 3: Create a policy → "Block Electronics" — category: electronics → BLOCK → Priority 10, save Minute 4: Open Claude Desktop → Ask: "buy me AirPods Pro for $249" → Claude gets blocked ❌ → Dashboard shows the block with reason Minute 5: Ask: "book a hotel for $350" (budget $500) → Escalated — email arrives in seconds → Click Approve ✅ → Dashboard shows approved → Troxy is working. The agent is under control.
What is NOT in the MVP — and why
| Feature | Why it waits | When |
|---|---|---|
| Insights / analytics tab | Needs real spending history to be useful. With 10 test users we have no data. Build after launch when users ask for it. | V2 |
| Claude API intent verification | Deterministic rules cover 95% of cases. LLM intent check is a nice-to-have, not a blocker for the demo. Adds cost and latency before we need it. | V2 |
| Smart card routing | Auto-selecting the right card based on merchant category. Cool feature, not core to the value prop. Manual alias selection in policies is enough for MVP. | V2 |
| Slack / push notifications | Email is sufficient for MVP approval flow. Slack adds integration complexity. Add when users ask for it. | V2 |
| Multi-user / teams | MVP is single user. Team features (shared policies, multiple approvers, role-based access) require a lot of UX and auth work. Post-funding. | V3 |
| Mobile app | Email approval flow works fine for MVP. Native app is post-funding, post-PMF. | V3 |
| Subscription / billing | MVP is free. We're not charging anyone yet — we're proving the product works. Add paid plans after first 100 real users. | V2 |
| Multiple MCP servers | MVP intercepts Stripe MCP only. Adding PayPal, Shopify, etc. is straightforward but not needed to prove the concept. Start with Stripe — most developer agents use it. | V2 |
| Anomaly detection | Needs a baseline of real spending data to detect anomalies. Not possible at MVP with 10 users. Build when we have real transaction history. | V3 |
Dashboard pages — MVP only
The dashboard has 5 pages in MVP. Insights is included but shows only simple data-driven metrics — no AI, no Claude API. That's V2.
| Page | What it shows | In MVP |
|---|---|---|
| Overview | Budget bars per card alias (used vs remaining), daemon status (online/offline), last 5 transactions at a glance | ✅ Full |
| Policies | List of all policy rules ordered by priority, create/edit/delete/reorder. Toggle enable/disable per rule. | ✅ Full |
| Activity | Full transaction log — every ALLOW, BLOCK, ESCALATE with merchant, amount, policy matched, timestamp. Filterable by decision type. | ✅ Full |
| Cards | Card aliases with their budgets and current usage. Add/edit/delete aliases. Budget reset date visible. | ✅ Full |
| Insights | Simple data-driven metrics from the audit_log — no Claude API needed. See below for exact scope. | ✅ V1 (SQL only) |
Insights V1 — what's in MVP (pure SQL, no AI)
Everything below is a simple query on the audit_log table. No Claude API, no ML, no external services. One afternoon of work. Shows investors real data without overbuilding.
| Metric | How it's built |
|---|---|
| Total spent this month | SUM(amount) WHERE decision='ALLOW' AND month=current |
| Decisions breakdown | COUNT(*) GROUP BY decision — pie chart: X allowed, Y blocked, Z escalated |
| Top 5 merchants by spend | GROUP BY merchant_name ORDER BY SUM(amount) DESC LIMIT 5 |
| Spend by category | GROUP BY merchant_category — bar chart per category |
| Budget health per card | Progress bar: used vs limit — same data as Overview but more detail |
| Spend over time | GROUP BY date_trunc('day', created_at) — simple line chart, last 30 days |
Insights V2 — post-MVP technical implementation
Build when we have 50+ active users with real spending history. The technical approach is simple — one Lambda, one Claude API call, results cached in RDS. No new infrastructure needed beyond what MVP already has.
A Lambda queries the user's audit_log, formats the data as context, sends it to the Claude API, gets back a plain-language analysis, caches it in RDS, and the dashboard displays it. That's it.
The flow
# User clicks "Generate Insights" in dashboard 1. Dashboard calls: POST /dashboard/insights/generate 2. insights-generator Lambda runs: a. Query audit_log — last 30 days of transactions for this user b. Aggregate: totals, categories, merchants, trends c. Build Claude API prompt (see below) d. Call Claude API → claude-sonnet-4 e. Parse response → structured JSON f. Cache result in RDS (insights_cache table, expires 24h) g. Return to dashboard 3. Dashboard displays the insights # On subsequent loads within 24h → serve from RDS cache # No Claude API call = no cost # User can force-refresh: DELETE /dashboard/insights/cache
The Claude API prompt — exactly what we send
// System prompt You are a financial insights assistant for Troxy, an AI agent payment control layer. Analyze the user's agent spending data and return a JSON object with insights. Be concise, specific, and actionable. Never invent data not in the input. Respond ONLY with valid JSON — no preamble, no markdown. // User prompt (built dynamically from audit_log data) Here is the user's agent spending data for the last 30 days: Total spent: $1,247.50 Total blocked: $340.00 (12 transactions) Total escalated: $490.00 (5 transactions, 4 approved, 1 declined) Transactions by category: - grocery: $342.00 across 8 transactions (merchants: Rami Levy x5, Shufersal x3) - travel: $480.00 across 3 transactions (merchants: Marriott x2, Booking x1) - software: $199.00 across 1 transaction (merchants: Ahrefs x1) Blocked transactions: - electronics: $249.00 (AirPods Pro) — blocked by "Block Electronics" policy - electronics: $91.00 (phone case) — blocked by "Block Electronics" policy Recurring charges (same merchant, multiple months): - Ahrefs: $199.00 in March, $199.00 in February, $199.00 in January Return this JSON structure: { "summary": "2-3 sentence plain-language overview of this month's spending", "highlights": ["up to 3 short bullet points — notable patterns or facts"], "duplicates": [ { "merchant": "name", "amount": 199.00, "months": 3, "note": "recurring monthly" } ], "anomalies": [ { "description": "what's unusual", "severity": "low|medium|high" } ], "suggestions": [ { "text": "actionable suggestion", "potential_saving": 50.00 } ] }
Example Claude API response
{
"summary": "Your agents spent $1,247 this month, with travel being the largest category at $480. Troxy blocked $340 in electronics purchases and escalated 5 transactions for your approval, of which you approved 4.",
"highlights": [
"Travel spending is up — 3 hotel charges totaling $480 in 30 days",
"Ahrefs subscription recurring at $199/mo for 3 consecutive months",
"All blocked transactions were electronics — your policy is working"
],
"duplicates": [
{
"merchant": "Ahrefs",
"amount": 199.00,
"months": 3,
"note": "Recurring monthly subscription — consider reviewing if still needed"
}
],
"anomalies": [
{
"description": "Travel spend this month ($480) is 3x higher than the previous 2-month average ($160)",
"severity": "medium"
}
],
"suggestions": [
{
"text": "You have no spending limit on the travel category. Consider adding a monthly travel budget to avoid surprise escalations.",
"potential_saving": null
},
{
"text": "Ahrefs has been charged 3 months in a row. Verify this subscription is still actively used.",
"potential_saving": 199.00
}
]
}
New database table — insights_cache
-- Add to existing schema when building V2 insights_cache ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), period_days INTEGER NOT NULL, -- 30 | 90 result JSONB NOT NULL, -- full Claude API response tokens_used INTEGER, -- track API cost created_at TIMESTAMPTZ DEFAULT now(), expires_at TIMESTAMPTZ NOT NULL -- now() + 24h )
New API endpoints for V2
# Generate (or serve from cache) POST /dashboard/insights/generate?period=30d → Returns insights JSON (from cache if fresh, from Claude if stale) → Cost: ~$0.01–0.05 per call (Sonnet 4 pricing at ~2K tokens input) # Force refresh — bypass cache POST /dashboard/insights/generate?period=30d&refresh=true # AWS service: same core-handler Lambda, new route # No new Lambda needed — just a new function in core-handler # Secrets Manager: add troxy/anthropic/api_key
New secret needed
| Secret name | What it contains | Used by |
|---|---|---|
troxy/anthropic/api_key | Anthropic API key for Claude calls | core-handler Lambda (Insights V2 route only) |
Cost per user per month
| Scenario | API calls/mo | Cost per user |
|---|---|---|
| User generates insights once/week | ~4 calls (cache handles the rest) | ~$0.10–0.20/mo |
| User generates daily | ~30 calls | ~$0.60–1.50/mo |
| 100 active users, weekly usage | ~400 calls total | ~$15–20/mo total — completely manageable |
Without caching, every dashboard page load would call Claude API — that would be expensive and slow. With the 24-hour cache, we call Claude at most once per user per day. Most users check insights a few times a week. Real cost at 100 users: under $20/month total. Covered comfortably by any paid plan.
What's needed to build V2 — checklist
| Task | Effort |
|---|---|
Add insights_cache table to RDS (migration) | 30 min |
Add troxy/anthropic/api_key to Secrets Manager | 10 min |
| Add Insights V2 route to core-handler Lambda | 1 day |
| Update Insights page in dashboard to show AI content | 1 day |
| Apply for Anthropic for Startups credits | 10 min — do before building |
| Total | ~2–3 days of work |
The "agent" clarification — important
The word "agent" in this document always refers to the external AI that the user is already running — Claude Desktop, Cursor, a custom script, etc. Troxy is the proxy layer that intercepts calls from that external agent. We don't deploy an agent. We don't build an agent. The user brings their own agent — we just sit in the middle and control what it can spend.
User's external agent (Claude Desktop, Cursor, custom)
↓ makes MCP payment tool call
Troxy daemon (localhost:4242) ← we build this
↓ evaluates against policies
Troxy API (api.troxy.ai) ← we build this
↓ ALLOW → forwards to real MCP server
Real payment provider (Stripe MCP)
↓
Actual payment
# The only place Claude API enters Troxy is Insights V2
# — a Lambda calling Claude to generate plain-language summaries.
# That's just an API call. Not an agent. Not something to deploy.
# Leave it for post-MVP.
Yes — because it proves the hardest thing: Troxy is genuinely in the middle of the agent's payment calls. The agent tries to spend, Troxy intercepts, decides, and either allows, blocks, or escalates. The dashboard shows real data. The Insights tab shows real metrics. Investors don't fund feature lists — they fund proof that the core mechanism works and that someone will pay for it. This is enough.
The One-Line Install
The install IS the acquisition. One command, everything configured, dashboard live. This is the moment someone decides to use Troxy.
npx troxy init
What Happens During Install
✓ Detecting installed agent tools (Claude Desktop, Cursor...)
✓ Found Claude Desktop config at ~/Library/Application Support/Claude/
✓ Found Cursor config at ~/.cursor/mcp.json
✓ Backing up original configs
✓ Injecting Troxy proxy for: stripe, paypal, shopify
✓ Starting Troxy daemon on localhost:4242
→ Opening browser → dashboard.troxy.ai to finish setup
✓ Done. All agent payments now route through Troxy.
Install Per Platform
| Platform | Command | Background service | Priority |
|---|---|---|---|
| Mac | brew install troxy or curl | sh | launchd plist | First |
| Docker | docker run troxy/daemon | Container restart policy | First |
| Linux | curl | sh or apt/yum | systemd service | Second |
| Windows | winget install troxy | Windows Service | Third |
| npx (universal) | npx troxy init | Session only | First (try) |
Docker — The Enterprise Play
Companies running internal agents don't run them on someone's MacBook. They run them on EC2, Kubernetes, or GitHub Actions. Docker is their natural answer.
# docker-compose.yml
version: '3'
services:
troxy:
image: troxy/daemon:latest
environment:
- TROXY_TOKEN=${TROXY_TOKEN}
- TROXY_POLICY=strict
ports:
- "4242:4242"
restart: always
my-agent:
image: my-company/procurement-agent
environment:
- MCP_STRIPE_URL=http://troxy:4242/stripe
depends_on:
- troxy
API Tokens
The daemon requires an API token to function. No token = daemon doesn't start. This is the gate between a free install and an account.
Signup flow
- User runs
npx troxy init - Terminal prompts: create account or paste token
- Browser opens to
troxy.ai/signup— 30 seconds, Google OAuth - Token generated:
txy-xk29dj8f... - User pastes back in terminal
- Daemon starts, machine appears as "Online" in dashboard
- User asks Claude to do something with money — first transaction appears
- Aha moment.
Multiple Tokens
One account, many tokens — one per machine, environment, or use case. Each token can be revoked individually without affecting others. Model: identical to GitHub personal access tokens or AWS access keys.
Why Go for the Daemon Binary
The daemon should be written in Go. Single reason: compiles to one static binary per OS with zero dependencies. No runtime required.
GOOS=darwin GOARCH=amd64 go build -o troxy-mac
GOOS=darwin GOARCH=arm64 go build -o troxy-mac-m1
GOOS=linux GOARCH=amd64 go build -o troxy-linux
GOOS=windows GOARCH=amd64 go build -o troxy.exe
The Distribution Chain
User runs: npx troxy init
↓
Hits: install.troxy.ai (shell script on S3)
↓
Script detects OS → downloads binary from:
releases.troxy.ai/v1.0.2/troxy-mac
↓
Daemon starts → connects to:
api.troxy.ai
↓
Fetches policies, logs traffic, sends notifications
Three Servers, Three Jobs
50-line bash script hosted on S3 + CloudFront. Detects OS, downloads right binary, sets up background service. Globally cached, costs pennies. Same pattern as Homebrew, Rust, ngrok installers.
Versioned directory of compiled daemon binaries on S3. Every release publishes all platform variants. Also consider GitHub Releases early on — free, built-in versioning, zero ops.
The real backend. AWS Lambda + API Gateway + RDS + Redis. This is where policies live, events are logged, approvals are managed. Full infra as Terraform — you know this pattern from Rapyd.
Daemon ↔ Cloud Communication
Policies are cached locally on the machine for fast evaluation. No network round trip on every transaction:
- On startup: daemon calls
api.troxy.ai/v1/policies→ fetches user's rules → caches locally - Every transaction: evaluated against local cache — no network needed
- After decision: event logged to
api.troxy.ai/v1/eventsasynchronously - Cache refreshes every few minutes from cloud
This means Troxy keeps working even if the internet is slow or the API is temporarily down. Local cache + async logging = no single point of failure for the core enforcement function.
What Troxy Sees vs. What It Never Sees
- Transaction amount
- Merchant name
- Merchant category
- Which agent made the request
- Timestamp
- Task context
- Full card number
- CVV
- Card expiry
- Bank account details
- User passwords
- Raw MCP payloads
"Troxy never processes, stores, or transmits card numbers or payment credentials. We are a decision layer, not a payment processor. Card credentials go directly between your payment provider and the merchant."
Five Security Pillars
1. Never touch card data (architectural)
Troxy routes the decision, not the money. The actual payment credentials never enter Troxy's systems. Get this verified by a security auditor and put it on the trust page explicitly.
2. Encryption everywhere
At rest: AWS KMS encrypts RDS, S3, DynamoDB. API tokens stored as bcrypt hashes only. In transit: TLS 1.3 minimum. Daemon ↔ API uses mTLS (mutual TLS) — both sides verify certificates. HSTS on all web properties. Certificate pinning in daemon binary.
3. Data minimization
Only log what's needed: amount, merchant, category, agent ID, decision, policy applied. Never log full MCP payloads or card details. 90-day retention default, then deleted. Enterprise can configure longer.
4. Local-first evaluation
Policy decisions happen on the user's machine against a local cache. For 99% of transactions, nothing leaves the machine except an async audit log. This is both a security and performance win.
5. Token security
Never log tokens. Store only bcrypt hashes. Tokens expire and are rotatable from dashboard. One-click revoke per machine. Rate limit all API endpoints. Anomaly detection: token used from new country/IP → alert user immediately.
Compliance Roadmap
| Certification | Why it matters | When |
|---|---|---|
| SOC 2 Type II | Enterprise sales requirement — without this, Fortune 500 cannot sign | As soon as first paying customers |
| GDPR | European users — DPA template, right to deletion, EU data residency | Day 1 — write the policy |
| PCI DSS scope letter | QSA confirms Troxy is out of scope — not a card processor | Before first enterprise deal |
| ISO 27001 | Enterprise nice-to-have | After SOC 2 |
The Trust Page
troxy.ai/security — publish this from day one. It does more for enterprise sales than any pitch deck slide. Should contain: architecture diagram showing no card data, encryption standards, SOC 2 report when available, pen test results, bug bounty program, incident response SLA, uptime status link, contact: [email protected].
This is the most critical technical question: how do we guarantee agents can't bypass Troxy? A config file rewrite is a gentleman's agreement — not a technical guarantee. Here are the three enforcement levels:
| Level | Method | Guarantee | When to build |
|---|---|---|---|
| 1 | MCP config rewrite | Low — well-behaved agents only | V1 (already described) |
| 2 | DNS interception + firewall rules | High — blocks direct endpoint access | V1 — build this |
| 3 | Full TUN/VPN interface | Very high — all traffic | V2 enterprise |
| 4 | Cryptographic payment provider tokens | Absolute | V4 — requires partnerships |
Level 2 — DNS Interception (Recommended for V1)
Troxy runs a local DNS resolver that intercepts requests to payment domains:
# Agent resolves: mcp.stripe.com
# Normal DNS: → 54.187.x.x (Stripe's real IP)
# Troxy DNS: → 127.0.0.1 (localhost, Troxy)
# Agent thinks it's talking to Stripe.
# It's actually talking to Troxy.
# Troxy forwards if policy allows.
Level 2 — Firewall Rules
# Mac pf firewall — blocks direct access to payment endpoints
block out proto tcp to mcp.stripe.com port 443
block out proto tcp to mcp.paypal.com port 443
# Only allow through Troxy
pass out proto tcp from localhost:4242 to mcp.stripe.com port 443
Show a "Security Status" panel in the dashboard: which enforcement layers are active, and a counter for "bypass attempts blocked." The moment a user sees their agent tried to go around Troxy and was caught — they will never uninstall it.
TAM / SAM / SOM
| Level | Definition | Size |
|---|---|---|
| TAM | All agentic commerce payment infrastructure | ~$10B by 2030 |
| SAM | Developer & SMB agent payment tooling | ~$1.5B by 2028 |
| SOM | MCP-native auth layer, Year 1-3 | ~$150M |
Competitive Landscape
| Competitor | What they do | The gap |
|---|---|---|
| Visa Intelligent Commerce | Network identity + tokenization for enterprise partners | No developer self-serve, months to onboard |
| Mastercard Agent Pay | Agentic tokens for large issuers | Same gap as Visa |
| Ramp Agent Cards | Corporate spend management with agent cards | Enterprise only, no task context |
| Stripe Issuing | Card infrastructure for merchants | Merchant-facing, not agent-facing |
| CardForAgent.com | Instant prepaid card via MCP | No policy layer, no task context, no audit |
| Troxy | Task-aware policy enforcement, envelopes, routing, audit | The gap nobody fills |
Four Customer Segments
1. The Business Owner
Given AI assistant access to company cards. Needs budget control and visibility without manual oversight. Buyer: founder or CFO of 10–200 person company.
2. The Developer Building Agent Products
Building a product where users need agents that can transact. Can't hand users a raw credit card. Needs Troxy's MCP as drop-in safety layer. Buyer: developer, integrates via SDK.
3. The Finance Team at Scaling Company
Teams deploying internal agents 24/7. Procurement bots, travel agents, vendor payment automation. Need audit trail and policy enforcement. Buyer: CFO or CISO.
4. Individual with Peace of Mind
Uses Claude or ChatGPT for daily tasks. Wants to feel safe giving agent access to cards. Free tier user, possible bottom-up enterprise motion. Buyer: consumer.
Revenue Streams
Tiered subscription for SMBs. Includes advanced policy rules, multi-agent support, approval workflows, audit exports.
White-labeled authorization engine. Revenue share + flat licensing. SOC 2, dedicated support, SLA.
500 transactions/month. Bottom-up acquisition — developers bring Troxy to their companies.
Unit Economics (SMB)
| Metric | Value |
|---|---|
| SMB ARPU | $180/month |
| Average agent spend managed | $3,200/month |
| Gross margin | >75% |
| Target 18-month: paying SMBs | 300 |
| Target 18-month: ARR | ~$650K |
Pricing Tiers — Freemium Model (Freemius-style)
Free forever for 1 person, 1 machine, 1 card. Pay to scale. Bottom-up: individual dev uses free → brings it to their company → company upgrades.
| Feature | Free | Pro ($29/mo) | Enterprise |
|---|---|---|---|
| MCP connections (API keys) | 1 | Unlimited | Unlimited |
| Cards | 1 | Unlimited | Unlimited |
| Policies | 3 | Unlimited | Unlimited |
| Transactions/mo | 500 | Unlimited | Unlimited |
| Team members (users) | 1 | — | Unlimited |
| Named MCP connections | — | ✓ (distinguish agents) | ✓ |
| Approval flow | ✓ | ✓ | ✓ |
| Audit export | — | ✓ | ✓ |
| SSO / SAML | — | — | ✓ |
| SLA + dedicated support | — | — | ✓ |
Technical Readiness for Freemium
| Capability | Status | Notes |
|---|---|---|
| Multiple named API keys (MCPs) | Done | Already in DB + dashboard. Free limit = 1, enforce at creation. |
| Multiple cards | Done | Already works. Free limit = 1, enforce at creation. |
| Multiple policies | Done | Already works. Free limit = 3 suggested. |
| plan field on users (free/pro) | Easy | One DB migration + check at resource creation endpoints. |
| Limit enforcement at API | Easy | Count rows before INSERT in cards.py / tokens.py, return 403 if over limit. |
| Upgrade flow / Stripe | Medium | Stripe Checkout + webhook to flip user.plan. ~1 week. |
| Team / multi-user accounts | Hard | Requires organizations table, org_members, re-scoping all resources from user_id → org_id. Design needed before building. |
Phase 1 — Developer Land Grab (Months 1–6)
- Ship MCP server + API, free tier: 500 transactions/month
- Post on Hacker News (Show HN), ProductHunt, r/ClaudeAI, r/LangChain
- Write tutorials: "How to add spending controls to your Claude agent in 5 minutes"
- Integrate with Claude Desktop, Cursor, Windsurf
- Target: 2,000 active developers
Phase 2 — SMB Conversion (Months 6–18)
- Paid policy layer ($49–$499/month)
- Multi-agent support, approval workflows, finance team dashboard
- Bottom-up motion: developer uses free tier → brings to company → company pays
- Target: 300 paying SMBs
Phase 3 — Platform Partnerships (Year 2–3)
- Partner with AI platforms — be the default payment safety layer
- White-label enterprise deals
- Visa/Mastercard ecosystem program — cryptographic token integration
- Target: 3 platform deals
Validation Approach — Before Writing Code
| Day | Action | Owner |
|---|---|---|
| 1–2 | Build landing page, clear problem/solution statement, waitlist CTA | Both |
| 3 | Post on HN, relevant communities | Both |
| 3–4 | Yuval sends 20 messages to HBS network — "how are you handling agent payment risk?" | Yuval |
| 4–5 | Gilad has informal conversations at Rapyd — would they buy this? | Gilad |
| 5–7 | 5–10 customer discovery calls with people who respond | Both |
"I already tried to solve this myself" — strongest signal. "Who do I pay and when can I get it" — even stronger. "I know five other people with this problem" — means you're not in a niche of one.
Last updated: April 2026. Focus: 100% test before going live, then enforce plan limits.
Pre-Launch: Test Everything
| Item | Status | Notes |
|---|---|---|
| ALLOW flow end-to-end | Done | Transactions show in dashboard, budget_used updates |
| BLOCK flow | To test | Create a block policy, trigger via MCP, verify 0 budget impact |
| NOTIFY flow | To test | Policy action exists, SNS wiring needs verification |
| ESCALATE flow | To test | Blocked on SES production access for approval email |
| MCP green dot | Done | Heartbeat → Lambda → dashboard polls every 30s |
| Dashboard overview | Done | Stats, recent activity, budget envelopes all loading |
| Responsive dashboard | Done | Hamburger menu on mobile |
| CLI init/login/mcp flow | Done | Tested on EC2. Version 0.1.9 on npm. |
| Automate npm publish | To do | Add GitHub Action with NPM_TOKEN secret — no more manual OTP |
| SES production access | To do | Submit AWS request. Blocker for real user signups + ESCALATE emails |
| Budget monthly reset | Bug | budget_reset_day column exists but no cron job zeros out budget_used. Cards accumulate forever. |
Pre-Monetization: Plan Enforcement
| Item | Effort | Notes |
|---|---|---|
| Add plan field to users (free/pro) | Easy | One DB migration |
| Enforce 1 card / 1 API key on free tier | Easy | Count rows before INSERT, return 403 with upgrade message |
| Show upgrade prompt in dashboard | Easy | Banner/modal when hitting free tier limits |
| Stripe checkout + webhook | Medium | ~1 week. Webhook flips user.plan to pro. |
| Team / multi-user accounts | Hard | Requires org model refactor. Enterprise tier only — defer until needed. |
Known Bugs
| Bug | Impact | Fix |
|---|---|---|
| Budget never resets monthly | High — cards show wrong spend forever | EventBridge cron → Lambda → reset budget_used WHERE budget_reset_day matches day |
| Lambda migration lock_timeout was session-level | Fixed Apr 2026 | Changed to SET LOCAL — reverts on commit |
| pg_advisory_lock deadlock on cold start | Fixed Apr 2026 | Removed advisory lock entirely |
Gilad
Role: CTO / Technical Co-founder
- DevSecOps / Network Engineer at Rapyd (payment company)
- AWS CloudWAN, multi-account architecture, security infrastructure
- Terraform, Terragrunt, ControlMonkey — IaC daily
- Lives the buyer problem — would evaluate and buy this tool
- Rapyd as potential first reference customer
Yuval Kadmon
Role: CEO / Business Co-founder
- MBA Candidate, Harvard Business School
- McKinsey & Company — enterprise financial workflows
- Lieutenant, Israeli Navy — 7.5 years
- HBS network for fundraising and enterprise distribution
- Built automated Python pipelines — understands agentic tooling
Yuval brings enterprise GTM and fundraising credibility. Gilad brings technical depth and the "I lived this problem at a payment company" founder story. Together: the suit in the room + the engineer who'd actually build and use the product. That combination is rare and valuable.
Equity Structure Note
50/50 agreed. Recommendation: consider 51/49 with clear domain ownership to avoid deadlocks under pressure — Yuval owns business/GTM decisions, Gilad owns product/technical decisions. Have this conversation before lawyers and cap tables.
Seed Round Target
| Use of funds | Allocation |
|---|---|
| Engineering (MCP server, policy engine) | 40% |
| BaaS integration + infrastructure | 30% |
| Developer growth + community | 20% |
| Legal, compliance, operations | 10% |
| Total target | $2.5M |
One step at a time. One Claude Code task at a time. Test passes → next step. Test fails → fix before moving on. Never skip ahead.
How to use this with Claude Code
# For each step, give Claude Code exactly this: "Build [step name]. Here is the spec: [paste relevant sections] Rules: - Everything in Terraform + Terragrunt - No manual AWS console steps - Follow the spec exactly, nothing extra - Use us-east-1" # Never give Claude Code the whole document at once # Never ask it to build more than one step at a time # Always review the output before committing
Phase 1 — Infrastructure
Goal: AWS is fully provisioned by Terraform. CI/CD pipeline live. No manual AWS console clicks.
| # | Step | Spec sections | ✅ Pass condition |
|---|---|---|---|
| 1 | OIDC GitHub → AWS trust | Section 17, 18 | Push to troxy-tf-live → GitHub Actions runs → authenticates with AWS → no errors |
| 2 | S3 + CloudFront (dashboard hosting) | Dashboard Deploy section | curl https://dashboard.troxy.io → returns something, even a blank page |
| 3 | RDS PostgreSQL | Section 19 | Connect from local via SSO credentials → psql works → can create tables |
| 4 | Secrets Manager | Section 29 — Error Handling | aws secretsmanager get-secret-value --secret-id troxy/rds/password → returns value |
| 5 | SES domain verification | Section 18 | SES console shows troxy.io as Verified → send test email → lands in inbox not spam |
Phase 2 — Backend
Goal: /evaluate endpoint works end to end. ALLOW and BLOCK paths fully functional.
| # | Step | Spec sections | ✅ Pass condition |
|---|---|---|---|
| 6 | Database schema + migrations | Section 19 | psql → \dt → shows all 8 tables |
| 7 | core-handler Lambda (ALLOW + BLOCK) | Section 25, 27, 28 | POST /evaluate with valid token → returns ALLOW → audit_log row created in RDS. Same with BLOCK policy → returns BLOCK. |
| 8 | API Gateway | Section 25 | curl https://api.troxy.io/health → returns {"status":"ok"} |
Phase 3 — Approval Flow
Goal: Full ESCALATE path works. Email arrives, approve/decline links work, RDS updated.
| # | Step | Spec sections | ✅ Pass condition |
|---|---|---|---|
| 9 | approval-webhook Lambda | Section 25, 28, 29 | POST /evaluate → returns ESCALATE → email arrives in inbox → click Approve → pending_approvals updated in RDS → click Decline → same |
| 10 | Full backend validation | Section 22 | Run all validation checklist items from Section 22 — all pass → backend is done |
Phase 4 — Dashboard
Goal: Real UI connected to real data. No hardcoded mock data anywhere.
| # | Step | Spec sections | ✅ Pass condition |
|---|---|---|---|
| 11 | Auth flow (magic link) | Section 30 — Auth & SAML | Go to dashboard.troxy.io → enter email → magic link email arrives → click → logged in → JWT in browser |
| 12 | Dashboard 5 pages | Section 11, 34 | Each page loads real data from api.troxy.io — Overview, Policies, Activity, Cards, Insights V1 |
| 13 | GitHub Actions deploy pipeline | Dashboard Deploy section | Push to main → GitHub Actions runs → dashboard.troxy.io updates in under 2 minutes |
Phase 5 — Daemon
Goal: Claude Desktop intercepted. Agent payments flow through Troxy.
| # | Step | Spec sections | ✅ Pass condition |
|---|---|---|---|
| 14 | Go daemon — install + start | Section 26, 32 | npx troxy init → installs binary → troxy daemon status → running → curl localhost:4242/health → ok |
| 15 | MCP config rewrite + interception | Section 27, 33 | Open Claude Desktop → ask agent to make a payment → Troxy intercepts → decision appears in Activity tab 🎉 |
Phase 6 — Demo Ready
This is what you show investors. Run through it exactly as written. If any step fails — fix it before showing anyone.
Step 1: npx troxy init → installs, daemon starts, Claude Desktop connected ✅ Step 2: Open dashboard.troxy.io → loads clean, real data, no errors ✅ Step 3: Add card alias + $500 monthly budget → saves to RDS, appears in Cards tab ✅ Step 4: Create "Block Electronics" policy → saves to RDS, appears in Policies tab ✅ Step 5: Open Claude Desktop, connect agent → daemon shows machine as Online in dashboard ✅ Step 6: Ask agent: "buy AirPods Pro for $249" → Troxy intercepts → BLOCK → Activity tab shows block with reason ✅ Step 7: Ask agent: "book a hotel for $350" → Troxy intercepts → ESCALATE → Approval email arrives in inbox within 10 seconds ✅ Step 8: Click Approve in email → payment goes through → Activity tab shows ESCALATE → Approved ✅ Step 9: Ask agent: "buy groceries for $30" → Troxy intercepts → ALLOW → Activity tab shows ALLOW ✅ All 9 pass → you're ready for investors. 🚀
What Gilad builds, what Yuval does — in parallel
- All 15 build steps above
- Go daemon + MCP interception
- Review every Claude Code output before committing
- Keep the internal doc updated as decisions change
- 20 customer discovery calls
- Waitlist page live on troxy.io from day 1
- AWS Activate + Anthropic for Startups credits
- Stripe Atlas — legal entity
- Investor list + warm intro outreach
- First 5 investor meetings booked by demo day
Things We Talked About — To Build
The envelope feature we discussed — user loads a virtual budget onto any credit card. Agent can only spend up to that amount. When it runs out, blocked. Feels like a prepaid card psychologically, credit card behind the hood. This was one of the original vision ideas and it's a headline feature.
If agent tries to spend more than X% over the envelope, send push notification before auto-declining. User has a configurable window to approve. This is the feature that turns Troxy from a rigid blocker into a smart assistant. Already in V1 plan — build this first.
Business Amex for work expenses, personal Visa for groceries, Romi's Mastercard for her agent only. Troxy automatically routes to the right card based on merchant category, agent identity, and user-defined rules. No more wrong card on the expense report.
Give Romi her own Troxy login with limited permissions. She can see her agent's activity, her card's budget, but nothing else. Parent/guardian controls their children's AI agent spending. This is actually a massive consumer opportunity nobody has touched.
Product Features — Near Term
Every approved transaction generates a structured receipt — merchant, amount, agent, task context, policy that approved it, timestamp. Exportable. Accountants will love this. Finance teams will demand it.
Auto-generated email on the 1st of every month. "Your agents spent $X last month. Here's what they bought, which agent spent what, and what was blocked." Clean summary, no login required to read it.
Max 3 transactions per hour. Max $500 in any 24-hour window. Catches agents that loop or hallucinate and try to place the same order 200 times. This is the hallucination risk defense layer.
No agent spending between midnight and 6am. Weekend spending requires approval. Holiday lockdown mode. Useful for companies that want to ensure no autonomous spending happens outside business hours.
Whitelist specific merchants your agent can always use. Blacklist others it can never touch. "My travel agent can always book Marriott and Delta. It can never buy from Amazon." Simple, powerful, enterprise-ready.
When agent starts a specific task, Troxy spins up a virtual one-time card scoped to that task, merchant, and budget. Task ends — card dies. Ultimate isolation. Requires BaaS partnership (Lithic) but the UX is magical.
Security Ideas
Show users every time an agent tried to go around Troxy directly and was caught. "3 bypass attempts blocked this week." The moment a user sees this — they never uninstall Troxy. This is the single most powerful trust-building UI element we could ship.
Troxy learns what "normal" looks like for each agent. Travel agent usually books hotels $100-300. If it suddenly tries to charge $4,000 to an unknown merchant — that's anomalous. Flag it regardless of whether it technically passes the policy rules. Behavioral baseline per agent.
When an agent tries to make a payment that doesn't match the session's original task context — flag it. "You asked your agent to book flights. It's trying to buy gift cards. That's suspicious." The LLM intent verification layer we discussed for V3.
One big red button in the dashboard. Press it — all agents are immediately blocked from all spending. Every card frozen in Troxy. For when something goes very wrong and the user needs to stop everything instantly. Think of it as a nuclear stop-loss.
Agent tries to pay a merchant in a country it has never transacted in before — pause and notify. Same logic banks use for fraud detection, applied to agent behavior. "Your shopping agent just tried to buy something from a merchant in Nigeria. Approve?"
Enterprise Ideas
A dedicated finance view — not the technical dashboard, a clean executive summary. Total agent spend this quarter, broken down by department, by agent, by merchant category. Compare to last quarter. Forecast. Export to Excel. The CFO never has to touch Troxy's tech UI — they just get this clean report.
Pre-built policy sets for common use cases. "E-commerce company starter pack." "SaaS company policy set." "Family safety bundle." One-click install. Companies share and publish their policy templates. Could become a community ecosystem like Terraform modules or GitHub Actions.
AI companies building agent products don't want to build payment safety themselves. They embed Troxy white-labeled — their users get the safety layer, the AI company gets the credibility, Troxy gets revenue share on every transaction. This is the platform play that could 10x the business.
One-click export of all agent transactions in audit-ready format. Every transaction with: what was purchased, which agent, which employee authorized the agent, which policy approved it, timestamp, receipt. Turns Troxy's audit trail into a compliance product. Big companies will pay a lot for this.
Wild / Long-Term Bets
A public score for agent products. "This agent has a Troxy Score of 94 — it has processed 50,000 transactions with a 0.02% anomaly rate." Like a credit score, but for AI agents. Developers display it as a trust badge. Users check it before giving an agent their card. Becomes the standard signal of agent payment safety. This could be bigger than the product itself.
Right now MCP is young and nobody owns "the safe payment tool for agents." If Troxy ships first, gets developer adoption, and becomes the default — developers will write Troxy into their agent code from day one. Switching cost becomes enormous. Troxy schema becomes the lingua franca of agent payments. This is the Microsoft Office / Excel moat applied to agent tooling.
The future isn't just humans giving agents money. It's agents paying other agents for services. An orchestrator agent hires a specialized research agent, pays it per task. Troxy sits between them — enforcing policies even in agent-to-agent transactions. This is a 2027+ problem but positioning Troxy early here could be enormous. We'd be the trust layer for the entire agentic economy.
"Pay the contractor only if the work is delivered and verified." Agent hires a freelancer, payment is held in escrow by Troxy, released only when another agent or the human confirms delivery. Smart contract logic without blockchain complexity. Could completely reshape how agents handle service payments.
Agents sign up for things on your behalf. SaaS trials, newsletters, memberships. Troxy tracks every recurring charge an agent created, surfaces them in a "subscriptions" tab, and lets you cancel with one click. "Your agents have signed you up for 7 recurring charges totalling $340/month. Here they are." Subscription management that didn't exist before agents existed.
Troxy watches your agent's spending patterns for 30 days and suggests policies automatically. "We noticed your travel agent always books economy class under $500. Want us to create a policy that blocks anything over $600 for flights?" The system teaches itself your preferences and turns them into rules. Policies write themselves.
Instead of connecting a real card, users fund a Troxy wallet with a specific amount. Agents spend from the wallet only. When it's empty — done. Like a prepaid gift card for your AI. Consumer-friendly, low-risk, no card details ever involved. This could be the on-ramp for users too scared to connect a real card.
A live geographic map on the dashboard showing where your agents are spending. Dots appearing in real time — Tel Aviv, London, New York. Beautiful, visceral, immediately communicates scale and reach. Also a security feature — if dots appear in unexpected countries, something is wrong. Purely a delight feature but the kind people screenshot and share.
A public API that lets developers write custom policy logic beyond the UI. "If the merchant's website contains the word 'crypto', block." "If the total spend across all agents today exceeds $1,000, lock everything down." Programmable policies for power users. Turns Troxy into a platform, not just a product.
Wild but: every purchase your agent makes has a carbon footprint estimate. Flight bookings, physical goods, digital services. Troxy shows total estimated carbon generated by your agents this month. Companies with sustainability goals would pay for this. Nobody in the agent space is even thinking about this yet.
Distribution Ideas
For users who don't want to install a daemon. Extension watches for MCP payment calls in the browser, intercepts them, shows a quick approve/block popup before the charge fires. Lightweight, zero install friction, works on any OS. Perfect consumer on-ramp and a great ProductHunt launch.
The approval notification is powerful on mobile. Agent needs approval — phone buzzes — user sees the charge — one tap to approve or block. The app is essentially a smart notification center for everything your agents are trying to do. Simple, beautiful, one purpose. Could go viral just from the UX alone.
Developers who integrate Troxy get a badge they can put on their product page. "Troxy-verified: all payments enforced." Like the SSL padlock but for agent safety. Creates social proof, drives developer adoption, makes Troxy a brand signal not just a tool.
Opt-in anonymous feed of interesting Troxy blocks. "A travel agent tried to book 14 identical flights in 3 seconds — blocked." "A shopping agent was hijacked via prompt injection and tried to send $2,000 to an unknown merchant — blocked." Real stories, anonymized, building the case for why this matters. Think Cloudflare's radar reports but for agent payments.
Every idea above points at the same thing: Troxy becomes the trust infrastructure for the entire agentic economy. Not just "a tool that blocks bad charges" — but the layer that makes humans comfortable giving AI agents real autonomy, because they know Troxy is watching. The more agents exist, the more valuable Troxy becomes. That's the compounding flywheel.
Every architectural decision at MVP stage is justified by one question: does this help us demo to investors in 4 weeks? If no — it waits. Investors want to see the product work, not admire the infrastructure. We show them we're cost-aware by proving we can do more with less.
Languages — same for both MVP and Prod
| Component | Language | Why |
|---|---|---|
| Daemon (local process) | Go |
Single binary, zero dependencies, cross-compiles to Mac/Linux/Windows in one command. Same approach as ngrok, Terraform, Cloudflare WARP. Users run npx troxy init and get a native binary — no Node.js runtime required. |
| Backend (Lambda) | TypeScript (Node.js) |
MCP ecosystem is JS-first. Anthropic's SDK is TypeScript. Fast to iterate on. Lambda means zero idle cost — we only pay per invocation, not per hour. At MVP scale with 10 test users, this costs effectively $0. |
| Dashboard | Next.js (TypeScript) |
Next.js static export — no SSR. All data fetching client-side. Deployed via GitHub Actions to S3 + CloudFront. Claude Code turns the mockup into real React fast. |
| Infrastructure | Terraform + Terragrunt |
Gilad knows this cold from Rapyd. Same OIDC pattern, same module structure. Moving from MVP to Prod is just copying one folder and changing an account ID. |
| Database | PostgreSQL (RDS) |
Structured queries, filtering by agent/date/policy/amount. The audit log needs to be queryable, not just stored. NoSQL won't work here. |
MVP Architecture — one Lambda, one database
We have zero real customers during MVP. A user with 10+ policies is just 10+ rows in a table — a SELECT that takes microseconds. There is no scale problem to solve. One Lambda doing everything is faster to build, easier to debug, and just as correct as four Lambdas. We add complexity only when we hit actual limits.
# MVP: one Lambda does everything Daemon → API Gateway → ONE Lambda (core-handler) → RDS PostgreSQL ↓ + AWS SES (email) The Lambda: 1. Receives MCP call from daemon 2. Validates API token (bcrypt check vs RDS) 3. Loads user policies from RDS (ORDER BY priority) 4. Evaluates policies top-to-bottom — first match wins 5. Checks budget envelope (SELECT FOR UPDATE on RDS) 6. Updates envelope if ALLOW 7. If ESCALATE → fires AWS SES email, stores pending_approval 8. Writes to audit_log 9. Returns ALLOW / BLOCK / ESCALATE to daemon + ONE approval Lambda (tiny, only handles email link clicks): approval-webhook → reads pending_approvals → resolves + notifies
| Component | MVP choice | Cost | Why not the complex version |
|---|---|---|---|
| Lambda count | 2 (core-handler + approval-webhook) | $0 (free tier) | 4 Lambdas = 4x the debugging surface with no benefit at zero scale |
| Envelope tracking | RDS with SELECT FOR UPDATE | $0 extra | Redis ($15/mo) is correct at scale — overkill for 10 test users |
| Event bus | None — synchronous calls | $0 | EventBridge adds async complexity with no benefit at MVP |
| Auth between daemon + API | Signed API token + HTTPS | $0 | mTLS is fiddly to configure and adds days of work — token + TLS is secure enough for demo |
| Total monthly AWS | RDS t3.micro only | ~$20/mo | Full prod stack would be ~$55/mo — we save $35/mo with no functional difference |
Prod Architecture — split when we hit real limits
This is what the architecture becomes after funding, with real customers. Nothing here is needed for the investor demo — but having it documented shows investors we've thought past MVP.
# PROD: split Lambdas, add Redis, add EventBridge
Daemon → API Gateway → mcp-proxy Lambda
↓ invoke
policy-engine Lambda ← reads Redis (fast envelopes)
↓ EventBridge event
audit-logger Lambda ← async, doesn't slow decision
notification-handler Lambda
ElastiCache Redis ← atomic envelope ops, sub-5ms
RDS PostgreSQL ← audit trail, policies, users
| What changes | Why we add it | When to add it |
|---|---|---|
| Split into 4 Lambdas | Independent scaling per function, cleaner IAM, audit logging doesn't block policy decision | First paying customers |
| ElastiCache Redis | Atomic envelope ops at scale — RDS can't handle 1000s of concurrent updates safely | When concurrent users hit RDS limits |
| EventBridge | Decouples audit logging — decision returns instantly, logging happens async | When latency becomes a concern |
| mTLS | Enterprise customers require it for security compliance | Before first enterprise deal |
Cost comparison — MVP vs Prod
| Service | MVP cost | Prod cost | When it grows |
|---|---|---|---|
| Lambda (all functions) | $0 | ~$5–20/mo | After 1M requests/mo |
| RDS PostgreSQL (t3.micro) | ~$20/mo | ~$40/mo (t3.small) | When DB CPU >60% consistently |
| ElastiCache Redis | $0 (not needed) | ~$15/mo (t3.micro) | When concurrent envelope writes cause contention |
| API Gateway | $0 | ~$3–10/mo | After 1M calls/mo |
| S3 + CloudFront | $0 | ~$2/mo | After 1TB transfer/mo |
| S3 + CloudFront (dashboard) | ~$0 | Stays cheap at scale | Never needs upgrading |
| Claude API | ~$10–20/mo | ~$50–200/mo | Scales with transaction volume |
| Total AWS + infra | ~$30–40/mo | ~$120–300/mo | Grows with real usage — not before |
"Our MVP runs on a single Lambda and one Postgres instance — $30/month. We've deliberately kept the architecture flat until we have real traffic. Every component we add in production has a specific trigger that justifies it. We don't add complexity speculatively." This shows financial discipline and engineering maturity — exactly what seed investors want to hear.
The three repos
troxy-tf-modules/
├── rds/
│ ├── main.tf ← postgres instance
│ ├── variables.tf
│ └── outputs.tf ← endpoint, port, db_name
├── elasticache/
│ ├── main.tf ← redis cluster
│ ├── variables.tf
│ └── outputs.tf ← endpoint, port
├── lambda/
│ ├── main.tf ← function + IAM role
│ ├── variables.tf
│ └── outputs.tf ← function_arn, invoke_url
├── api-gateway/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── s3-cloudfront/
│ ├── main.tf ← binary distribution
│ ├── variables.tf
│ └── outputs.tf
└── iam-oidc-github/
├── main.tf ← OIDC federation for GitHub Actions
├── variables.tf ← same pattern built at Rapyd
└── outputs.tf
troxy-tf-live/
├── terragrunt.hcl ← root config, AWS provider, remote state in S3
├── troxy-mvp/ ← the one account for now
│ ├── account.hcl ← account_id, region
│ ├── rds/
│ │ └── terragrunt.hcl
│ ├── elasticache/
│ │ └── terragrunt.hcl
│ ├── lambda/
│ │ ├── mcp-proxy/
│ │ │ └── terragrunt.hcl
│ │ ├── policy-engine/
│ │ │ └── terragrunt.hcl
│ │ ├── notification-handler/
│ │ │ └── terragrunt.hcl
│ │ └── audit-logger/
│ │ └── terragrunt.hcl
│ ├── api-gateway/
│ │ └── terragrunt.hcl
│ ├── s3-cloudfront/
│ │ └── terragrunt.hcl
│ └── iam-oidc-github/
│ └── terragrunt.hcl
└── .github/
└── workflows/
└── terraform.yml ← OIDC, plan on PR, apply on merge
When we need prod: copy troxy-mvp/ → troxy-prod/, update account.hcl. Done.
troxy-dashboard/
├── app/
│ ├── (auth)/
│ │ └── login/
│ ├── dashboard/
│ │ ├── page.tsx ← overview
│ │ ├── cards/
│ │ ├── policies/
│ │ ├── agents/
│ │ ├── activity/
│ │ └── insights/
│ └── api/ ← Next.js API routes → calls api.troxy.ai
├── components/
│ ├── ui/
│ └── dashboard/
├── lib/
│ └── api.ts ← typed API client
└── .github/
└── workflows/
└── deploy.yml ← GitHub Actions: build + s3 sync + CF invalidation
Remote state config (root terragrunt.hcl)
# terragrunt.hcl
remote_state {
backend = "s3"
config = {
bucket = "troxy-mvp-tf-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
contents = <<EOF
provider "aws" {
region = "us-east-1"
}
EOF
}
GitHub Actions workflow (terraform.yml)
# Same OIDC pattern as rapyd-financial — swap account ID + role name
name: Terraform
on:
push:
branches: [main]
pull_request:
jobs:
terraform:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/TroxyGithubTerraform
aws-region: us-east-1
- name: Terragrunt plan
if: github.event_name == 'pull_request'
run: terragrunt run-all plan
- name: Terragrunt apply
if: github.event_name == 'push'
run: terragrunt run-all apply --auto-approve
First steps in AWS (before Terraform)
These four things must be done manually — everything else Terraform handles:
- Create AWS account — troxy-mvp
- Enable IAM Identity Center (SSO) — never use root credentials
- Create S3 bucket manually — troxy-mvp-tf-state (must exist before Terraform can store state)
- Create the OIDC GitHub role — TroxyGithubTerraform (bootstrap manually first time, then manage in TF)
Layer 1 — Network
- All traffic TLS 1.3 minimum — no exceptions
- API Gateway accepts HTTPS only
- RDS and Redis in private subnet — no public internet access
- Security groups: Lambda → RDS on port 5432 only, Lambda → Redis on port 6379 only
- Nothing else can reach RDS or Redis
- No NAT Gateway — Lambda in public subnet with tight security groups (saves $32/mo and is perfectly secure for our architecture)
Layer 2 — Authentication
- Every daemon request carries:
Authorization: Bearer <token> - Token stored as bcrypt hash in RDS — never plaintext, ever
- API Gateway rejects any request without a valid token
- Tokens are per-machine, per-user, revocable instantly from dashboard
- PROD ONLY mTLS between daemon and API Gateway — add before first enterprise customer, not for MVP
Layer 3 — Secrets
- Zero secrets in code or environment variables — ever
- All secrets in AWS Secrets Manager (DB password — SES uses IAM role (no secret needed))
- Lambda reads secrets at cold start, caches in memory
- KMS encrypts everything at rest: RDS, Redis, S3, Secrets Manager
- GitHub Actions uses OIDC — no long-lived AWS keys stored anywhere
Layer 4 — Data
- Postgres encrypted at rest (AWS managed KMS key)
- Redis encrypted at rest + in transit
- S3 bucket: private, no public access, versioning enabled
- Card numbers NEVER stored — only aliases + last 4 digits
- Audit logs immutable — append only, no deletes allowed
Layer 5 — Application
- Input validation on every Lambda endpoint
- SQL via parameterized queries only — no string concatenation
- Rate limiting on API Gateway (per token, per IP)
- CORS locked to
dashboard.troxy.aionly - Each Lambda has minimum IAM permissions — see below
IAM — least privilege per Lambda
| Lambda | Can | Cannot |
|---|---|---|
| mcp-proxy | Read own Secrets Manager secret, invoke policy-engine, invoke audit-logger | Touch RDS directly, touch Redis directly |
| policy-engine | Read from Redis (envelopes), read from RDS (policies table), write envelope updates to Redis | Invoke other Lambdas, write to RDS, touch S3 |
| notification-handler | Call AWS SES via SDK, read/write RDS pending_approvals table | Touch Redis, touch S3 |
| audit-logger | Write to RDS audit_log table only | Read from RDS, touch Redis, touch S3, invoke other Lambdas |
Full system architecture diagram
The diagram below shows the MVP architecture — 2 Lambdas, no Redis, no EventBridge. Section 15 (Tech Plan) explains what gets added post-funding and why. If you're building MVP, follow this diagram. Ignore any references to 4 Lambdas or ElastiCache in earlier sections — those are prod.
┌─────────────────────────────────────────────────────┐
│ USER'S MACHINE │
│ │
│ ┌─────────────────┐ ┌───────────────────────┐ │
│ │ Claude Desktop │────▶│ Troxy Daemon (Go) │ │
│ │ Cursor / etc. │ │ localhost:4242 │ │
│ └─────────────────┘ └──────────┬────────────┘ │
│ │ │
│ mcp_config.json rewritten: │ HTTPS + token │
│ stripe → localhost:4242/stripe │ │
└─────────────────────────────────────┼───────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ AWS troxy-mvp account │
│ │
│ API Gateway HTTP API (api.troxy.ai) │
│ │ │
│ ├──▶ Lambda: core-handler ← MVP: does ALL │
│ │ validates token (bcrypt vs RDS) │
│ │ loads policies (SELECT ORDER BY prio) │
│ │ evaluates rules top-to-bottom │
│ │ checks budget envelope (SELECT FOR UPDATE)│
│ │ writes audit_log │
│ │ fires AWS SES if ESCALATE │
│ │ returns ALLOW / BLOCK / ESCALATE │
│ │ │
│ └──▶ Lambda: approval-webhook │
│ handles approve/decline email clicks │
│ updates pending_approvals in RDS │
│ fires AWS SES confirmation │
│ │
│ RDS PostgreSQL t3.micro (~$20/mo — only paid svc) │
│ (all data: users, policies, audit_log, envelopes, │
│ pending_approvals, api_tokens, card_aliases) │
│ │
│ S3 + CloudFront (free tier) │
│ (daemon binaries + install.troxy.ai script) │
│ │
│ ✗ No Redis (ElastiCache) — added post-funding │
│ ✗ No EventBridge — added post-funding │
│ ✗ No mTLS — added before enterprise customers │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ S3 + CloudFront │
│ dashboard.troxy.io (Next.js static export) │
│ → all data fetching client-side → api.troxy.io │
│ → deployed via GitHub Actions on every push │
└─────────────────────────────────────────────────────┘
The dashboard is built as a static Next.js export — no server-side rendering. All data fetching happens client-side via fetch() calls to api.troxy.io. This means it can be hosted on S3 + CloudFront for essentially free, managed entirely by Terraform. Claude Code must be told this explicitly.
AWS resources (all Terraform)
# S3 bucket — stores built dashboard files aws_s3_bucket "dashboard" { bucket = "troxy-dashboard-prod" # private — only CloudFront can read it } # CloudFront distribution — serves the dashboard globally aws_cloudfront_distribution "dashboard" { origin = s3 bucket above aliases = ["dashboard.troxy.io"] default_root_object = "index.html" # custom_error_response for 404 → index.html (SPA routing) price_class = "PriceClass_100" # US + Europe only — cheapest } # ACM certificate — free SSL for dashboard.troxy.io aws_acm_certificate "dashboard" { domain_name = "dashboard.troxy.io" region = "us-east-1" # must be us-east-1 for CloudFront } # Cloudflare DNS record — points to CloudFront cloudflare_record "dashboard" { name = "dashboard" type = "CNAME" value = cloudfront_distribution.domain_name }
GitHub Actions workflow — deploy.yml
# .github/workflows/deploy.yml in troxy-dashboard repo name: Deploy Dashboard on: push: branches: [main] # deploy on merge to main pull_request: branches: [main] # build check on every PR jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write # required for OIDC contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Build run: npm run build # next build + next export → ./out env: NEXT_PUBLIC_API_URL: https://api.troxy.io - name: Configure AWS credentials (OIDC) if: github.ref == 'refs/heads/main' uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::454983519464:role/TroxyGithubDashboard aws-region: us-east-1 - name: Deploy to S3 if: github.ref == 'refs/heads/main' run: | aws s3 sync ./out s3://troxy-dashboard-prod \ --delete \ --cache-control "max-age=31536000,immutable" - name: Invalidate CloudFront cache if: github.ref == 'refs/heads/main' run: | aws cloudfront create-invalidation \ --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \ --paths "/*"
next.config.js — static export config
// next.config.js — tell Claude Code to use this exactly /** @type {import('next').NextConfig} */ const nextConfig = { output: 'export', // static export — no SSR trailingSlash: true, // needed for S3 routing images: { unoptimized: true // S3 can't do Next.js image optimization } } module.exports = nextConfig // All API calls in the dashboard use: // const API = process.env.NEXT_PUBLIC_API_URL || 'https://api.troxy.io' // fetch(`${API}/dashboard/activity`) // Never use getServerSideProps — always useEffect + fetch
Cost
| Service | MVP cost | At scale |
|---|---|---|
| S3 storage (dashboard files) | ~$0.01/mo | ~$0.01/mo — files never change size |
| CloudFront (data transfer) | ~$0/mo (free tier) | ~$1–5/mo at 10K users |
| ACM certificate | Free | Free forever |
| Total | ~$0/mo | ~$1–5/mo |
-- Users and their API tokens users ( id UUID PRIMARY KEY, email TEXT UNIQUE NOT NULL, password_hash TEXT, -- bcrypt(password, cost=12). NULL if magic-link only verified_at TIMESTAMPTZ, -- NULL = email not yet verified created_at TIMESTAMPTZ DEFAULT now() ) email_verifications ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), token TEXT NOT NULL, -- random 32 bytes, single-use expires_at TIMESTAMPTZ NOT NULL, used_at TIMESTAMPTZ -- NULL = not yet used ) api_tokens ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), token_hash TEXT NOT NULL, -- bcrypt, never plaintext name TEXT, -- "MacBook Pro", "Work Laptop" last_used_at TIMESTAMPTZ, revoked_at TIMESTAMPTZ -- NULL = active ) -- Card aliases — real card number NEVER stored card_aliases ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), alias_name TEXT NOT NULL, -- "Gilad's Groceries" last_four TEXT NOT NULL, -- "1234" card_type TEXT, -- "visa", "mastercard" monthly_budget NUMERIC, budget_used NUMERIC DEFAULT 0, budget_reset_day INTEGER DEFAULT 1 ) -- Registered agents agents ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), name TEXT NOT NULL, -- "Romi Agent" allowed_card_alias_ids UUID[] ) -- Policy rules — evaluated top to bottom, first match wins policies ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), name TEXT NOT NULL, priority INTEGER NOT NULL, agent_id UUID, -- NULL = any agent card_alias_id UUID, -- NULL = any card merchant_category TEXT, -- NULL = any category amount_operator TEXT, -- 'lt', 'gt', 'pct_over_budget' amount_value NUMERIC, action TEXT NOT NULL, -- 'ALLOW', 'BLOCK', 'ESCALATE' notify_channels TEXT[] -- ['email', 'slack'] ) -- Every single transaction attempt — immutable, no deletes audit_log ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), agent_id UUID, card_alias_id UUID, merchant_name TEXT, merchant_category TEXT, amount NUMERIC NOT NULL, currency TEXT DEFAULT 'USD', policy_id_matched UUID, -- which rule fired decision TEXT NOT NULL, -- 'ALLOW', 'BLOCK', 'ESCALATE' approved_by TEXT, -- 'auto' or user email approved_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT now() ) -- Held transactions waiting for human approval pending_approvals ( id UUID PRIMARY KEY, audit_log_id UUID REFERENCES audit_log(id), user_id UUID REFERENCES users(id), approval_token TEXT NOT NULL, -- signed, expires in N minutes expires_at TIMESTAMPTZ NOT NULL, resolved_at TIMESTAMPTZ, resolution TEXT -- 'APPROVED', 'DECLINED', 'EXPIRED' )
Troxy intercepts the MCP call before it reaches the real payment provider. So for testing, you replace Stripe's real MCP server with a fake one that always returns "success". The agent never knows the difference. No real card, no real charges, no real money — ever during development.
How the fake Stripe MCP works
Real flow (production): Agent → Troxy daemon → Troxy API → real Stripe MCP → real charge 💳 Test flow (local dev): Agent → Troxy daemon → Troxy API → fake Stripe MCP → returns "success" 🧪 (no money moves, ever)
Fake Stripe MCP server (20 lines)
This is all you need to simulate the payment provider. It accepts any MCP tool call, logs it so you can see exactly what Troxy sent, and returns a success response.
// fake-stripe-mcp/index.ts import express from 'express' const app = express() app.use(express.json()) app.post('/tools/:toolName', (req, res) => { console.log(`💳 MCP call intercepted: ${req.params.toolName}`, req.body) // Pretend to be Stripe — return success for everything res.json({ success: true, payment_id: 'fake_' + Date.now(), amount: req.body.amount, merchant: req.body.merchant, status: 'completed' }) }) app.listen(3001, () => console.log('🟢 Fake Stripe MCP running on localhost:3001') )
docker-compose.yml — full local stack
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: troxy
POSTGRES_PASSWORD: localdev
ports: ["5432:5432"]
fake-stripe-mcp:
build: ./fake-stripe-mcp
ports: ["3001:3001"]
# Logs every MCP call so you can see what Troxy forwarded
troxy-api:
build: ./backend
environment:
DATABASE_URL: postgres://postgres:localdev@postgres/troxy
POSTMARK_API_KEY: "POSTMARK_API_TEST" # sandbox mode — no real emails
FAKE_MCP_URL: http://fake-stripe-mcp:3001
ports: ["3000:3000"]
depends_on: [postgres, fake-stripe-mcp]
Starting the local stack
# 1. Start everything docker-compose up # 2. Start the daemon pointing at local API (not prod) TROXY_API_URL=http://localhost:3000 troxy daemon start # 3. Verify it's running curl http://localhost:3000/health # → {"status":"ok","db":"connected"} # 4. Manually fire a test MCP call curl -X POST http://localhost:4242/stripe/tools/create_payment \ -H "Authorization: Bearer test_token_123" \ -H "Content-Type: application/json" \ -d '{"amount": 299, "merchant": "Ahrefs", "category": "software"}' # 5. Check the logs — you should see: # troxy-api: ✅ ALLOW — policy matched, forwarding # fake-stripe-mcp: 💳 MCP call intercepted: create_payment {amount:299...}
Real services during testing
| Service | Dev / local | Investor demo | Real users |
|---|---|---|---|
| Payment provider | Fake Stripe MCP (Docker) | Stripe test mode — card 4242 4242 4242 4242 | Real Stripe MCP |
| Email (AWS SES) | Sandbox mode — no real delivery | AWS SES sandbox or real | Real AWS SES |
| Database | Local Postgres (Docker) | AWS RDS (troxy-mvp) | AWS RDS (troxy-prod) |
| Real money | Never | Never (test mode) | Only then |
Environment 1 — Local Docker (daily)
Fastest feedback loop. Zero cost. Catches 80% of bugs. Every code change tested here first.
# Start the stack docker-compose up # Run the test suite npm test # unit tests — policy engine logic npm run test:integration # full flow against local Docker stack # Watch logs in real time while Claude makes a call docker-compose logs -f troxy-api
Environment 2 — AWS troxy-mvp (weekly)
Real AWS. Real Lambda cold starts. Real RDS latency. GitHub Actions deploys here automatically on every push to main. Catches the 20% of bugs Docker misses — IAM permission gaps, Lambda timeouts, VPC networking issues.
Environment 3 — Full end-to-end demo run (before every investor meeting)
Run this checklist the morning of every investor meeting. All 9 steps must pass.
| # | Action | Expected result |
|---|---|---|
| 1 | Run npx troxy init | Daemon installs, browser opens dashboard |
| 2 | Paste API token from dashboard | Machine appears as "Online" in sidebar |
| 3 | Ask Claude: "charge $50 to Rami Levy" | AUTO-APPROVE — under budget, right category |
| 4 | Check dashboard Activity tab | Transaction appears with green checkmark ✓ |
| 5 | Ask Claude: "charge $600 to Apple Store" | BLOCK — Electronics category policy fires |
| 6 | Check dashboard — blocked transaction | Red X with policy name and reason shown |
| 7 | Ask Claude: "charge $240 to hotel" (budget $200) | ESCALATE — over budget, notification fires |
| 8 | Check email — approval notification | Email with Approve / Decline in inbox (not spam) |
| 9 | Click Approve in email | Dashboard updates to approved ✓ |
Steps 3–9 run live in the meeting. Agent tries to spend → Troxy blocks it → escalates it → human approves from email → dashboard shows the full audit trail. That's the product. That's what gets funded.
It's not enough for the dashboard to show a transaction. You need to prove that Troxy was the reason the transaction was allowed or blocked — not that things happened to work out the same way they would have without Troxy.
Level 1 — The block test (strongest proof)
Create a policy that blocks Electronics. Ask Claude to buy AirPods. This is the most definitive test — you cannot fake a block.
Policy: BLOCK Electronics category
Ask Claude: "buy me AirPods for $150"
If Troxy is working: Claude gets an error. Dashboard shows the block.
If Troxy is bypassed: Claude succeeds. Nothing in the dashboard.
→ If Claude got blocked, Troxy is real. You cannot fake this.
Level 2 — The database test
Every request that hits Troxy gets a row in the audit log. No row means Troxy never saw it.
# After any agent action, run:
SELECT decision, merchant_name, amount, created_at
FROM audit_log
ORDER BY created_at DESC
LIMIT 5;
Row exists → Troxy processed it ✅
No row exists + Claude succeeded → Claude went around Troxy ❌
Level 3 — The block-all test (nuclear)
Set a policy to block everything. Ask Claude to buy anything. Nothing should go through.
Policy: BLOCK — all merchants, all amounts, all categories Ask Claude: "buy me anything — coffee, a book, a pen" Expected: Claude is blocked every single time. No exceptions. → If block-all works, Troxy is genuinely in the middle of every payment call. No bypasses possible.
Level 4 — The kill switch test (most important)
This is the ultimate proof. Shut down the Troxy daemon. Try to make a payment.
# Stop the Troxy daemon troxy daemon stop # or just kill the process # Now ask Claude to make a payment Payment fails → Troxy was the gateway. It's real. ✅ Payment works → MCP config not pointing at Troxy. Fix the config. ❌ → This is your proof of life test. If removing Troxy breaks payments, Troxy was genuinely required.
Level 5 — The network proof
Verify at the OS level that Claude's traffic is routing through the daemon.
# While Claude is making a payment, in another terminal: sudo lsof -i :4242 # You should see two things: # 1. troxy daemon LISTEN on :4242 # 2. Claude connecting TO :4242 → Seeing Claude connect to the daemon port = Troxy is intercepting the traffic at the network level. Claude is NOT talking to Stripe directly.
Run these in order — complete validation:
| # | Test | Pass condition | What it proves |
|---|---|---|---|
| 1 | Block test — Electronics policy + buy AirPods | Claude blocked | Troxy is intercepting and enforcing |
| 2 | DB test — check audit_log after every action | Row exists in DB | Troxy saw and logged every call |
| 3 | Block-all test — block everything, try anything | Nothing gets through | Troxy controls 100% of payment calls |
| 4 | Kill switch — stop daemon, try payment | Payment fails | Troxy is a hard dependency, not optional |
| 5 | Network test — lsof -i :4242 during payment | Claude connects to daemon port | Traffic routes through Troxy at OS level |
If all 5 levels pass, Troxy is genuinely sitting in the middle of every payment call. Not appearing to — actually doing it. This is what you run before every investor demo and before every new version ships.
| Week | What we build | Deliverable | Running cost |
|---|---|---|---|
| Week 1 | troxy-tf-live + modules. OIDC GitHub Actions. RDS + 2 Lambda shells. Local docker-compose with fake Stripe MCP. | api.troxy.ai returns 200 | ~$20/mo |
| Week 2 | core-handler Lambda logic. MCP call interception. Policy evaluation against RDS. Budget envelope (SELECT FOR UPDATE). Audit log. ALLOW + BLOCK paths. | Validation levels 1–4 pass | ~$20/mo |
| Week 3 | ESCALATE path. approval-webhook Lambda. AWS SES approval email. Approve/decline links. Resolution written back to RDS. Full 3-path flow. | All 9 demo steps pass | ~$20/mo |
| Week 4 | Next.js dashboard. Real data from RDS. All 5 pages. npx troxy init works. Full validation checklist passes. | Investor demo ready | ~$20/mo |
Infrastructure deploy sequence — Week 1
# MVP — lean, 2 Lambdas, no Redis, no EventBridge 1. iam-oidc-github ← bootstrap manually — GitHub Actions auth 2. s3-cloudfront ← install.troxy.ai, daemon binaries 3. rds ← Postgres t3.micro — only real monthly cost 4. lambda/core-handler ← all backend logic in one function 5. lambda/approval-webhook ← approve/decline email link handler 6. api-gateway ← wires both Lambdas to api.troxy.ai # Explicitly NOT in MVP: # elasticache — add when concurrent envelope writes need Redis speed # eventbridge — add when async logging latency becomes a concern # 4 Lambdas — add when independent scaling is actually needed
Post-funding additions — when triggered, not before
# Trigger-based additions — each has a specific reason + elasticache ← RDS envelope contention under real concurrent users + lambda/policy-engine ← need independent scaling from core-handler + lambda/audit-logger ← audit logging blocking decision latency + lambda/notification ← notification volume needs dedicated function + eventbridge ← decoupling audit from decision path + rds → t3.small ← DB CPU above 60% consistently + mTLS ← first enterprise customer requires it # All modules are already written in troxy-tf-modules. # Adding prod components = uncomment + deploy. No rewrite.
Claude Code is a very fast junior developer. It writes a lot quickly, but needs an experienced engineer — Gilad — to catch subtle mistakes. The workflow is: Claude Code writes the first draft, Gilad reviews critically, iterate. Not: Claude Code builds it, Gilad deploys it blindly.
What Claude Code handles well
| Component | Why Claude Code is good here | Time saving |
|---|---|---|
| Terraform modules | Repetitive, boilerplate-heavy, well-defined inputs/outputs. Perfect for code generation. | 5 days → 2 days |
| Next.js dashboard | Full HTML mockup already exists. Claude Code converts mockup → real React components with real API calls. | 2 weeks → 4 days |
| TypeScript Lambda | Policy evaluation logic, audit log writes, AWS SES integration. Logic is well-defined in this doc. | 1 week → 2–3 days |
| DB schema + migrations | Schema is fully defined in Section 18. Claude Code writes the SQL and migration scripts. | 1 day → 2 hours |
| docker-compose + fake MCP | Fully specified in Section 19. Pure boilerplate, Claude Code nails this. | 1 day → 1 hour |
Where Gilad leads — Claude Code assists
| Component | Why Gilad leads |
|---|---|
| Go daemon | Cross-compiling for Mac/Linux/Windows, OS-specific service setup (launchd vs systemd), MCP config file interception per OS. Claude Code helps but Gilad must understand and validate every line. |
| MCP interception layer | MCP spec is new and evolving. Claude Code may confidently write something subtly wrong. Read the Anthropic MCP docs yourself first, then use Claude Code with that understanding as a guide. |
| Approval token security | The signed approval token (expires in 10min, single-use, cryptographically verified) must be reviewed personally. A subtle bug here is a real vulnerability — this is not a place to trust output blindly. |
| AWS networking | VPC, security groups, subnet config. Gilad knows AWS networking better than Claude Code does. Trust your instincts over its output here. |
How to give tasks to Claude Code
The right way to use Claude Code: write a clear spec for each component (what it does, inputs, outputs, constraints, what NOT to do), give it the spec, review critically.
// Good Claude Code prompt example: "Build the core-handler Lambda function based on this spec: - TypeScript, Node.js 20, AWS Lambda - Receives MCP tool call from daemon via API Gateway - Validates API token against RDS (bcrypt compare) - Loads user policies from RDS ordered by priority ASC - Evaluates each policy top-to-bottom, first match wins - On ALLOW: checks budget envelope with SELECT FOR UPDATE updates envelope, writes audit_log, returns {decision:'ALLOW'} - On BLOCK: writes audit_log, returns {decision:'BLOCK', reason} - On ESCALATE: writes audit_log, writes pending_approvals, calls AWS SES with approval email, returns {decision:'ESCALATE'} - Never store card numbers. Never log tokens. - Database schema is in [attach Section 18 of this doc] - Connection strings from AWS Secrets Manager, not env vars" // Bad Claude Code prompt: "Build the backend for my payment proxy startup" // → too vague, output will need massive rework
Time estimate — with vs without Claude Code
| Without Claude Code | With Claude Code | |
|---|---|---|
| Terraform modules | 5 days | 2 days |
| TypeScript Lambda | 1 week | 2–3 days |
| Dashboard (Next.js) | 2 weeks | 4–5 days |
| Go daemon | 2 weeks | 1 week (Gilad leads) |
| docker-compose + fake MCP | 1 day | 1–2 hours |
| Total to demo | 6–8 weeks | 3–4 weeks |
This internal document is the spec. When it's complete and reviewed by both founders, hand it to Claude Code section by section. Start with Terraform (lowest risk, most boilerplate), then Lambda, then dashboard. Keep the Go daemon and MCP interception for Gilad to lead with Claude Code assisting. Review every output before committing.
API Gateway HTTP API (not REST API — HTTP API is cheaper and faster for our use case) → routes to Lambda. All endpoints require Authorization: Bearer <token> except /health. Base URL: https://api.troxy.ai
Authentication header — every request
Authorization: Bearer troxy_<base64url_random_32_bytes> # Example: Authorization: Bearer troxy_dGhpcyBpcyBhIHRlc3QgdG9rZW4K # Token format: "troxy_" prefix + base64url(32 random bytes) # Stored in RDS as: bcrypt(token, cost=12) # Never stored in plaintext anywhere
POST /evaluate — core endpoint
The daemon calls this for every MCP payment tool call it intercepts. The Lambda evaluates the request against the user's policies and returns a decision. Must respond in under 300ms.
# REQUEST POST https://api.troxy.ai/evaluate Authorization: Bearer txy-xxx Content-Type: application/json { "request_id": "uuid-v4", // generated by daemon, for idempotency "agent_id": "uuid-v4", // which agent made the call "card_alias_id": "uuid-v4", // which card alias to charge "mcp_tool": "stripe_create_payment",// exact MCP tool name called "merchant_name": "Apple Store", "merchant_category": "electronics", // MCC category string "amount": 599.99, // always in USD, float "currency": "USD", "task_context": "User asked: buy AirPods Pro" // raw agent task description } # RESPONSE — ALLOW HTTP 200 { "decision": "ALLOW", "audit_log_id": "uuid-v4", "reason": "matched policy: Default Allow under $500", "forward_to": "https://mcp.stripe.com" // daemon forwards original call here } # RESPONSE — BLOCK HTTP 200 { "decision": "BLOCK", "audit_log_id": "uuid-v4", "reason": "matched policy: Block Electronics", "policy_name": "Block Electronics", "show_user": "This purchase was blocked by your Troxy policy." } # RESPONSE — ESCALATE HTTP 200 { "decision": "ESCALATE", "audit_log_id": "uuid-v4", "approval_id": "uuid-v4", "reason": "amount $599.99 exceeds budget envelope ($500 remaining)", "show_user": "Approval request sent to your email.", "expires_in_secs": 600 // 10 minutes }
POST /approve/:approval_id — approval webhook
Called when the user clicks Approve or Decline in their email. Handled by the approval-webhook Lambda. The approval token is in the URL — it is signed and single-use.
# Approve GET https://api.troxy.ai/approve/<approval_id>?token=<signed_token>&action=approve # Decline GET https://api.troxy.ai/approve/<approval_id>?token=<signed_token>&action=decline # RESPONSE — success (redirect to dashboard confirmation page) HTTP 302 Location: https://dashboard.troxy.ai/approved?id=<approval_id> # RESPONSE — expired token HTTP 200 HTML page: "This approval link has expired. Open your dashboard to review." # RESPONSE — already resolved HTTP 200 HTML page: "This request was already resolved." # Signed token = HMAC-SHA256(approval_id + expires_at, SECRET_KEY) # SECRET_KEY stored in AWS Secrets Manager — never in code # Single-use: resolved_at is set on first click, subsequent clicks rejected
GET /health — health check
# No auth required GET https://api.troxy.ai/health # RESPONSE HTTP 200 { "status": "ok", "db": "connected", "ts": "2026-03-27T10:00:00Z" } # If DB unreachable: HTTP 503 { "status": "degraded", "db": "unreachable" } # Used by UptimeRobot to monitor status.troxy.ai # Also used by daemon to verify connectivity on startup
GET /dashboard/activity — dashboard data
GET https://api.troxy.ai/dashboard/activity?limit=50&offset=0
Authorization: Bearer txy-xxx
HTTP 200
{
"items": [
{
"id": "uuid-v4",
"agent_name": "Shopping Agent",
"card_alias_name": "Gilad's Groceries",
"merchant_name": "Rami Levy",
"merchant_category":"grocery",
"amount": 49.90,
"currency": "USD",
"decision": "ALLOW", // "ALLOW" | "BLOCK" | "ESCALATE"
"policy_name": "Default Allow",
"created_at": "2026-03-27T10:00:00Z"
}
],
"total": 142,
"has_more": true
}
GET /dashboard/insights — Insights V1 (SQL only)
GET https://api.troxy.ai/dashboard/insights?period=30d
Authorization: Bearer txy-xxx
HTTP 200
{
"period_days": 30,
"total_spent": 1247.50,
"total_blocked": 340.00,
"decisions": { "ALLOW": 87, "BLOCK": 12, "ESCALATE": 5 },
"top_merchants": [
{ "name": "Rami Levy", "category": "grocery", "total": 342.00 }
],
"by_category": [
{ "category": "grocery", "total": 342.00 }
],
"spend_by_day": [
{ "date": "2026-03-01", "total": 49.90 }
]
}
GET /dashboard/envelopes — budget status
GET https://api.troxy.ai/dashboard/envelopes
Authorization: Bearer txy-xxx
HTTP 200
{
"envelopes": [
{
"card_alias_id": "uuid-v4",
"alias_name": "Gilad's Groceries",
"last_four": "1234",
"monthly_budget": 500.00,
"budget_used": 237.50,
"budget_remaining":262.50,
"reset_day": 1
}
]
}
POST /dashboard/policies — create policy
POST https://api.troxy.ai/dashboard/policies
Authorization: Bearer txy-xxx
Content-Type: application/json
{
"name": "Block Electronics",
"priority": 10,
"agent_id": null, // null = any agent
"card_alias_id": null, // null = any card
"merchant_category": "electronics", // null = any category
"amount_operator": null, // null = any amount
"amount_value": null,
"action": "BLOCK",
"notify_channels": ["email"]
}
HTTP 201
{ "id": "uuid-v4", "created_at": "2026-03-27T10:00:00Z" }
# Other policy endpoints:
GET /dashboard/policies ← list all policies (ordered by priority)
PATCH /dashboard/policies/:id ← update a policy
DELETE /dashboard/policies/:id ← delete a policy
POST /dashboard/policies/reorder ← drag-to-reorder (send full priority array)
POST /tokens — create API token
POST https://api.troxy.ai/tokens Authorization: Bearer txy-xxx // existing session token from dashboard login { "name": "MacBook Pro Work" } HTTP 201 { "token": "troxy_dGhpcyBpcyBhIHRlc3QK", // shown ONCE, never again "id": "uuid-v4", "name": "MacBook Pro Work" } # Other token endpoints: GET /tokens ← list tokens (name + last_used_at, never the token itself) DELETE /tokens/:id ← revoke a token instantly
HTTP error codes — standard across all endpoints
| Code | Meaning | Response body |
|---|---|---|
| 400 | Missing or invalid request fields | {"error":"amount is required"} |
| 401 | Missing or invalid token | {"error":"unauthorized"} |
| 404 | Resource not found | {"error":"policy not found"} |
| 409 | Duplicate request (same request_id) | {"error":"duplicate request_id"} |
| 429 | Rate limit exceeded (API Gateway) | {"error":"too many requests"} |
| 503 | Database unreachable | {"error":"service unavailable"} |
AWS services behind the API
| Endpoint | Routes to | AWS service |
|---|---|---|
POST /evaluate | core-handler Lambda | API Gateway HTTP API → Lambda → RDS |
GET /approve/:id | approval-webhook Lambda | API Gateway → Lambda → RDS → AWS SES |
GET /health | core-handler Lambda | API Gateway → Lambda → RDS ping |
/dashboard/* | core-handler Lambda | API Gateway → Lambda → RDS |
/tokens | core-handler Lambda | API Gateway → Lambda → RDS |
What npx troxy init does — step by step
# npx troxy init runs this sequence:
1. Detect OS (darwin / linux / windows)
2. Detect CPU architecture (amd64 / arm64)
3. Download correct binary from S3/CloudFront:
https://install.troxy.ai/v1.0.0/troxy-darwin-arm64
https://install.troxy.ai/v1.0.0/troxy-linux-amd64
https://install.troxy.ai/v1.0.0/troxy-windows-amd64.exe
4. Verify SHA256 checksum (hardcoded in npm package for that version)
5. Make binary executable: chmod +x troxy
6. Move to user path:
macOS/Linux: /usr/local/bin/troxy
Windows: %APPDATA%\troxy\troxy.exe
7. Run: troxy setup
→ Generates a machine_id (UUID, stored in ~/.troxy/config.json)
→ Opens browser: https://dashboard.troxy.ai/connect?machine_id=xxx
→ User logs in, creates token, copies it
→ User pastes token: "Paste your API token:"
→ Token stored in: ~/.troxy/config.json
→ Daemon verifies token against api.troxy.ai/health
8. Rewrite MCP config (see Section 26)
9. Start daemon:
macOS: register launchd service → auto-start on login
Linux: register systemd service → auto-start on boot
Windows: register Windows Service → auto-start on boot
10. Print: "✅ Troxy is running. Dashboard: https://dashboard.troxy.ai"
Config file — ~/.troxy/config.json
{
"machine_id": "uuid-v4",
"api_token": "txy-xxx", // stored here, read on startup
"api_url": "https://api.troxy.ai",
"daemon_port": 4242,
"version": "1.0.0",
"mcp_configs": [ // list of MCP config files we rewrote
"~/.config/claude/claude_desktop_config.json",
"~/.cursor/mcp.json"
]
}
Daemon runtime — what it does every second
# On startup: 1. Read ~/.troxy/config.json 2. Verify token: GET api.troxy.ai/health (401 = token revoked, exit) 3. Start HTTP server on localhost:<daemon_port> (default 4242) 4. Log: "Troxy daemon running on port 4242" # On each incoming MCP call (from Claude Desktop / Cursor / etc.): 1. Receive HTTP request on localhost:4242/<mcp_server>/tools/<tool_name> 2. Extract: tool_name, request body (amount, merchant, etc.) 3. Build evaluate request body (see API contract Section 24) 4. POST to api.troxy.ai/evaluate with Bearer token 5. Read decision from response: ALLOW → forward original request to real MCP endpoint return real MCP response to agent BLOCK → do NOT forward return error to agent: {"error": "troxy_blocked", "reason": "..."} ESCALATE → do NOT forward yet poll api.troxy.ai/approval/:id every 5s (timeout: 600s) if approved → forward original request → return response if declined/expired → return error to agent # Background goroutine (every 30s): 1. GET api.troxy.ai/health 2. If 401: token revoked — stop accepting new requests, log error 3. If 503: API down — enter fail-open or fail-closed mode (configurable)
Daemon binary — build matrix
| OS | Arch | Binary name | Build command |
|---|---|---|---|
| macOS | arm64 (M1/M2/M3) | troxy-darwin-arm64 | GOOS=darwin GOARCH=arm64 go build |
| macOS | amd64 (Intel) | troxy-darwin-amd64 | GOOS=darwin GOARCH=amd64 go build |
| Linux | amd64 | troxy-linux-amd64 | GOOS=linux GOARCH=amd64 go build |
| Windows | amd64 | troxy-windows-amd64.exe | GOOS=windows GOARCH=amd64 go build |
All 4 binaries are built by GitHub Actions on every release tag, uploaded to S3 (install.troxy.ai via CloudFront), and SHA256 checksums are published alongside them.
How MCP servers are configured
Claude Desktop, Cursor, and other MCP-compatible agents read a config file at startup that lists which MCP servers to connect to and where they are. This is the file we rewrite.
# Claude Desktop config file locations: macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Linux: ~/.config/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json # Cursor config file locations: macOS: ~/Library/Application Support/Cursor/User/globalStorage/mcp.json Linux: ~/.config/Cursor/User/globalStorage/mcp.json
Before Troxy — original MCP config
// claude_desktop_config.json — BEFORE troxy init
{
"mcpServers": {
"stripe": {
"command": "npx",
"args": ["-y", "@stripe/mcp-server"],
"env": {
"STRIPE_API_KEY": "sk_live_xxx"
}
}
}
}
After Troxy — rewritten MCP config
// claude_desktop_config.json — AFTER troxy init { "mcpServers": { "stripe": { "command": "npx", "args": ["-y", "@stripe/mcp-server"], // original kept for forwarding "env": { "STRIPE_API_KEY": "sk_live_xxx" }, "proxy": { "enabled": true, "url": "http://localhost:4242/stripe", "troxy_card_alias_id": "uuid-of-card-alias" } } } } # The highlighted "proxy" block is injected by troxy init. # Claude Desktop reads this and sends ALL stripe MCP calls # to localhost:4242/stripe instead of executing stripe directly. # The daemon intercepts, evaluates, then decides whether to # forward to the real stripe server or block.
What the daemon does with a forwarded call
# Incoming from Claude Desktop: POST http://localhost:4242/stripe/tools/create_payment { "amount": 599, "currency": "usd", "description": "AirPods Pro", "customer": "cus_xxx" } # Daemon extracts payment fields, calls Troxy API: POST https://api.troxy.ai/evaluate { "agent_id": "claude-desktop-machine-uuid", "card_alias_id": "uuid-from-config", "mcp_tool": "create_payment", "merchant_name": "Apple", // extracted from description "merchant_category": "electronics", // MCC lookup by daemon "amount": 599, "currency": "USD" } # If ALLOW — daemon forwards original call to real Stripe: # Spawns the real @stripe/mcp-server process (from original config) # Pipes the original request to it # Returns real Stripe response to Claude Desktop # Claude Desktop never knew Troxy was in the middle # If BLOCK — daemon returns error to Claude Desktop: { "error": "Payment blocked by Troxy policy: Block Electronics" } # If ESCALATE — daemon holds the call until approved: # Returns pending status to Claude Desktop # Polls api.troxy.ai/approval/:id every 5 seconds # If approved → executes the original call → returns result # If expired (10min) → returns error to Claude Desktop
MCC (Merchant Category) detection
The daemon needs to figure out the merchant category from the payment description. MVP approach: a static lookup table in the daemon binary.
// mcc_map.go — built into daemon binary var merchantCategories = map[string]string{ "apple": "electronics", "amazon": "retail", "uber": "transport", "airbnb": "travel", "booking": "travel", "marriott": "travel", "rami levy": "grocery", "shufersal": "grocery", "ahrefs": "software", "github": "software", "netflix": "entertainment", // ... ~200 common merchants } // If merchant not in map → category = "other" // User can override category per policy rule
Policy rule — full structure
{
"id": "uuid-v4",
"user_id": "uuid-v4",
"name": "Block Electronics over $100",
"priority": 10, // lower = evaluated first
"enabled": true,
// MATCH CONDITIONS — all must be true for rule to fire (AND logic)
"agent_id": "uuid-v4", // null = any agent
"card_alias_id": "uuid-v4", // null = any card
"merchant_category": "electronics", // null = any category
"merchant_name": null, // null = any merchant (exact match if set)
"amount_operator": "gt", // null = any amount
"amount_value": 100.00, // used with operator
// ACTION — what to do when all conditions match
"action": "BLOCK", // "ALLOW" | "BLOCK" | "ESCALATE"
// NOTIFICATIONS — who to tell when this rule fires
"notify_channels": ["email"] // ["email"] only for MVP
}
Amount operators
| Operator | Meaning | Example |
|---|---|---|
gt | amount greater than value | Block if amount > $500 |
lt | amount less than value | Allow if amount < $50 (auto-approve small) |
gte | amount greater than or equal | Escalate if amount ≥ $200 |
lte | amount less than or equal | Allow if amount ≤ $100 |
eq | amount exactly equals | Rare — for exact subscription amounts |
pct_over_budget | amount exceeds remaining envelope by X% | Escalate if charge is >10% over remaining budget |
Evaluation algorithm — exact logic
This is the precise pseudocode the Lambda implements. Claude Code must follow this exactly.
function evaluateRequest(req, userId): // 1. Validate token user = validateToken(req.headers.authorization) if !user → return 401 // 2. Idempotency check existing = db.query("SELECT * FROM audit_log WHERE request_id = $1", req.request_id) if existing → return existing decision (same response, no double processing) // 3. Load policies ordered by priority policies = db.query( "SELECT * FROM policies WHERE user_id = $1 AND enabled = true ORDER BY priority ASC", userId ) // 4. Evaluate each policy — first match wins matchedPolicy = null for policy in policies: if policy.agent_id != null AND policy.agent_id != req.agent_id → continue if policy.card_alias_id != null AND policy.card_alias_id != req.card_alias_id → continue if policy.merchant_category != null AND policy.merchant_category != req.merchant_category → continue if policy.merchant_name != null AND policy.merchant_name != req.merchant_name → continue if policy.amount_operator != null: if !evaluateAmount(req.amount, policy.amount_operator, policy.amount_value) → continue matchedPolicy = policy break // first match wins — stop evaluating // 5. If no policy matched → default action if matchedPolicy == null: decision = "ALLOW" // default: allow if no rule explicitly blocks reason = "no matching policy — default allow" else: decision = matchedPolicy.action reason = "matched policy: " + matchedPolicy.name // 6. Budget envelope check (only if ALLOW) if decision == "ALLOW": envelope = db.queryWithLock( "SELECT * FROM card_aliases WHERE id = $1 FOR UPDATE", req.card_alias_id ) if envelope.monthly_budget != null: remaining = envelope.monthly_budget - envelope.budget_used if req.amount > remaining: decision = "ESCALATE" reason = "amount $" + req.amount + " exceeds remaining budget $" + remaining // 7. Write audit log auditId = db.insert("audit_log", { ...req, decision, policy_id: matchedPolicy?.id }) // 8. Handle ALLOW — update envelope if decision == "ALLOW": db.query("UPDATE card_aliases SET budget_used = budget_used + $1 WHERE id = $2", req.amount, req.card_alias_id) return { decision: "ALLOW", audit_log_id: auditId, forward_to: realMcpUrl } // 9. Handle BLOCK if decision == "BLOCK": if matchedPolicy.notify_channels includes "email": ses.send(blockNotificationEmail) return { decision: "BLOCK", audit_log_id: auditId, reason, show_user: ... } // 10. Handle ESCALATE if decision == "ESCALATE": approvalId = uuid() token = hmacSign(approvalId + expiresAt, SECRET_KEY) db.insert("pending_approvals", { approvalId, auditId, token, expiresAt: now+600s }) ses.send(escalationEmail with approve/decline links) return { decision: "ESCALATE", approval_id: approvalId, expires_in_secs: 600 }
Default starter policies — created on signup
Every new user gets these 3 policies automatically. They can edit or delete them.
| Priority | Name | Condition | Action |
|---|---|---|---|
| 10 | Auto-approve small | amount < $50 | ALLOW |
| 20 | Escalate large | amount > $200 | ESCALATE |
| 100 | Default allow | anything else | ALLOW |
Condition fields — full list (MVP)
| Field | Operators | Example |
|---|---|---|
amount ($) | lt, lte, gt, gte, eq, between | amount greater than 500 |
amount (% of budget) | gt, gte, lt, lte | amount % of budget greater than 80 — escalate when over 80% spent |
category | is, is not | category is electronics |
merchant_name | is, contains, starts_with, not_contains | merchant name contains "Amazon" |
agent_name | is, is not, is any | agent name is "Shopping Agent" |
card | is, is any | card is "Work Expenses" |
hour of day | lt, gt, between | hour is between 22:00 and 06:00 → block after hours |
day of week | is, is not | day is Saturday or Sunday → block on weekends |
tx_per_hour | gt, gte | transactions per hour greater than 5 → block velocity attack |
tx_per_day | gt, gte | transactions per day greater than 20 |
currency | is, is not | currency is not USD → block foreign currency |
merchant_country | is, is not | merchant country is not IL → block outside Israel |
Default fallback
When a user first signs up, all transactions are allowed by default. The user creates policies to restrict. This is identical to AWS SCPs — you get a default Allow policy, then you add Deny policies on top. Never block-by-default on a new account — it would break every agent immediately and the user wouldn't understand why.
The multi-condition rule builder is powerful but complex. Most users want to say "block anything over $500 on weekends except travel" — not click through 4 dropdowns. The AI creator bridges this gap. The structured policy builder stays for power users and for the AI to render its output into.
The flow
# User types in the policy input box: "Block electronics and entertainment purchases over $100, unless it's before 6pm on a weekday" # Claude API converts this to structured conditions: { "name": "Block high-value leisure purchases", "conditions": [ { "field": "category", "operator": "eq", "value": "electronics" }, ← OR { "field": "category", "operator": "eq", "value": "entertainment" }, { "field": "amount", "operator": "gt", "value": 100 }, { "field": "hour", "operator": "gt", "value": 18 }, ← OR { "field": "day_of_week", "operator": "in", "value": ["Saturday","Sunday"] } ], "action": "BLOCK" } # Dashboard renders it in the visual rule builder # User can review, tweak, and confirm # Policy is saved exactly like a manually-created one
Implementation plan (V2)
| Step | What | Notes |
|---|---|---|
| 1 | Add text input to Policies page | "Describe your policy in plain English..." textarea with "Generate" button |
| 2 | Call Claude API from core-handler | POST /dashboard/policies/generate → passes text to Claude with structured JSON schema prompt |
| 3 | Claude returns structured policy JSON | Use tool_use / JSON mode to guarantee parseable output. Schema matches existing conditions format. |
| 4 | Dashboard renders in visual builder | User sees the generated policy in the same multi-condition UI. Can edit before saving. |
| 5 | User confirms → saved to RDS | Identical to manually-created policy. AI-generated policies get a ✨ badge in the list. |
Prompt for Claude Code to implement (V2)
// System prompt for policy generation:
You are a payment policy assistant for Troxy, an AI agent payment control platform.
Convert the user's plain English policy description into a structured JSON policy object.
The policy must use only these fields:
- amount ($): operators: lt, lte, gt, gte, eq, between
- amount_pct (% of budget): operators: gt, gte, lt, lte
- category: values: electronics, grocery, travel, lodging, saas, restaurant, entertainment, gas, pharmacy, clothing, health, education, government, other
- merchant_name: operators: eq, contains, starts_with, not_contains
- agent_name: operators: eq, neq, any
- hour (0-23): operators: lt, gt, between
- day_of_week: values: Monday-Sunday
- tx_per_hour: operators: gt, gte
- tx_per_day: operators: gt, gte
- currency: values: USD, EUR, GBP, ILS, JPY, CAD, AUD
- merchant_country: operators: eq, neq
Action must be: ALLOW, BLOCK, ESCALATE, or NOTIFY.
Return ONLY valid JSON matching this schema:
{
"name": "string",
"conditions": [{"field": "string", "operator": "string", "value": "string", "value2": "string|null"}],
"action": "ALLOW|BLOCK|ESCALATE|NOTIFY"
}
"We built the full structured policy engine first — AI is a UI layer on top of the same data model. Every AI-generated policy renders in the visual builder for user review. No black box. The user is always in control." This is a much better story than replacing rules with AI — we augment rule creation with AI.
When in doubt, block. If Troxy can't reach the API, it should not silently let payments through. The user can always manually approve via the dashboard. An unexpected charge is far worse than a delayed one. The only exception: if the user explicitly sets "fail_mode": "open" in their config.
Daemon error scenarios
| Scenario | Daemon behavior | Agent sees | User sees |
|---|---|---|---|
| api.troxy.ai unreachable | Retry 3x with 1s backoff → fail closed | Error: "Troxy unavailable — payment blocked for safety" | Dashboard shows daemon offline, Sentry alert fires |
| Token revoked (401) | Stop processing all calls immediately | Error: "Troxy token invalid — payments paused" | Email: "Your Troxy token was revoked. Generate a new one." |
| Daemon crashes | OS service manager restarts it (launchd/systemd) | Brief error, then recovers automatically | Dashboard shows brief offline blip |
| /evaluate timeout (>5s) | Cancel request, fail closed | Error: "Troxy evaluation timed out — payment blocked" | CloudWatch alarm fires, Sentry logs it |
| ESCALATE approval timeout (10min) | Return DECLINED to agent | Error: "Approval expired — payment declined" | Email: "Your approval request expired." |
Lambda (core-handler) error scenarios
| Scenario | Lambda behavior | HTTP response |
|---|---|---|
| RDS unreachable | Return 503, log to CloudWatch, Sentry captures it | 503 {"error":"service unavailable"} |
| Invalid request body | Validate all fields on entry, reject early | 400 {"error":"amount is required"} |
| AWS SES fails (email) | Log error, still return ESCALATE decision — email failure doesn't block the decision | 200 ESCALATE (email error logged separately) |
| Duplicate request_id | Return original decision from audit_log | 200 with original decision |
| Lambda cold start | Acceptable — first request may take 500–800ms. Not a problem for MVP. | Normal response, slightly delayed |
| Unhandled exception | Sentry captures full stack trace, CloudWatch logs it, Lambda returns 500 | 500 {"error":"internal error"} |
AWS services used for observability
| What | AWS service | Alert when |
|---|---|---|
| Lambda error rate | CloudWatch Alarm → SNS → email | Error rate > 1% in 5 minutes |
| Lambda duration | CloudWatch Alarm → SNS → email | p99 latency > 3 seconds |
| RDS CPU | CloudWatch Alarm → SNS → email | CPU > 80% for 5 minutes |
| RDS storage | CloudWatch Alarm → SNS → email | Free storage < 2GB |
| API Gateway 5xx | CloudWatch Alarm → SNS → email | Any 5xx response |
| Application errors | Sentry (free tier) | Any unhandled exception |
| Uptime | UptimeRobot → email + status.troxy.ai | api.troxy.ai/health returns non-200 |
Approval email content — exact template
This is what the ESCALATE email looks like. AWS SES uses this template. Nothing vague — Claude Code implements this exactly.
Subject: Action needed — your agent wants to spend $240.00 Your AI agent is requesting a payment: Agent: Shopping Agent Merchant: Marriott Hotels Amount: $240.00 USD Reason: 20% over your $200 travel budget Expires: In 10 minutes (09:45 PM) [ ✅ APPROVE ] [ ❌ DECLINE ] https://api.troxy.ai/approve/<id>?token=xxx&action=approve https://api.troxy.ai/approve/<id>?token=xxx&action=decline If you don't respond, this request will automatically expire. View your full activity log: https://dashboard.troxy.ai/activity — Troxy
Secrets Manager keys — exact names
All secrets use these exact key names. Lambda reads them by name from AWS Secrets Manager on cold start.
| Secret name (in Secrets Manager) | What it contains | Used by |
|---|---|---|
troxy/rds/password | PostgreSQL master password | core-handler, approval-webhook |
AWS SES — no secret needed | SES uses IAM role attached to Lambda. No API key. Just verify troxy.io domain in SES console once, then Terraform grants Lambda ses:SendEmail permission. | core-handler, approval-webhook |
troxy/approval/hmac_secret | HMAC-SHA256 key for signing approval tokens | core-handler, approval-webhook |
troxy/jwt/secret | JWT secret for dashboard session tokens | core-handler (dashboard auth) |
User signs up → logs in → generates API key in dashboard → runs npx troxy init --key txy-xxx → daemon starts and is immediately authenticated. The key is the connection. No chicken-and-egg problem.
Full onboarding flow — step by step
Step 1 — Sign up User visits dashboard.troxy.io → enters email → clicks "Send magic link" → AWS SES sends link → user clicks → JWT session created → logged in Step 2 — Generate API key Dashboard shows empty state: "Generate your first API key" → User clicks "New API key" → Names it (e.g. "My MacBook", "Work laptop", "CI server") → Key shown ONCE: txy-abc123xyz... → User copies it → stored safely (Bitwarden etc.) → Dashboard shows key in list: name, created date, last used, status Step 3 — Connect the machine npx troxy init --key txy-abc123xyz → Daemon authenticates with api.troxy.io using the key → MCP config rewritten automatically → Machine appears as "Connected" in dashboard Step 4 — Agent makes a payment Agent calls Stripe MCP → hits localhost:4242 (daemon) → Daemon calls api.troxy.io/evaluate with API key → Policy checked → decision made → Shows up in dashboard audit log in real time
API key management — what users can do
| Action | When | Notes |
|---|---|---|
| Create new key | MVP ✅ | Name it, shown once, copy it. One key per machine/environment. |
| List all keys | MVP ✅ | Name, created date, last used date, status (active/revoked). Never show full key again — only last 4 chars. |
| Revoke a key | MVP ✅ | Instant. Daemon using that key gets 401 on next call. Machine disconnects. |
| Rename a key | MVP ✅ | Just update the label. Useful when machines change. |
| Per-key permissions | V2 | e.g. "this key can only evaluate, not manage policies." Useful for CI/CD keys vs human keys. |
| Key expiry | V2 | Auto-expire keys after 90 days. Enterprise security requirement. |
| Team keys | V3 | Keys scoped to a team member, not the account owner. Multi-user orgs. |
Two token types — do not confuse
| Type | Format | Used by | Stored | Expires |
|---|---|---|---|---|
| Dashboard session | eyJ... (JWT) | Browser → dashboard API | localStorage | 7 days |
| API key | txy-xxx | Go daemon → core-handler | ~/.troxy/config.json + RDS bcrypt hash | Never (until revoked) |
API key format
# Format: txy-[32 random alphanumeric chars] # Example: txy-k8mN2pQxR7vL9wJ4nF6hD3cA1bE5gI0z # Storage in RDS (never store plaintext): api_keys ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), name TEXT, -- "My MacBook" key_prefix TEXT, -- "txy-k8mN" (first 8 chars for display) key_hash TEXT, -- bcrypt hash of full key last_used TIMESTAMPTZ, revoked_at TIMESTAMPTZ, created_at TIMESTAMPTZ )
Copy buttons — when key is shown after creation
When a user creates a new API key, the dashboard shows the key exactly once with two copy buttons side by side:
┌─────────────────────────────────────────────────────────────┐ │ Your API key — copy it now. It won't be shown again. │ │ │ │ txy-k8mN2pQxR7vL9wJ4nF6hD3cA1bE5gI0z │ │ │ │ [ Copy key ] [ Copy install command ] │ └─────────────────────────────────────────────────────────────┘ "Copy key" copies: txy-k8mN2pQxR7vL9wJ4nF6hD3cA1bE5gI0z "Copy install command" copies: npx troxy init --key txy-k8mN2pQxR7vL9wJ4nF6hD3cA1bE5gI0z
Most developers will want the full command — they just paste it in terminal and they're done. The "Copy key" button is for people who want to store it in Bitwarden or 1Password first. The install command button is the one-click path to getting started in 10 seconds.
Auth endpoints
POST /auth/magic-link ← send magic link (no auth required) POST /auth/verify ← verify token, return JWT (no auth required) GET /auth/me ← return current user info (JWT required) GET /tokens ← list all API keys (JWT required) POST /tokens ← create new API key (JWT required) → returns full key ONCE DELETE /tokens/:id ← revoke API key (JWT required) PATCH /tokens/:id ← rename API key (JWT required)
Auth roadmap
| Method | When | Why | Effort |
|---|---|---|---|
| Magic link | MVP ✅ | Simple, secure, no passwords | Done |
| Google OAuth | V2 | Most users want "Sign in with Google" | ~2 days |
| SAML / SSO | V3 — first enterprise customer | Okta, Azure AD, Google Workspace | ~1–2 weeks |
How it works — EventBridge + core-handler
EventBridge rule: cron(0 0 1 * ? *) ← midnight UTC, 1st of every month
Triggers: core-handler Lambda with special event body:
{ "type": "budget_reset" }
Lambda detects the event type and runs:
UPDATE card_aliases SET budget_used = 0 WHERE monthly_budget IS NOT NULL;
Log to CloudWatch: "Budget reset: X envelopes reset at {timestamp}"
Cost: $0 — EventBridge custom events are free, Lambda runs <1 second
Terraform for the EventBridge rule
resource "aws_cloudwatch_event_rule" "budget_reset" {
name = "troxy-budget-reset"
schedule_expression = "cron(0 0 1 * ? *)"
}
resource "aws_cloudwatch_event_target" "budget_reset" {
rule = aws_cloudwatch_event_rule.budget_reset.name
arn = aws_lambda_function.core_handler.arn
input = jsonencode({ "type": "budget_reset" })
}
resource "aws_lambda_permission" "eventbridge_budget" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.core_handler.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.budget_reset.arn
}
The schema has budget_reset_day per alias. For MVP: only support day 1. Run the cron on the 1st and reset all envelopes. Custom reset days (e.g. reset on the 15th per credit card billing cycle) require running a daily check — that's V2.