Service API (API Key Authentication)
The Service API provides secure service-to-service endpoints for managing users, subscriptions, and analytics using API key authentication. These endpoints enable backend services to perform administrative operations without requiring end-user authentication.
Overview
The Service API enables backend services to:
- User Management: List, create, read, update, and delete users who have authenticated with your service
- Subscription Management: Manage user subscriptions and access levels
- Analytics: Access service-specific usage metrics and statistics
- Service Configuration: Update service information and settings
All Service API endpoints use API key authentication instead of JWT tokens, making them ideal for server-to-server communication.
Authentication
API Key Authentication
All Service API endpoints require authentication via API key in the X-API-Key header:
curl -X GET https://sso.example.com/api/service/users \
-H "X-API-Key: sk_live_abc123xyz..."
API Key Format
API keys follow the format: sk_{environment}_{random}
sk_test_...: Test/development environment keyssk_live_...: Production environment keys
Obtaining API Keys
API keys are generated via the Organization Management API:
POST /api/organizations/:org_slug/services/:service_slug/api-keys
See API Keys Documentation for details on generating and managing keys.
Permissions Model
Each API key has an associated set of permissions that control access to endpoints:
| Permission | Description | Endpoints Enabled |
|---|---|---|
read:users |
Read user data | GET /api/service/users, GET /api/service/users/:user_id |
write:users |
Create/update users | POST /api/service/users, PATCH /api/service/users/:user_id |
delete:users |
Delete users | DELETE /api/service/users/:user_id |
read:subscriptions |
Read subscription data | GET /api/service/subscriptions, GET /api/service/subscriptions/:user_id |
write:subscriptions |
Create/update subscriptions | POST /api/service/subscriptions, PATCH /api/service/subscriptions/:user_id |
delete:subscriptions |
Delete subscriptions | DELETE /api/service/subscriptions/:user_id |
read:analytics |
Access analytics | GET /api/service/analytics |
read:service |
Read service info | GET /api/service/info |
write:service |
Update service info | PATCH /api/service/info |
Endpoints Summary
| Method | Path | Description | Required Permission |
|---|---|---|---|
GET |
/api/service/users |
List users | read:users |
POST |
/api/service/users |
Create user | write:users |
GET |
/api/service/users/:user_id |
Get user details | read:users |
PATCH |
/api/service/users/:user_id |
Update user | write:users |
DELETE |
/api/service/users/:user_id |
Delete user | delete:users |
GET |
/api/service/subscriptions |
List subscriptions | read:subscriptions |
POST |
/api/service/subscriptions |
Create subscription | write:subscriptions |
GET |
/api/service/subscriptions/:user_id |
Get user subscription | read:subscriptions |
PATCH |
/api/service/subscriptions/:user_id |
Update subscription | write:subscriptions |
DELETE |
/api/service/subscriptions/:user_id |
Delete subscription | delete:subscriptions |
GET |
/api/service/analytics |
Get service analytics | read:analytics |
GET |
/api/service/info |
Get service information | read:service |
PATCH |
/api/service/info |
Update service information | write:service |
User Management
GET /api/service/users
List all users who have authenticated with your service, with pagination support.
Required Permission: read:users
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Maximum results (default: 50, max: 100) |
offset |
integer | No | Pagination offset (default: 0) |
Response (200 OK):
{
"users": [
{
"id": "user_123",
"email": "john@example.com",
"created_at": "2025-01-10T08:00:00Z"
},
{
"id": "user_456",
"email": "jane@example.com",
"created_at": "2025-01-12T10:30:00Z"
}
],
"total": 245
}
Example Request:
curl -X GET "https://sso.example.com/api/service/users?limit=20&offset=0" \
-H "X-API-Key: sk_live_abc123xyz..."
Error Responses:
401 Unauthorized: Missing or invalid API key403 Forbidden: API key lacksread:userspermission
POST /api/service/users
Create a new user or return existing user if email already exists. Useful for provisioning users before they authenticate.
Required Permission: write:users
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Content-Type |
application/json |
Yes |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Valid email address |
Example Request:
curl -X POST https://sso.example.com/api/service/users \
-H "X-API-Key: sk_live_abc123xyz..." \
-H "Content-Type: application/json" \
-d '{
"email": "newuser@example.com"
}'
Response (200 OK):
{
"id": "user_789",
"email": "newuser@example.com",
"created_at": "2025-01-15T10:30:00Z"
}
Error Responses:
400 Bad Request: Invalid email format{ "error": "Invalid email format", "error_code": "BAD_REQUEST" }401 Unauthorized: Missing or invalid API key403 Forbidden: API key lackswrite:userspermission
Important Notes:
- If user already exists with this email, returns existing user (idempotent)
- Does not send verification emails or welcome emails
- User cannot set a password until they authenticate through another method first
- User can authenticate immediately via OAuth (if email matches existing OAuth account)
- Publishes
UserSignupSuccessevent for new users
User Access After Provisioning
Users created via the Service API are provisioned without passwords and cannot receive email verification. To gain access, they have these options:
1. OAuth Authentication (Recommended)
If the user’s email matches an existing OAuth account (Google, GitHub, Microsoft):
# User clicks OAuth login button on your application
# System matches email and authenticates successfully
2. Password Reset Flow (Manual Admin Action)
An administrator must trigger a password reset for the provisioned user:
# Admin calls password reset endpoint
POST /api/auth/forgot-password
{
"email": "provisioned@company.com"
}
# User receives reset email and sets their password
3. Invitation System (Alternative)
Use the invitation system instead of direct provisioning for better user experience:
# Send invitation with password setup
POST /api/organizations/:org_slug/invitations
{
"email": "user@company.com",
"role": "member"
}
Best Practice: For new users who need password access, prefer the invitation system over direct provisioning to provide a complete onboarding experience.
GET /api/service/users/:user_id
Get detailed information about a specific user.
Required Permission: read:users
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
user_id |
string | User ID (UUID) |
Response (200 OK):
{
"id": "user_123",
"email": "john@example.com",
"created_at": "2025-01-10T08:00:00Z"
}
Example Request:
curl -X GET https://sso.example.com/api/service/users/user_123 \
-H "X-API-Key: sk_live_abc123xyz..."
Error Responses:
404 Not Found: User not found or hasn’t authenticated with this service{ "error": "User not found or has not authenticated with this service", "error_code": "NOT_FOUND" }401 Unauthorized: Missing or invalid API key403 Forbidden: API key lacksread:userspermission
PATCH /api/service/users/:user_id
Update user information. Currently supports updating email address.
Required Permission: write:users
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Content-Type |
application/json |
Yes |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
user_id |
string | User ID (UUID) |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | No | New email address |
Example Request:
curl -X PATCH https://sso.example.com/api/service/users/user_123 \
-H "X-API-Key: sk_live_abc123xyz..." \
-H "Content-Type: application/json" \
-d '{
"email": "newemail@example.com"
}'
Response (200 OK):
{
"id": "user_123",
"email": "newemail@example.com",
"created_at": "2025-01-10T08:00:00Z"
}
Error Responses:
400 Bad Request: Invalid email format or email already taken{ "error": "Email already taken by another user", "error_code": "BAD_REQUEST" }404 Not Found: User not found or hasn’t authenticated with this service401 Unauthorized: Missing or invalid API key403 Forbidden: API key lackswrite:userspermission
DELETE /api/service/users/:user_id
Permanently delete a user and all associated data. This action is irreversible.
Required Permission: delete:users
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
user_id |
string | User ID (UUID) |
Response: 204 No Content
Example Request:
curl -X DELETE https://sso.example.com/api/service/users/user_123 \
-H "X-API-Key: sk_live_abc123xyz..."
Error Responses:
404 Not Found: User not found or hasn’t authenticated with this service401 Unauthorized: Missing or invalid API key403 Forbidden: API key lacksdelete:userspermission
Important Notes:
- Deletes all user data including identities, sessions, and audit logs
- Cannot be undone
- User can re-authenticate to create a new account
Subscription Management
GET /api/service/subscriptions
List all subscriptions for your service with optional status filtering.
Required Permission: read:subscriptions
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
string | No | Filter by status: active, cancelled, expired, past_due |
limit |
integer | No | Maximum results (default: 50, max: 100) |
offset |
integer | No | Pagination offset (default: 0) |
Response (200 OK):
{
"subscriptions": [
{
"id": "sub_123",
"user_id": "user_123",
"plan_id": "plan_pro",
"plan_name": "Professional Plan",
"status": "active",
"current_period_end": "2025-02-15T10:30:00Z"
},
{
"id": "sub_456",
"user_id": "user_456",
"plan_id": "plan_basic",
"plan_name": "Basic Plan",
"status": "active",
"current_period_end": "2025-02-20T08:00:00Z"
}
],
"total": 180
}
Example Request:
curl -X GET "https://sso.example.com/api/service/subscriptions?status=active&limit=20" \
-H "X-API-Key: sk_live_abc123xyz..."
Error Responses:
401 Unauthorized: Missing or invalid API key403 Forbidden: API key lacksread:subscriptionspermission
POST /api/service/subscriptions
Create a new subscription for a user. Typically used when a user subscribes through your billing system.
Required Permission: write:subscriptions
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Content-Type |
application/json |
Yes |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
user_id |
string | Yes | User ID (UUID) |
plan_id |
string | Yes | Plan ID (UUID) |
status |
string | No | Subscription status (default: active) |
current_period_end |
string | No | ISO 8601 datetime (default: 30 days from now) |
Example Request:
curl -X POST https://sso.example.com/api/service/subscriptions \
-H "X-API-Key: sk_live_abc123xyz..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "user_123",
"plan_id": "plan_pro",
"status": "active",
"current_period_end": "2025-02-15T10:30:00Z"
}'
Response (200 OK):
{
"id": "sub_789",
"user_id": "user_123",
"plan_id": "plan_pro",
"plan_name": "Professional Plan",
"status": "active",
"current_period_end": "2025-02-15T10:30:00Z"
}
Error Responses:
400 Bad Request: Invalid plan_id or user_id403 Forbidden: Plan doesn’t belong to this service{ "error": "Plan does not belong to this service", "error_code": "FORBIDDEN" }404 Not Found: User or plan not found401 Unauthorized: Missing or invalid API key403 Forbidden: API key lackswrite:subscriptionspermission
Important Notes:
- User must exist before creating subscription
- Plan must belong to your service
- Only one active subscription per user per service
- Subscription status affects JWT claims
GET /api/service/subscriptions/:user_id
Get subscription details for a specific user.
Required Permission: read:subscriptions
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
user_id |
string | User ID (UUID) |
Response (200 OK):
{
"id": "sub_123",
"user_id": "user_123",
"plan_id": "plan_pro",
"plan_name": "Professional Plan",
"status": "active",
"current_period_end": "2025-02-15T10:30:00Z"
}
Example Request:
curl -X GET https://sso.example.com/api/service/subscriptions/user_123 \
-H "X-API-Key: sk_live_abc123xyz..."
Error Responses:
404 Not Found: Subscription not found for this user{ "error": "Subscription not found for this user", "error_code": "NOT_FOUND" }401 Unauthorized: Missing or invalid API key403 Forbidden: API key lacksread:subscriptionspermission
PATCH /api/service/subscriptions/:user_id
Update subscription status or period end date. Used for subscription renewals, cancellations, or status changes.
Required Permission: write:subscriptions
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Content-Type |
application/json |
Yes |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
user_id |
string | User ID (UUID) |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | No | New status: active, cancelled, expired, past_due |
current_period_end |
string | No | New period end date (ISO 8601) |
Example Request:
curl -X PATCH https://sso.example.com/api/service/subscriptions/user_123 \
-H "X-API-Key: sk_live_abc123xyz..." \
-H "Content-Type: application/json" \
-d '{
"status": "active",
"current_period_end": "2025-03-15T10:30:00Z"
}'
Response (200 OK):
{
"id": "sub_123",
"user_id": "user_123",
"plan_id": "plan_pro",
"plan_name": "Professional Plan",
"status": "active",
"current_period_end": "2025-03-15T10:30:00Z"
}
Error Responses:
404 Not Found: Subscription not found for this user401 Unauthorized: Missing or invalid API key403 Forbidden: API key lackswrite:subscriptionspermission
Common Use Cases:
- Renewal: Update
current_period_endto extend subscription - Cancellation: Set
statustocancelled - Payment failure: Set
statustopast_due - Expiration: Set
statustoexpired
DELETE /api/service/subscriptions/:user_id
Delete a user’s subscription. This permanently removes the subscription record.
Required Permission: delete:subscriptions
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
user_id |
string | User ID (UUID) |
Response: 204 No Content
Example Request:
curl -X DELETE https://sso.example.com/api/service/subscriptions/user_123 \
-H "X-API-Key: sk_live_abc123xyz..."
Error Responses:
404 Not Found: Subscription not found for this user401 Unauthorized: Missing or invalid API key403 Forbidden: API key lacksdelete:subscriptionspermission
Important Notes:
- Use
PATCHwithstatus: "cancelled"for soft cancellation - Use
DELETEonly for complete removal - User can create new subscription later
Analytics
GET /api/service/analytics
Get comprehensive analytics for your service including user counts, subscription metrics, and login activity.
Required Permission: read:analytics
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Response (200 OK):
{
"total_users": 245,
"total_subscriptions": 180,
"active_subscriptions": 165,
"total_logins_30d": 12450,
"unique_users_30d": 210
}
Example Request:
curl -X GET https://sso.example.com/api/service/analytics \
-H "X-API-Key: sk_live_abc123xyz..."
Error Responses:
401 Unauthorized: Missing or invalid API key403 Forbidden: API key lacksread:analyticspermission
Metrics Explained:
total_users: All users who have ever authenticated with your servicetotal_subscriptions: All subscriptions regardless of statusactive_subscriptions: Subscriptions with statusactivetotal_logins_30d: Number of login events in last 30 daysunique_users_30d: Distinct users who logged in during last 30 days
Use Cases:
- Dashboard metrics
- Monitoring service health
- Tracking growth trends
- Calculating churn rates
Service Configuration
GET /api/service/info
Get information about your service including name, slug, and type.
Required Permission: read:service
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Response (200 OK):
{
"id": "service_123",
"name": "Main Application",
"slug": "main-app",
"service_type": "web",
"created_at": "2025-01-01T00:00:00Z"
}
Example Request:
curl -X GET https://sso.example.com/api/service/info \
-H "X-API-Key: sk_live_abc123xyz..."
Error Responses:
401 Unauthorized: Missing or invalid API key403 Forbidden: API key lacksread:servicepermission
PATCH /api/service/info
Update service configuration. Currently supports updating the service name.
Required Permission: write:service
Headers:
| Header | Value | Required |
|---|---|---|
X-API-Key |
sk_live_... |
Yes |
Content-Type |
application/json |
Yes |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | New service name |
Example Request:
curl -X PATCH https://sso.example.com/api/service/info \
-H "X-API-Key: sk_live_abc123xyz..." \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Application Name"
}'
Response (200 OK):
{
"id": "service_123",
"name": "Updated Application Name",
"slug": "main-app",
"service_type": "web",
"created_at": "2025-01-01T00:00:00Z"
}
Error Responses:
401 Unauthorized: Missing or invalid API key403 Forbidden: API key lackswrite:servicepermission
Security Considerations
API Key Security
Storage:
- Store API keys in environment variables or secure vaults
- Never commit API keys to version control
- Use different keys for test and production environments
Transmission:
- Always use HTTPS for API requests
- Never send API keys in URL parameters
- Use
X-API-Keyheader for authentication
Rotation:
- Rotate API keys regularly (quarterly recommended)
- Immediately revoke compromised keys
- Generate new keys when team members leave
Permission Scope
Principle of Least Privilege:
- Grant minimum required permissions
- Use separate keys for different purposes
- Review and audit key permissions regularly
Rate Limiting
Service API endpoints have the following rate limits:
- Read operations: 100 requests per minute per API key
- Write operations: 50 requests per minute per API key
- Delete operations: 20 requests per minute per API key
Data Access
API keys can only access:
- Users who have authenticated with the specific service
- Subscriptions for that service
- Analytics for that service
Cross-service access is not permitted.
Error Responses
All endpoints return consistent error format:
{
"error": "Human-readable error message",
"error_code": "ERROR_CODE_ENUM",
"timestamp": "2025-01-15T10:30:00Z"
}
Common Error Codes
| HTTP Status | Error Code | Description |
|---|---|---|
400 Bad Request |
BAD_REQUEST |
Invalid request parameters |
401 Unauthorized |
UNAUTHORIZED |
Missing or invalid API key |
403 Forbidden |
FORBIDDEN |
Insufficient permissions |
404 Not Found |
NOT_FOUND |
Resource not found |
429 Too Many Requests |
RATE_LIMIT_EXCEEDED |
Rate limit exceeded |
500 Internal Server Error |
INTERNAL_SERVER_ERROR |
Server error |
Best Practices
Error Handling
Always check HTTP status codes and implement retry logic for transient errors.
Pagination
Always paginate large result sets to avoid timeouts.
Idempotency
User creation is idempotent - safe to call multiple times.
Caching
Cache analytics data but always fetch fresh user/subscription data.