Docs/Plans & Subscriptions

Plans & Subscriptions

Define pricing tiers, let users pick a plan through a hosted page, and gate features using the plan claim in the JWT. No billing code in your app — Astapa stores the plan as a custom claim and your app reads it from the token.

How it works

1
Create plans

Define plans in the dashboard or via API — free, pro, team, whatever you need

2
User picks a plan

Hosted plan selection page after login, or assign plans programmatically via API

3
Claim in JWT

Selected plan is stored as a plan custom claim in the access token

4
Gate features

Your app reads the JWT claims and controls access — no extra API calls needed

Plan structure

Each plan has these fields. Create them in the dashboard (Project → Plans tab) or via the API.

FieldDescription
plan_keyUnique slug (e.g. pro). Lowercase, alphanumeric, hyphens, underscores. 1–64 chars.
display_nameHuman-readable name shown to users (e.g. "Pro Plan")
pricePrice as a number (e.g. 9.99). Supports decimals.
currencyUSD or IDR
periodmonthly, yearly, or one_time
descriptionOptional description (max 512 chars)

Hosted plan selection

When your project has plans defined, Astapa automatically shows a plan selection page after login — before redirecting to your app. Zero code changes needed.

✓Works with email/password and OAuth flows
✓Users can skip plan selection
✓Shows "Current" badge if user already has a plan
✓Fully hosted — no UI code in your app
No plans? No page.
The plan selection page only appears when you have at least one plan defined. If you don't need plans, users go straight to your app after login.

Assigning plans programmatically

Need to assign plans from your backend? Use the Claims API to set the plan claim — perfect for payment webhooks, admin tools, or onboarding flows.

assign-plan.tstypescript
// After confirming payment, update the user's plan
await fetch("https://astapa.com/api/platform/claims", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    email: "user@example.com",
    claims: { plan: "pro" },
  }),
});

Feature gating with the JWT

After verifying the access token, read the plan claim to determine the user's tier. Your app decides what each plan level unlocks.

feature-gate.tstypescript
import jwt from "jsonwebtoken";

const decoded = jwt.verify(accessToken, publicKey, {
  algorithms: ["RS256"],
});

const plan = decoded.plan; // "free" | "pro" | "team" | undefined

if (plan === "pro" || plan === "team") {
  // Unlock premium features
} else {
  // Free tier or no plan
}

Common subscription patterns

Plans are stored as custom claims, so you can model various patterns beyond simple tiers:

ClaimValueUse case
plan"pro"Feature gating by tier
trial_ends"2026-04-15"Time-limited trial access
feature_flags"beta,export"Granular feature toggles
seats"10"Seat-based licensing

API reference

Plan CRUD (dashboard)

Plan CRUD (server-to-server)

Manage plans from your backend using client credentials.

User plan assignment

Claims management (server-to-server)

Next steps

API Playground
Click "Try it" on any endpoint to get started.
Plans & Subscriptions | Astapa