Billing and subscriptions
View pricing and plans, manage the free trial and subscription, add and update payment methods, and download invoices for the hosted SenseCrypt service.
Billing is an account-level concern on the hosted SenseCrypt service: the account you created at sign-up is the entity that carries the trial, the subscription, the saved card, and the invoices. This guide is for the account owner (or a delegated admin with billing access) working in the admin console. It covers viewing pricing, understanding the trial and subscription lifecycle, adding and managing payment methods, and downloading invoices.
This applies only to the hosted SenseCrypt service. If you run SenseCrypt yourself (self-hosted), there is no billing surface at all — you manage your own infrastructure and the entire Stripe integration stays dormant. See Self-hosting. The rest of this page assumes the managed, hosted service.
Before you start. Reading the billing surface (subscription, saved cards, invoices) needs the billing:read capability; changing anything (adding or switching a card, closing the account) needs billing:manage. The account owner holds both implicitly. Closing or reopening the account is owner-only — a delegated billing:manage admin can manage payment but cannot delete the account.
How billing works
SenseCrypt bills per account, usage-based, monthly. Usage is metered per tenant and summed across all the tenants your account owns, so one card and one invoice cover everything under the account. There are three billable dimensions:
- Workforce seats — one per provisioned workforce seat, volume-priced.
- External MAU — one per monthly-active external user, on its own (separate) volume curve.
- KMS keys — a flat monthly fee per active KMS-custody signing key, with no minimum.
Pricing is volume-based: the more of a dimension your account totals, the lower the per-unit rate for that dimension, applied uniformly to every tenant's line. A workforce-seat minimum is always committed (10 seats by default) — if your account occupies fewer, a top-up line bills the shortfall. External MAU and KMS keys never count toward that seat floor.
Every new account starts on a 30-day free trial with full features and unlimited usage at $0 — no card required to begin, and no trial usage is ever billed. Add a payment method before the trial ends to convert to a paid subscription seamlessly.
Card data never touches SenseCrypt. Cards are collected by Stripe's embedded Payment Element and stored at Stripe against your account's Stripe customer. SenseCrypt only ever sees non-sensitive metadata (brand, last four digits, expiry). There is no Stripe-hosted Checkout and no Customer Portal — card entry and all card management happen in-app.
Viewing pricing and plans
The live rate schedule is public and config-derived — the same numbers power the pre-login pricing page, the in-app rate schedule, and the invoice math. Fetch it any time from GET {tenant-host}/v1/billing/pricing (no authentication), or read it in the console.
Open the admin console and go to Billing.
The Rate schedule panel lists the workforce-seat and external-MAU volume bands, the per-KMS-key fee, the committed seat minimum, the currency, and the free-trial length. Your account's current volume band is highlighted.
Screenshot: the Billing page Rate schedule, showing the workforce and external volume bands with the account's current band highlighted, the KMS-key fee, and the seat minimum.
The trial and subscription lifecycle
Your account is always in exactly one billing state. GET {tenant-host}/v1/admin/billing/subscription is the single source of truth, and the Billing page renders it as a status ribbon:
| Status | What it means | End-user sign-ins | Console |
|---|---|---|---|
trialing | On the free trial | Work | Full access |
active | Paid subscription, card on file | Work | Full access |
past_due | Trial lapsed with no card, or a payment ultimately failed | Keep working | Login and pay open; creating or growing anything is blocked |
pending_deletion | Self-service deletion requested | Blocked (403) | Billing, session, and auth only |
The subscription payload also carries a live cost estimate for the open period at your current usage and rates (estimate_lines + estimate_total, in the currency's minor units — cents), plus a per-tenant usage breakdown, so you can see what the next invoice will look like before it is cut.
Screenshot: the Billing page status ribbon (the gold "Free trial" ribbon with days remaining) above the live cost estimate and per-tenant usage breakdown.
Add a card before the trial ends. If the trial lapses with no payment method, the account moves to past_due: end-user OIDC/SAML sign-ins keep working (non-payment never breaks production SSO), but any attempt to create or grow — a new app, user, or key — is refused with 402 payment_required until you add a card. Non-payment has a long grace window before an account is purged, but the create/grow block bites immediately.
When a card is on file at trial end, SenseCrypt charges it immediately for the first period's seats, KMS keys, and seat minimum (external MAU is billed in arrears, and trial MAU is free). Adding a card during a live trial does not end the trial early — the trial is honored to its end date.
Adding a payment method
Cards are added through Stripe's embedded Payment Element, rendered directly in the Billing page — there is no redirect to a hosted checkout.
On the Billing page, click Add payment method. The console requests a Stripe SetupIntent (POST /v1/admin/billing/setup-intent) and renders the embedded Payment Element in place.
Enter the card details and confirm. Stripe saves the card against your account's customer and makes it the default invoice card. The console then refreshes the subscription so the saved card and any state change (for example, recovering from past_due) appear immediately.
Screenshot: the Add payment method panel with the embedded Stripe Payment Element open in the Billing page.
Indian cards and the RBI e-mandate. For a card issued in India, the same add-card form additionally completes on-session authentication (AFA/3DS) and registers an RBI e-mandate so future invoices can be auto-debited off-session. The saved card shows an "auto-pay on" indicator (mandate_active) once the mandate is active. Cards from every other country are unaffected. A charge above the mandate cap or the RBI AFA threshold needs a one-time on-session confirmation — see the "Confirm your payment" banner below.
Managing saved cards
The Billing page lists every saved card with its brand, last four digits, expiry, default flag, and (for Indian cards) mandate status. From the list you can:
Set a different card as default — the default is the card future invoices are charged to.
Remove a card you no longer want on file.
Screenshot: the saved-cards list showing brand, last four digits, expiry, the default badge, and an "auto-pay on" indicator on a mandated card.
Payment-health banners
While an account is active, three finer signals surface as a banner before any hard suspension, so you can fix the card while everything still works:
- "Payment failed — update your payment method" — a charge failed but the account is still
active. Add or switch to a working card; the next successful charge clears it. - "Confirm your payment" — the charge is not a decline but needs on-session authentication (3D Secure / OTP), typically an Indian e-mandate charge over the AFA threshold or mandate cap. Follow the link (Stripe's hosted invoice page) to authenticate; the banner clears once the invoice is paid.
- "Card expiring soon" / "Card expired" — surfaced from the saved card's expiry before a charge ever fails. Add or replace the card in-app.
Viewing and downloading invoices
Past invoices are listed on the Billing page from GET {tenant-host}/v1/admin/billing/invoices. Each invoice is itemized per tenant (one line per tenant × dimension) plus the seat-minimum top-up line when seats are under the floor.
Open Billing → Invoices (the billing-history table).
For any invoice, open the hosted invoice page or download the PDF — both are Stripe-hosted links (hosted_url / pdf_url) returned with each row.
Screenshot: the Invoices history table listing invoice number, period, total, status, and the PDF / hosted-invoice links.
Closing or reopening the account
Cancelling the subscription is closing the account — there is no separate "downgrade" or "pause". The account owner can self-delete (a soft delete with a 30-day grace window) and reactivate within that window from the same Billing page. Because deletion tears down every tenant the account owns, it is documented separately.
Account deletion is owner-only, requires typing the account email to confirm, and is destructive when the grace window expires. See Account lifecycle and deletion for the full flow, the 423 console block during the grace window, and what the final purge erases.
Using the Management API
Everything above is done in the admin console. The read and payment-management endpoints also live on the tenant Management API at {tenant-host}/v1/admin/billing/*, driven with a machine-to-machine (M2M) access token passed as Authorization: Bearer {mgmtToken}. Reads need a token carrying billing:read; mutations need billing:manage. See Machine-to-machine for how to mint {mgmtToken}.
Billing is fundamentally a console-operator task, and two things have no headless path. (1) Adding a card cannot be completed via curl — POST /setup-intent only bootstraps the flow; the returned client_secret must be confirmed by Stripe.js in a browser (the embedded Payment Element). There is no Stripe-hosted Checkout to redirect to. (2) Closing or reopening the account is owner-only — POST /account/delete and POST /account/reactivate require the account owner's authenticated console session and reject M2M service tokens. Also note an M2M token is bound to one tenant, so GET /subscription returns the per-tenant usage breakdown for only that tenant; the account-level status, trial, and payment fields are always complete.
The public pricing endpoint needs no auth:
curl {tenant-host}/v1/billing/pricing{
"currency": "usd",
"trial_days": 30,
"min_users": 10,
"kms_key_price_cents": 2000,
"workforce_bands": [
{ "up_to": 50, "cents": 100 },
{ "up_to": 500, "cents": 75 },
{ "up_to": null, "cents": 50 }
],
"external_bands": [
{ "up_to": 1000, "cents": 50 },
{ "up_to": 10000, "cents": 30 },
{ "up_to": null, "cents": 20 }
]
}up_to: null is the unbounded top band; cents is the per-unit rate. (These are the shipped defaults; the live values come from the operator's configuration.)
Read the subscription, usage, and estimate
GET {tenant-host}/v1/admin/billing/subscription (needs billing:read):
curl {tenant-host}/v1/admin/billing/subscription \
-H "Authorization: Bearer {mgmtToken}"{
"status": "trialing",
"trial_end": "2026-08-15T00:00:00Z",
"trial_days_left": 30,
"current_period_start": null,
"current_period_end": null,
"has_payment_method": false,
"payment_failed_at": null,
"payment_retry_at": null,
"payment_action_required_at": null,
"payment_action_url": null,
"card_exp_month": null,
"card_exp_year": null,
"suspended_at": null,
"deletion_requested_at": null,
"purge_date": null,
"per_tenant": [
{ "tenant_id": "6f9c1e2a-3b4d-4c5e-8a9b-0c1d2e3f4a5b", "name": "Default", "seats": 3, "mau": 120, "kms": 0 }
],
"totals": { "workforce": 3, "external": 120, "kms": 0 },
"estimate_lines": [
{ "description": "Default · Workforce seats", "quantity": 3, "unit_amount": 100, "amount": 300 },
{ "description": "Default · External users", "quantity": 120, "unit_amount": 50, "amount": 6000 },
{ "description": "Minimum commitment (10 seats)", "quantity": 7, "unit_amount": 100, "amount": 700 }
],
"estimate_total": 7000
}All amounts are in the currency's minor units (cents). The example above shows the seat-minimum top-up: 3 occupied seats bill 7 extra to reach the 10-seat floor.
List, set-default, and remove saved cards
GET {tenant-host}/v1/admin/billing/payment-methods (needs billing:read) returns the saved cards in the standard list envelope:
curl {tenant-host}/v1/admin/billing/payment-methods \
-H "Authorization: Bearer {mgmtToken}"{
"items": [
{
"id": "pm_1AbCdEf...",
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2030,
"is_default": true,
"is_india": false,
"mandate_active": false
}
],
"total": 1,
"limit": 1,
"offset": 0
}Make a saved card the default (needs billing:manage; returns 204 No Content):
curl -X POST {tenant-host}/v1/admin/billing/payment-methods/{pm_id}/default \
-H "Authorization: Bearer {mgmtToken}"Remove a saved card (needs billing:manage; returns 204 No Content):
curl -X DELETE {tenant-host}/v1/admin/billing/payment-methods/{pm_id} \
-H "Authorization: Bearer {mgmtToken}"Both operations verify the card belongs to your account's Stripe customer; an unrecognized {pm_id} returns 404 with no existence leak.
List invoices
GET {tenant-host}/v1/admin/billing/invoices (needs billing:read):
curl {tenant-host}/v1/admin/billing/invoices \
-H "Authorization: Bearer {mgmtToken}"{
"items": [
{
"number": "ABCD-0001",
"created": "2026-08-15T00:00:00Z",
"period_start": "2026-08-15T00:00:00Z",
"period_end": "2026-09-15T00:00:00Z",
"total": 7000,
"currency": "usd",
"status": "paid",
"pdf_url": "https://pay.stripe.com/invoice/.../pdf",
"hosted_url": "https://invoice.stripe.com/i/..."
}
],
"total": 1,
"limit": 1,
"offset": 0
}pdf_url and hosted_url are Stripe-hosted per-invoice links; total is in the currency's minor units and status is Stripe's (draft / open / paid / void / uncollectible).
Learn more
Account lifecycle and deletion
Machine-to-machine
{mgmtToken} used for the Management-API paths above.Multi-tenancy & issuers
Error codes
Account lifecycle and deletion
How a SenseCrypt account is closed and reopened — owner-only self-delete with a 30-day grace, the 423 block on the admin console during that window, what reactivation restores, and the crypto-shred and data erasure that happen on final deletion.
Rotating signing keys
Operator how-to for rotating a tenant's OIDC and SAML signing keys — one-shot OIDC rotate vs make-before-break SAML staging, the overlap (grace) windows, staged certs by kid, KMS vs software custody, and what your relying parties and service providers must do during the overlap.