1. Discover: machine-readable pricing
slug and publicCatalogEnabled. An unknown
slug and a disabled catalog return the same 404.
2. Sign up: one call, no CAPTCHA
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):
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):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:
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).
max_amount per day, week or month, across all of the customer’s keys.
Ask for one at signup or later:
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:
- Stripe runs the billing (
STRIPE_DRIVENfor customer keys,STRIPE_INTEGRATIONfor every caller): Stripe charges the prorated amount during the call. A200means 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 answers402with Stripe’s invoice asurl; the plan moves when it is paid. An unpaid Stripe change expires after about 23 hours. Tenant (sk_) callers get202withstatus: "payment_pending"and the payment link instead of a402. - Tanso runs the billing: a customer-key upgrade raises the proration
invoice and answers
402; paying completes the change.
The gate envelope
Every402, 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 acreditQuote 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 —:
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:
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:
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-Keyheader: identical retries within 24h replay the stored response; a reused key with a different body returns409 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 withck_ 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. Thegovernance block in
pricing.json reports only what is true.