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.