API · 5 operations

Billing

Plans, checkout and the billing portal.

post /v1/billing/checkout

Start a Stripe Checkout for a plan; returns the URL for the human

scope budget:write · startCheckout

Body · JSON

  • plan "plus" | "household" | "founder" · required

  • period "month" | "year" · required · default "month"

  • attribution object

  • attribution.utm_source string · ≤ 200 chars

  • attribution.utm_medium string · ≤ 200 chars

  • attribution.utm_campaign string · ≤ 200 chars

  • attribution.utm_term string · ≤ 200 chars

  • attribution.utm_content string · ≤ 200 chars

  • attribution.gclid string · ≤ 200 chars

  • attribution.gbraid string · ≤ 200 chars

  • attribution.wbraid string · ≤ 200 chars

  • attribution.fbclid string · ≤ 200 chars

  • attribution.rdt_cid string · ≤ 200 chars

  • attribution.landing string · ≤ 200 chars

  • attribution.at integer · > 0

  • ad_consent "granted" | "denied"

Returns · 201

{ url, plan, period, mode }. mode is subscription for plus and household (30-day trial, card required), payment for the one-time founder seat, or switch (200, url: null) when the budget already subscribes and the live subscription moved to the other plan or the other period (monthly ↔ yearly), prorated. Give the URL to the human; when they finish, the webhook sets the plan and GET /v1/me reports it, so poll entitlements rather than assuming. Plus is for one person; Household is what a second person in the budget needs (entitlements.member_blocked). 409 billing_conflict when the tenant already holds that plan, when founder seats are gone, or when household is asked for on a key's personal tenant.

Example

curl -X POST 'https://api.mintro.money/v1/billing/checkout' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "plan": "plus", "period": "month" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

    billing_conflict: The billing state does not allow this: the tenant already holds that plan, founder seats are gone, or the extra-bank count would drop below the banks in use. The message says which; nothing to retry until the human acts.

  • 503

    billing_unavailable: Checkout and billing are not connected on this deployment yet. Tell the human; nothing to retry.

post /v1/billing/portal

A Stripe Customer Portal URL: cards, invoices, cancel, the add-on quantity

scope budget:write · openBillingPortal

Returns · 201

{ url }. Session principals only: a call made with an API key is 403; tell the human to open Settings → Plan. 404 not_found when the tenant has never paid.

Example

curl -X POST 'https://api.mintro.money/v1/billing/portal' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

  • 503

    billing_unavailable: Checkout and billing are not connected on this deployment yet. Tell the human; nothing to retry.

get /v1/billing/connections

Banks included, bought and in use, and what one more costs

scope budget:read · getBillingConnections

Returns · 200

{ included, extra, allowed, used, ceiling, addon: { price, price_cents }, can_buy }. allowed is included + extra (null when unlimited); can_buy is false on free, and on a granted or carried-over plan, which have no subscription to hang an add-on on. A founder can always buy: the add-on becomes a subscription of its own.

Example

curl 'https://api.mintro.money/v1/billing/connections' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

put /v1/billing/connections

Set how many extra banks the tenant pays for (session principals only)

scope budget:write · setExtraConnections

Body · JSON

  • extra integer · required · ≥ 0 · ≤ 25

Returns · 200

{ included, extra, allowed, used, ceiling, pending, url }. Sets the quantity of the connection add-on on the Stripe subscription, prorated; extra lands on the tenant when Stripe confirms it (the webhook), so read GET again rather than trusting the echo. A founder with no subscription yet gets 201 and url, a Stripe Checkout page for the add-on alone. Send the human there; url is null otherwise. 409 billing_conflict when lowering below the banks in use (close some first) or when the tenant has no subscription to add to; 403 with an API key.

Example

curl -X PUT 'https://api.mintro.money/v1/billing/connections' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "extra": 0 }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

    billing_conflict: The billing state does not allow this: the tenant already holds that plan, founder seats are gone, or the extra-bank count would drop below the banks in use. The message says which; nothing to retry until the human acts.

  • 503

    billing_unavailable: Checkout and billing are not connected on this deployment yet. Tell the human; nothing to retry.

get /v1/plans

Every plan, what it unlocks, its price and its included bank connections

no key needed · listPlans

Returns · 200

{ plans: [{ id, label, price, price_annual, payer_type, connections_included, manual_accounts, features[], purchasable }], features: { <feature>: { label, line } }, connection_addon: { price, price_cents, ceiling }, founder_seats: { total, taken }, free_limits }. The one plan table: the app, the pricing page and any agent read the same bytes. Features are bank_sync, goals, assets, webhooks; every operation they gate carries x-mintro-feature in this document. manual_accounts: null means unlimited. Budgets and envelopes are never limited by the plan. One more bank on a paid plan is the connection add-on, up to the ceiling.

Example

curl 'https://api.mintro.money/v1/plans'