Service Management
Comprehensive API for managing services and subscription plans within organizations, including CRUD operations, plan management, and service limits enforcement.
Overview
Services are applications that belong to an organization and use AuthOS for authentication. Each service has:
- Client ID: Unique identifier for OAuth2 flows
- Service Type: Categorization (web, mobile, desktop, api)
- OAuth Scopes: Custom scopes for GitHub, Google, and Microsoft providers
- Redirect URIs: Allowed callback URLs for OAuth2 flows
- Device Activation URI: Optional URI for device flow authentication
- Plans: Subscription tiers with pricing and features
- Usage Tracking: Monitor plan and subscription counts
Organizations have service limits based on their tier. When a service is created, a default “Free” plan is automatically provisioned.
Data Models
Service Model
{
"id": "uuid",
"org_id": "uuid",
"slug": "unique-service-identifier",
"name": "Service Name",
"service_type": "web|mobile|desktop|api",
"client_id": "uuid",
"github_scopes": ["user:email", "read:user"],
"microsoft_scopes": ["openid", "profile", "email"],
"google_scopes": ["openid", "email", "profile"],
"redirect_uris": ["https://app.example.com/callback"],
"device_activation_uri": "https://app.example.com/device",
"created_at": "datetime"
}
Plan Model
{
"id": "uuid",
"service_id": "uuid",
"name": "Plan Name",
"price_cents": 1999,
"currency": "usd",
"features": ["feature1", "feature2"],
"stripe_price_id": "price_1234567890 (optional)",
"created_at": "datetime"
}
Service with Details
{
"id": "uuid",
"org_id": "uuid",
"slug": "main-app",
"name": "Main Application",
"service_type": "web",
"client_id": "550e8400-e29b-41d4-a716-446655440000",
"github_scopes": ["user:email"],
"microsoft_scopes": ["openid", "email"],
"google_scopes": ["openid", "email"],
"redirect_uris": ["https://app.example.com/callback"],
"device_activation_uri": null,
"created_at": "2025-01-15T10:30:00Z",
"plan_count": 3,
"subscription_count": 150
}
Endpoints Summary
| Method | Path | Description | Permissions |
|---|---|---|---|
| POST | /api/organizations/:org_slug/services |
Create new service | Owner/Admin |
| GET | /api/organizations/:org_slug/services |
List organization services | Member |
| GET | /api/organizations/:org_slug/services/:service_slug |
Get service details | Member |
| PATCH | /api/organizations/:org_slug/services/:service_slug |
Update service | Owner/Admin |
| DELETE | /api/organizations/:org_slug/services/:service_slug |
Delete service | Owner |
| POST | /api/organizations/:org_slug/services/:service_slug/plans |
Create plan | Owner/Admin |
| GET | /api/organizations/:org_slug/services/:service_slug/plans |
List service plans | Member |
| PATCH | /api/organizations/:org_slug/services/:service_slug/plans/:plan_id |
Update plan | Owner/Admin |
| DELETE | /api/organizations/:org_slug/services/:service_slug/plans/:plan_id |
Delete plan | Owner/Admin |
Service Operations
POST /api/organizations/:org_slug/services
Create a new service with automatic plan provisioning.
Permissions: Owner or Admin
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
slug |
string | Yes | Unique URL-friendly service identifier |
name |
string | Yes | Service display name |
service_type |
string | Yes | Service type: web, mobile, desktop, or api |
github_scopes |
string[] | No | GitHub OAuth scopes (e.g., ["user:email", "read:user"]) |
microsoft_scopes |
string[] | No | Microsoft OAuth scopes (e.g., ["openid", "email", "profile"]) |
google_scopes |
string[] | No | Google OAuth scopes (e.g., ["openid", "email", "profile"]) |
redirect_uris |
string[] | No | Allowed OAuth callback URLs |
device_activation_uri |
string | No | Device flow activation page URL |
Example Request:
curl -X POST https://sso.example.com/api/organizations/acme-corp/services \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"slug": "main-app",
"name": "Main Application",
"service_type": "web",
"github_scopes": ["user:email", "read:user"],
"microsoft_scopes": ["openid", "email", "profile"],
"google_scopes": ["openid", "email", "profile"],
"redirect_uris": ["https://app.acme.com/callback"],
"device_activation_uri": "https://app.acme.com/device"
}'
Example Response (200 OK):
{
"service": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"org_id": "org-uuid",
"slug": "main-app",
"name": "Main Application",
"service_type": "web",
"client_id": "client-uuid",
"github_scopes": ["user:email", "read:user"],
"microsoft_scopes": ["openid", "email", "profile"],
"google_scopes": ["openid", "email", "profile"],
"redirect_uris": ["https://app.acme.com/callback"],
"device_activation_uri": "https://app.acme.com/device",
"created_at": "2025-01-15T10:30:00Z"
},
"default_plan": {
"id": "plan-uuid",
"service_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Free",
"price_cents": 0,
"currency": "usd",
"features": [],
"created_at": "2025-01-15T10:30:00Z"
},
"usage": {
"current_services": 1,
"max_services": 5,
"tier": "Free Tier"
}
}
Error Responses:
400 Bad Request: Invalid service type, service limit reached, or validation error401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not an owner or admin, or organization not active404 Not Found: Organization not found500 Internal Server Error: Database error
Notes:
- Valid service types:
web,mobile,desktop,api - A unique
client_idis automatically generated - A default “Free” plan is automatically created
- Service count is checked against organization tier limits
- An audit log entry is created
- Scopes and redirect URIs are optional but recommended for OAuth flows
- Organization must be in
activestatus
Service Type Guidelines:
- web: Browser-based web applications
- mobile: iOS and Android native apps
- desktop: Native desktop applications (Windows, macOS, Linux)
- api: Backend services and APIs
GET /api/organizations/:org_slug/services
List all services for an organization with usage statistics.
Permissions: Member
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
service_type |
string | No | Filter by service type: web, mobile, desktop, api |
limit |
integer | No | Max services to return (default: no limit) |
offset |
integer | No | Number of services to skip (default: 0) |
Example Request:
curl -X GET "https://sso.example.com/api/organizations/acme-corp/services?service_type=web&limit=20&offset=0" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example Response (200 OK):
{
"services": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"org_id": "org-uuid",
"slug": "main-app",
"name": "Main Application",
"service_type": "web",
"client_id": "client-uuid",
"github_scopes": ["user:email"],
"microsoft_scopes": ["openid", "email"],
"google_scopes": ["openid", "email"],
"redirect_uris": ["https://app.acme.com/callback"],
"device_activation_uri": null,
"created_at": "2025-01-15T10:30:00Z",
"plan_count": 3,
"subscription_count": 150
},
{
"id": "another-service-uuid",
"org_id": "org-uuid",
"slug": "mobile-app",
"name": "Mobile Application",
"service_type": "mobile",
"client_id": "another-client-uuid",
"github_scopes": null,
"microsoft_scopes": ["openid"],
"google_scopes": ["openid"],
"redirect_uris": ["myapp://callback"],
"device_activation_uri": "https://app.acme.com/activate",
"created_at": "2025-01-16T14:20:00Z",
"plan_count": 2,
"subscription_count": 75
}
],
"usage": {
"current_services": 2,
"max_services": 5,
"tier": "Free Tier"
}
}
Error Responses:
401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not a member404 Not Found: Organization not found500 Internal Server Error: Database error
Notes:
- Returns all services the user has access to within the organization
- Each service includes plan count and active subscription count
- Usage information shows current service count vs. tier limits
- Results can be filtered by service type
- Pagination supported via
limitandoffset
GET /api/organizations/:org_slug/services/:service_slug
Get detailed information about a specific 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 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"org_id": "org-uuid",
"slug": "main-app",
"name": "Main Application",
"service_type": "web",
"client_id": "client-uuid",
"github_scopes": ["user:email", "read:user"],
"microsoft_scopes": ["openid", "email", "profile"],
"google_scopes": ["openid", "email", "profile"],
"redirect_uris": ["https://app.acme.com/callback"],
"device_activation_uri": "https://app.acme.com/device",
"created_at": "2025-01-15T10:30:00Z"
}
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 full service configuration
- All members can view service details
- Use this endpoint to retrieve OAuth configuration for integrations
PATCH /api/organizations/:org_slug/services/:service_slug
Update service configuration.
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 | No | New service name |
service_type |
string | No | New service type: web, mobile, desktop, api |
github_scopes |
string[] | No | Updated GitHub OAuth scopes |
microsoft_scopes |
string[] | No | Updated Microsoft OAuth scopes |
google_scopes |
string[] | No | Updated Google OAuth scopes |
redirect_uris |
string[] | No | Updated redirect URIs |
device_activation_uri |
string | No | Updated device activation URI |
Example Request:
curl -X PATCH https://sso.example.com/api/organizations/acme-corp/services/main-app \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Main Application (Production)",
"redirect_uris": ["https://app.acme.com/callback", "https://app.acme.com/oauth/callback"]
}'
Example Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"org_id": "org-uuid",
"slug": "main-app",
"name": "Main Application (Production)",
"service_type": "web",
"client_id": "client-uuid",
"github_scopes": ["user:email", "read:user"],
"microsoft_scopes": ["openid", "email", "profile"],
"google_scopes": ["openid", "email", "profile"],
"redirect_uris": ["https://app.acme.com/callback", "https://app.acme.com/oauth/callback"],
"device_activation_uri": "https://app.acme.com/device",
"created_at": "2025-01-15T10:30:00Z"
}
Error Responses:
400 Bad Request: Invalid service type or no fields to update401 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:
- All fields are optional - only provided fields are updated
- Service slug and client_id cannot be changed
- Valid service types:
web,mobile,desktop,api - At least one field must be provided
- Scopes and redirect URIs are completely replaced (not merged)
- Changes take effect immediately for new authentication flows
DELETE /api/organizations/:org_slug/services/:service_slug
Delete a service and all associated data.
Permissions: Owner only
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 DELETE https://sso.example.com/api/organizations/acme-corp/services/main-app \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example Response (204 No Content):
No response body.
Error Responses:
400 Bad Request: Service has active subscriptions (must be cancelled first)401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not the owner404 Not Found: Organization or service not found500 Internal Server Error: Database error
Notes:
- This is a destructive operation that cannot be undone
- Service cannot be deleted if it has active subscriptions
- Cascading deletes remove: plans, API keys, OAuth tokens, login events
- Only organization owners can delete services
- All users will lose access immediately
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”) |
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 |
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
Security Considerations
Service Limits:
- Organizations have service limits based on their tier
- Free tier typically allows 5 services
- Limits can be customized per organization by Platform Owners
- Service creation fails if limit is reached
Access Control:
- Only owners and admins can create/modify services and plans
- Only owners can delete services
- All members can view services and plans
- Organization must be
activefor service operations
Validation:
- Service type must be one of:
web,mobile,desktop,api - Service slug must be unique within the organization
- Plan price must be non-negative
- Currency should be valid ISO 4217 code
OAuth Configuration:
- Redirect URIs should use HTTPS in production
- Scopes should match provider documentation
- Device activation URI is optional for CLI/device flows
- Multiple redirect URIs supported for different environments
Plan Management:
- Plans with active subscriptions cannot be deleted
- Price changes don’t affect existing subscriptions
- Features are informational only (not enforced by the platform)
- Consider business logic implications before modifying plans
Audit Logging:
- Service creation logged with all configuration details
- Service updates and deletions logged
- Plan modifications logged
- Actor and timestamp tracked for all operations
Best Practices:
- Use descriptive service names and slugs
- Configure appropriate OAuth scopes for your use case
- Set up separate services for development and production
- Review subscription counts before deleting plans
- Test OAuth flows after creating/updating services
- Use semantic versioning in service names if versioning services