Skip to content

Plans Admin

Endpoints

GET   /api/v1/admin/plans                  list (sorted by position)
POST  /api/v1/admin/plans                  create
PATCH /api/v1/admin/plans/:code            update
POST  /api/v1/admin/plans/:code/archive    archive (soft delete)

All require admin.

Plan fields

typescript
{
  code: string,                    // 2–40 chars, unique stable identifier
  name: string,                    // 1–80 chars, display name
  description: string,             // 0–500 chars
  position: number,                // sort order in pricing table
  is_featured: boolean,            // highlight on the upgrade page
  is_public: boolean,              // visible in the public pricing list
  is_active: boolean,              // available for new subscriptions
  trial_days: number | null,
  prices: {
    try: { monthly: number, yearly: number },
    usd?: { monthly: number, yearly: number } | null,
  },
  limits: {
    social_accounts: number,
    posts_per_month: number,       // -1 = unlimited
    ai_credits: number,
    team_members: number,
    storage_mb: number,
    scheduled_posts_max: number,
    marketplace_sales: number,
  },
  features: string[],              // human-readable bullet list
  metadata: Record<string, string>, // provider-specific refs (Stripe price IDs, etc.)
}

Provider metadata

When using Stripe, store the price IDs in metadata:

json
{
  "stripe_price_id_monthly": "price_1ABC...",
  "stripe_price_id_yearly": "price_1XYZ..."
}

The same pattern applies to Iyzico (iyzico_subscription_pricing_plan_*) and PayTR.

Archiving vs deleting

Archive (is_active: false + lifecycle hook) hides the plan from new signups but preserves existing subscribers. There is no hard delete to prevent accidental loss of historical data.