Skip to main content
Self-serve funnels assume hands and eyeballs — signup forms, checkout pages, email loops. An AI agent either completes your funnel programmatically or it doesn’t convert. Tanso gives the product you build on it an agent-facing surface out of the box: your customers’ buying agents can discover pricing, sign up, pay, and monitor usage without a human touching anything. Everything here is opt-in per account and fails closed: until you set a slug and flip the toggles, none of these surfaces exist.

1. Discover: machine-readable pricing

No authentication. Returns your ACTIVE plans (price, interval, features, included credits), the current credit weight table and price book, governance flags, and integration pointers, in the agent-serve pricing.json schema. Enable it in settings: set a slug and publicCatalogEnabled. An unknown slug and a disabled catalog return the same 404.

2. Sign up: one call, no CAPTCHA

Returns a customer (agent_… reference ID), a subscription to your designated free default plan, and a customer-scoped API key — once. Enable with agentSignupEnabled + agentSignupDefaultPlanId (validated free and ACTIVE at save). An hourly per-account cap and a per-IP cap each return 429 with Retry-After. Every field is optional. email is recorded as the owner contact: Tanso sends nothing to it, Stripe may send receipts and invoices to it. A signup can also ask for a spend mandate up front (see step 4):
An account created without an email is provisional: it works immediately, carries an expires_at, and is deleted if nothing is paid for by then. Paying claims it — there is no confirmation email and no claim link. The signup response carries status_url and owner_url, and a nextSteps map of the endpoints an agent needs next.

3. Scoped keys

Customer-scoped keys (ck_live_…/ck_test_…) are pinned to one customer and carry scopes: read (balances, entitlements, usage) and purchase (actions that spend money). Endpoints not deliberately opened to customer keys deny them — a leaked agent key cannot see other customers or touch tenant configuration. Tenants manage keys with their own sk_ key:

4. Pay: off-session with a saved card, 402 fallback without

Pre-authorize once (SetupIntent — card data never touches Tanso):
Subscribe (POST /api/v1/client/subscriptions) with a saved or supplied paymentMethodId charges off-session and returns the created subscription synchronously. Without one, customer-key callers get HTTP 402 (payment_required) carrying a hosted checkoutUrl for the principal plus a checkoutSessionId to poll:
Buy credits at the current price book rate:
Success grants a PURCHASED credit grant stamped at book price, idempotent by payment intent. The same 402 + polling fallback applies. Limits. Three ceilings are checked before money moves; a breach is a 403 naming which one it hit:
  • the operator’s per-charge cap (agentMaxTopupAmount), on every charge an agent starts, whether a saved card is charged or a human pays on a page;
  • the calling key’s budget, set by the operator;
  • the spend mandate, approved by the human (below).
The key budget and the mandate only block off-session charges, where Tanso charges a saved card with no human present. A hosted page where the human pays in person is never blocked by them. Hosted payments count toward the key’s budget but not toward the mandate. Spend mandate. The human’s standing approval for off-session charges: up to max_amount per day, week or month, across all of the customer’s keys. Ask for one at signup or later:
Hand setup_url to the human. The Stripe page shows the amount they are approving, saves their card, and returns them to a Tanso confirmation page. The operator must set a ceiling (agentMaxMandateAmount) before mandates can be turned on; asking above it is a 400 that names the limit. /status shows the mandate’s max_amount, spent, remaining and resets_at. When Tanso handles the billing and Stripe only collects, there is no checkout session. The 402 then carries the hosted invoice URL, and poll is the customer’s status URL: watch plan and status there instead. Changing plan on an existing subscription works the same way:
An upgrade that costs money never reaches a paid tier ahead of the payment:
  • Stripe runs the billing (STRIPE_DRIVEN for customer keys, STRIPE_INTEGRATION for every caller): Stripe charges the prorated amount during the call. A 200 means it was paid and the new plan is already active. With no card, a declined charge, or a Stripe account that emails invoices instead of charging cards, the call answers 402 with Stripe’s invoice as url; the plan moves when it is paid. An unpaid Stripe change expires after about 23 hours. Tenant (sk_) callers get 202 with status: "payment_pending" and the payment link instead of a 402.
  • Tanso runs the billing: a customer-key upgrade raises the proration invoice and answers 402; paying completes the change.
Asking twice returns the same invoice; aiming at a different plan voids the first one. A downgrade is scheduled for the end of the period and charges nothing.

The gate envelope

Every 402, and every 403 caused by a limit or an access rule, carries the same error object, so an agent can branch on fields instead of prose:

5. Use: pre-flight quotes and the burndown API

Entitlement checks return a creditQuote with estimated credits and cost — an agent can ask “what will this run cost me, and can I afford it” before doing the work. For the standing question — when do I run out —:
Per-feature current-period usage with a linear end-of-period projection, and per-pool credit balances with average daily burn, projected depletion date, and the current credit price. Plans the customer has left are reported too, marked status: "ended" with an endedAt, for the last year. A plan change ends the subscription the usage was recorded on, and that period still has to be auditable, so their usage is read from the events themselves and carries no limit or projection. For a window you choose, and for reconciling a total against the events behind it:
Grouped by subscription, feature and event name, with usageUnits, an events count and first and last timestamps per group. Optional featureKey and subscriptionId filters. The window defaults to the last 90 days and is capped at 366. To check a total against the records it was built from:
The events themselves, newest first, each with the idempotency key it was written under. Recorded usage is append-only — nothing edits an event, and a correction is another event — so what comes back is what was written.

Dev readiness

  • Every error carries a stable code (unauthorized, payment_required, insufficient_credits, idempotency_conflict, …) in one envelope shape — branch on codes, not messages.
  • Mutating client-API requests accept an Idempotency-Key header: identical retries within 24h replay the stored response; a reused key with a different body returns 409 idempotency_conflict.
  • OpenAPI at /v3/api-docs, Swagger UI at /swagger-ui.html.

MCP for customer agents

The MCP server includes a curated customer-facing tool set that works with ck_ keys: listPlans, getCreditPrices, checkEntitlement, getUsageForecast, subscribePlan, purchaseCredits. Spend tools require confirmAction: true. Tenant-configuration tools (Admin*, Stripe setup) are gated behind app.mcp.admin-tools.enabled (default false) so an agent key can never reconfigure your pricing.

Deliberately not yet

Rate-limit headers, outbound webhooks (usage thresholds, spend alerts), Web Bot Auth, and A2A agent cards are roadmap. The governance block in pricing.json reports only what is true.