Skip to main content
The quickstart compose stack is the fastest path. This page covers what you’ll want to know past the first run.

Environment variables

Stripe: optional, and an adapter

Fresh accounts run without Stripe. Without it, the invoice lifecycle is fully functional — invoices generate at subscribe and cycle close, and you settle them via POST /api/v1/client/billing/invoices/{id}/mark-paid. Connecting a Stripe key (via the dashboard or the MCP Stripe setup tools) switches the account to Stripe-driven billing: checkout sessions, webhook sync, and meter forwarding. Billing state stays in Tanso either way — Stripe is the payment adapter, not the source of truth. A few things happen on their own once Stripe is connected:
  • Webhook events stay current. On startup Tanso adds any Stripe events it handles to its webhook destination in each connected Stripe account. It only adds events, never removes any. Turn it off with app.stripe-event-destination-sync-enabled=false, for example on a dev database whose accounts hold real Stripe keys.
  • Return pages. After a Stripe Checkout page, the human lands on /public/checkout/complete or /public/checkout/cancelled on your instance, unless the account sets stripeCheckoutSuccessUrl / stripeCheckoutCancelUrl.
  • Upgrades charge immediately. On Stripe-billed accounts an upgrade between in-advance plans charges the prorated amount during the call, and the plan moves once the payment succeeds. Downgrades take effect at the end of the period.

Behind a proxy

With the prod profile (forward-headers-strategy: framework), the per-IP agent signup cap reads the leftmost X-Forwarded-For entry. Your proxy or load balancer must overwrite that header, not append to it, otherwise a caller can pick its own IP and the per-IP cap stops working. The per-account cap holds either way.

Database & migrations

Liquibase applies the full schema on startup — an empty PostgreSQL is all you need. Schema changes ship as new changelog files only; existing changelogs are never edited, so upgrades are a restart.

Scheduled jobs

Invoice generation, subscription cycle rollover, cancellation processing, and credit expiration run as in-process scheduled jobs (ShedLock-guarded, so multiple instances won’t double-fire). Default crons live in application.yaml under jobs:. Internal spend adds three hourly jobs: vendorUsageSync (:15, re-pulls the last three days from each connected vendor), spendBudget (:30, checks every budget), and outcomeSync (:45, re-pulls merged PRs and completed issues).

Admin console

ui/ in the repo is a Next.js app covering the JWT admin surface — catalog, customers, subscriptions, credits, invoices, events, and margin analytics. npm run dev:ui runs it in development; for a production build, npm run build --workspace @tansohq/ui then npm run start --workspace @tansohq/ui. One variable matters: TANSO_BASE_URL, the API address the Next.js server proxies to (default http://localhost:8080). API calls go through that same-origin proxy, so the API’s CORS configuration needs no changes for the console.

Production checklist

  • Change the seeded test account credentials and API key — they are public knowledge (they’re in this documentation).
  • Set a strong, private JWT_SECRET; rotating it invalidates sessions.
  • Set APP_SECRETS_KEY and back it up outside the database — losing it means re-entering every stored credential.
  • The compose file ships Spring MVC and Hibernate logging at INFO and show_sql off. Do not raise them in production: at DEBUG, request bodies — including keys you paste into the console — are written to the logs.
  • Put the API behind TLS; API keys are bearer credentials.
  • docker compose in deploy/ is a starting point, not an HA architecture — the app is a standard Spring Boot container and runs anywhere containers run. Health probes are at /actuator/health/{liveness,readiness}.
  • Watch the logs for the first cycle close — billing bugs are cheapest the day you can still read the whole ledger by hand.