InkApex docs
Guides

Billing

Plans in makev1.config.ts, five payment providers, and webhooks that are applied exactly once.

Plans

Plans live in makev1.config.ts under billing.plans. The API, the dashboard and the pricing page all read that one list.

{
  id: 'pro',
  name: 'Pro',
  description: 'For growing teams.',
  limits: { projects: 50, members: null }, // null is unlimited
  features: ['api-keys', 'audit-log'],
  perSeat: true,
  trialDays: 14,
  highlighted: true,
  prices: [
    { key: 'pro-monthly', interval: 'month', amount: 1200, currency: 'usd',
      ids: { stripe: { test: 'price_…', live: 'price_…' } } },
  ],
}
  • A plan with no prices is a free plan. It must come first, and it is optional.
  • Without a free plan the product is paid only: an organization subscribes (a trial counts) before it can use anything except billing, API keys and the audit log.
  • Prices can be monthly, yearly or one-time (once, a lifetime deal).

Providers

Turn providers on with BILLING_PROVIDERS=stripe,lemonsqueezy and their keys. Stripe and Lemon Squeezy sit behind one interface, and each is Beta until verified in its sandbox.

Paddle, Polar and PayPal use the same interface: add paddle, polar or paypal to the list with their keys. PayPal has no customer portal, so the dashboard hides "Manage billing" for PayPal subscriptions. Paddle checkout opens on your default payment link, which is the dashboard's /pay page.

Webhooks

Register {API_URL}/api/v1/billing/webhooks/<provider> with the events listed in .env.example. Only signed webhooks change what an organization has. A retried delivery is ignored, and an older event arriving late cannot undo a newer one.

Limits in your code

await assertWithinLimit(db, organizationId, 'projects', currentCount); // 402 PLAN_LIMIT_REACHED
const plan = await entitlementsFor(db, organizationId); // gate features on plan.features

On this page