Schematic’s customer portal component is built to handle the full range of billing and entitlement scenarios that Schematic supports, from simple flat-rate plans to complex tiered pricing with usage-based billing, credits, and multi-currency support. The component is designed to be customized visually and dropped in to save your team time immediately, while automatically adapting as your pricing and packaging evolves.
That said, if you’d rather build your own portal to fully control the experience, this guide covers the APIs you’ll need and the functionality you’ll need to build to fully replace Schematic’s customer portal component.
Every endpoint requires a temporary access token, which your backend creates via the Schematic API (POST /temporary-access-tokens). The token is scoped to a specific company and is short-lived. Pass it to your frontend and include it on every request:
This returns the full billing state for the authenticated company in a single call. Key fields in the response:
Plans the company is currently on. Each includes name, description, pricing, entitlements, and features. The entry with current: true is their primary plan.
Active add-ons (same shape as plans).
Per-feature usage data: access status, current usage, allocation limits, pricing, credit info, and metric reset dates.
Current subscription: billing interval (month or year), currency, status, total price, trial end date, cancellation status, payment method, and active discounts.
Company info: name, all payment methods on file, default payment method, billing credit balances.
Active Schematic credit grants (usage-based units defined in your plan, not Stripe account balance) with quantities (total, used, remaining), source, renewal info, and expiration dates.
Credit bundles available for purchase.
If a downgrade is pending: from/to plan names, effective date, and price change.
Preview of the next invoice (amount due, due date).
The free/default plan for companies without a subscription.
Which plan the company will land on after their trial ends.
Whether a payment method is needed to start a trial.
If true, block self-service downgrade. Show prevent_self_service_downgrade_url to direct users to contact sales.
You can ignore the component, display_settings, checkout_settings, and deprecated show_* fields. Those control the behavior of Schematic’s hosted components and aren’t relevant when building your own UI.
Find the entry in active_plans where current is true:
To display the price, resolve it by period and currency (see Resolving prices below).
If subscription.trial_end is set and in the future, the company is on a trial. Show the trial end date and reference post_trial_plan for what happens next.
If scheduled_downgrade is present, show a notice: “Your plan will change from {from_plan_name} to {to_plan_name} on {effective_after}.”
The feature_usage.features array contains every feature the company has access to. Each entry includes:
feature.name (and feature.plural_name for plural context)access (boolean, whether the feature is currently available)entitlement_type ("boolean" for on/off flags, "numeric" for metered features)Boolean features just need a checkmark when access is true.
Numeric/metered features vary based on price_behavior:
Pre-computed helpers. The API returns several fields that simplify display: effective_limit (the resolved cap), effective_price (per-unit price for the current scenario), percent_used (0-100+), overuse (amount above soft limit), is_unlimited, and has_valid_allocation. These cover most display needs, though tiered pricing breakdowns require additional calculation on your end.
Reset periods. If period is set (current_day, current_week, current_month, current_year, or billing), usage resets on that cadence. metric_reset_at is the next reset timestamp.
Active add-ons are in active_add_ons. Each has a name, description, and charge_type ("recurring" or "one_time"). Resolve the price the same way as plans (see Resolving prices).
The credit_grants array represents Schematic-managed credits (usage-based units defined in your plan), not the customer’s Stripe account balance. For the Stripe account balance, see Customer balance.
Each entry includes:
credit_name, singular_name, plural_name for displayquantity (total granted), quantity_used, quantity_remainingsource_label (e.g. “Pro Plan”, “Purchased”)grant_reason ("plan_grant", "purchase", "promotion", etc.)renewal_enabled and renewal_period for auto-renewing grantsexpires_at (null if no expiry)Group credit grants by billing_credit_id to show per-credit-type balances.
The company.default_payment_method object (and company.payment_methods array for all methods) includes:
card_brand ("visa", "mastercard", etc.), card_last4, card_exp_month, card_exp_year for cardsbank_name, account_last4, account_name for bank accountspayment_method_type ("card", "us_bank_account", etc.)Returns an array of invoices, paginated with limit and offset. Each invoice includes amount_due, amount_paid, amount_remaining, currency, status, due_date, created_at, url (link to the Stripe hosted invoice page), and subtotal.
You’ll need to filter the results. Exclude invoices where:
status is void, draft, or uncollectibleamount_due is 0external_id starts with upcoming_due_date is in the futureSort by due_date (falling back to created_at) descending. Negative amount_due values represent credits.
Returns the customer’s Stripe account balance as a monetary amount in their billing currency (e.g. dollars for USD), representing balance adjustments from prorations, refunds, or other Stripe-level credits.
This is separate from credit grants used in credit based billing/entitlements, which are usage-based units defined in Schematic.
Your portal may need to let users update their payment method. This requires Stripe components to handle custom card data. Schematic never directly handles PCI data, relying instead on Stripe’s Payment Method ids:
Call POST /components/setup-intent to get a Stripe SetupIntent. The response includes setup_intent_client_secret, publishable_key, and optionally account_id.
Load Stripe.js with the publishable key. If account_id is present, you must pass it as stripeAccount when initializing Stripe. This is required when your Schematic account uses Stripe Connect.
PaymentElement and confirm the setup:No request body. The cancellation timing (immediate vs. end of period) is determined by your account’s configuration in Schematic.
Plans and add-ons can have prices in multiple currencies and for multiple billing periods (monthly, yearly). To resolve the correct price:
"month" or "year") and currencycurrency_prices array for a matching currency (case-insensitive)monthly_price or yearly_price based on the periodmonthly_price or yearly_priceFor add-ons, also check charge_type: use one_time_price for one-time charges instead of the period-based prices.
Price formatting: The price field is in smallest currency units (e.g. cents). Use price_decimal (a string) when available for fractional precision. Use Intl.NumberFormat with the currency code for proper locale formatting:
Some usage-based features use tiered pricing. The price_tier array on the price object defines the brackets. There are two modes:
Volume (tiers_mode is "volume"): find the tier the total quantity falls into, then multiply the full quantity by that tier’s per_unit_price, plus the tier’s flat_amount.
Graduated (default): accumulate cost across each tier bracket.
Each tier can also include a flat_amount that’s added once when that tier is entered. Use per_unit_price_decimal over per_unit_price when available.
Currency is locked to the subscription. Once a company has an active subscription, they can’t change currencies without cancelling and re-subscribing.
Invoice filtering is client-side. The invoices endpoint returns all invoices including drafts, voids, and zero-amount entries. You need to filter these yourself (see the invoices section above).
Price fields have two formats. price is an integer in the smallest currency unit (cents). price_decimal is a string with full decimal precision. Prefer price_decimal when available.
Credit grants can have multiple sources. A company might have plan-included credits, purchased credits, and promotional credits all at the same time. Group by billing_credit_id to show per-credit-type balances.
Stripe Connect requires stripeAccount. If the setup intent response includes an account_id, you must pass it as stripeAccount when loading Stripe.js. Without it, Stripe operations will fail.
Use active_plans for plan data, not company.plan. Both exist in the hydrate response, but active_plans is the richer version with full pricing and entitlement data.