Plan Management
POST /api/organizations/:org_slug/services/:service_slug/plans
Create a new subscription plan for a service.
Permissions: Owner or Admin
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Plan name (e.g., “Pro”, “Enterprise”) |
description |
string | No | Optional plan description |
price_cents |
integer | Yes | Price in cents (e.g., 1999 for $19.99) |
currency |
string | Yes | Currency code (e.g., “usd”, “eur”) |
features |
string[] | No | List of features included in the plan |
stripe_price_id |
string | No | Optional Stripe price ID for billing |
is_default |
boolean | No | Whether this is the default plan for the service (default: false) |
Example Request:
curl -X POST https://sso.example.com/api/organizations/acme-corp/services/main-app/plans \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Pro",
"price_cents": 1999,
"currency": "usd",
"features": ["Advanced analytics", "Priority support", "Custom branding"]
}'
Example Response (200 OK):
{
"plan": {
"id": "plan-uuid",
"service_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Pro",
"price_cents": 1999,
"currency": "usd",
"features": ["Advanced analytics", "Priority support", "Custom branding"],
"created_at": "2025-01-15T10:30:00Z"
},
"subscription_count": 0
}
Error Responses:
401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not an owner or admin404 Not Found: Organization or service not found500 Internal Server Error: Database error
Notes:
- Price is stored in cents to avoid floating-point precision issues
- Features array is optional and can be empty
- New plans start with zero subscriptions
- Currency should be ISO 4217 code (lowercase)
- Plans can be used immediately after creation
GET /api/organizations/:org_slug/services/:service_slug/plans
List all plans for a service.
Permissions: Member
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
Example Request:
curl -X GET https://sso.example.com/api/organizations/acme-corp/services/main-app/plans \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example Response (200 OK):
[
{
"plan": {
"id": "free-plan-uuid",
"service_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Free",
"price_cents": 0,
"currency": "usd",
"features": [],
"created_at": "2025-01-15T10:30:00Z"
},
"subscription_count": 350
},
{
"plan": {
"id": "pro-plan-uuid",
"service_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Pro",
"price_cents": 1999,
"currency": "usd",
"features": ["Advanced analytics", "Priority support", "Custom branding"],
"created_at": "2025-01-15T11:00:00Z"
},
"subscription_count": 75
}
]
Error Responses:
401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not a member404 Not Found: Organization or service not found500 Internal Server Error: Database error
Notes:
- Returns all plans for the service
- Each plan includes active subscription count
- Plans are not ordered (consider sorting by price or name on client)
- All members can view plans
PATCH /api/organizations/:org_slug/services/:service_slug/plans/:plan_id
Update a subscription plan.
Permissions: Owner or Admin
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
plan_id |
string | Plan ID (UUID) |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | New plan name |
price_cents |
integer | No | New price in cents |
currency |
string | No | New currency code |
features |
string[] | No | Updated features list |
Example Request:
curl -X PATCH https://sso.example.com/api/organizations/acme-corp/services/main-app/plans/plan-uuid \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"price_cents": 2499,
"features": ["Advanced analytics", "Priority support", "Custom branding", "API access"]
}'
Example Response (200 OK):
{
"plan": {
"id": "plan-uuid",
"service_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Pro",
"price_cents": 2499,
"currency": "usd",
"features": ["Advanced analytics", "Priority support", "Custom branding", "API access"],
"created_at": "2025-01-15T10:30:00Z"
},
"subscription_count": 75
}
Error Responses:
400 Bad Request: No fields to update or plan belongs to different service401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not an owner or admin404 Not Found: Organization, service, or plan not found500 Internal Server Error: Database error
Notes:
- All fields are optional - only provided fields are updated
- At least one field must be provided
- Features array replaces existing features (not merged)
- Price changes affect new subscriptions only (existing subscriptions keep their price)
- Changes take effect immediately
DELETE /api/organizations/:org_slug/services/:service_slug/plans/:plan_id
Delete a subscription plan.
Permissions: Owner or Admin
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
plan_id |
string | Plan ID (UUID) |
Example Request:
curl -X DELETE https://sso.example.com/api/organizations/acme-corp/services/main-app/plans/plan-uuid \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example Response (204 No Content):
No response body.
Error Responses:
400 Bad Request: Plan has active subscriptions (must be cancelled first)401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not an owner or admin404 Not Found: Organization, service, or plan not found500 Internal Server Error: Database error
Notes:
- This is a destructive operation that cannot be undone
- Plan cannot be deleted if it has active subscriptions
- Consider archiving instead of deleting plans with historical subscriptions
- Users on this plan will need to be migrated to a different plan first
Subscription & Checkout
POST /api/organizations/:org_slug/services/:service_slug/checkout
Creates a Stripe checkout session for the authenticated user to subscribe to a plan.
Permissions: Any organization member
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Content-Type |
application/json |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
plan_id |
string | Yes | The plan ID to subscribe to |
success_url |
string | Yes | URL to redirect after successful checkout. Use {CHECKOUT_SESSION_ID} placeholder to include session ID |
cancel_url |
string | Yes | URL to redirect if checkout is cancelled |
Example Request:
curl -X POST https://sso.example.com/api/organizations/acme-corp/services/main-app/checkout \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"plan_id": "550e8400-e29b-41d4-a716-446655440000",
"success_url": "https://app.acme.com/billing/success?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://app.acme.com/billing/cancel"
}'
Success Response (200 OK):
{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3d4e5f6g7h8i9j0...",
"session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0"
}
Error Responses:
400 Bad Request: Plan not found, plan has no Stripe price, or plan doesn’t belong to service401 Unauthorized: Missing or invalid JWT token403 Forbidden: User is not a member of the organization404 Not Found: Organization or service not found500 Internal Server Error: Stripe API error or missing checkout URL
Notes:
- Organization Membership Required: User must be a member of the organization
- Active Organization: Organization must be in active status (not suspended or pending)
- Stripe Price Required: Plans must have a
stripe_price_idconfigured to be purchasable - Automatic Customer Creation: Stripe customers are automatically created per organization
- Metadata Tracking: Subscription metadata includes
user_id,service_id,plan_id, andorg_id