Data Models
API Key Model
{
"id": "uuid",
"service_id": "uuid",
"name": "Production Backend Server",
"prefix": "sk_a1b2c3",
"key_hash": "sha256-hash",
"permissions": ["read:users", "read:subscriptions", "read:analytics"],
"last_used_at": "datetime",
"expires_at": "datetime",
"created_at": "datetime",
"created_by": "uuid"
}
API Key Response (without key)
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"service_id": "service-uuid",
"name": "Production Backend Server",
"prefix": "sk_a1b2c3",
"permissions": ["read:users", "read:subscriptions", "read:analytics"],
"last_used_at": "2025-01-20T14:22:00Z",
"expires_at": "2025-04-15T10:30:00Z",
"created_at": "2025-01-15T10:30:00Z",
"created_by": "user-uuid"
}
API Key Create Response (with full key)
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"service_id": "service-uuid",
"name": "Production Backend Server",
"prefix": "sk_a1b2c3",
"permissions": ["read:users", "read:subscriptions", "read:analytics"],
"expires_at": "2025-04-15T10:30:00Z",
"created_at": "2025-01-15T10:30:00Z",
"created_by": "user-uuid",
"key": "sk_a1b2c3_d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"
}
Valid Permissions
API keys support the following granular permissions:
| Permission | Description |
|---|---|
read:users |
List and retrieve user information for users who have authenticated with the service |
write:users |
Create and manage users |
delete:users |
Delete users |
read:subscriptions |
List and retrieve subscription information |
write:subscriptions |
Create and update subscriptions |
delete:subscriptions |
Delete subscriptions |
read:analytics |
Access service analytics and metrics |
read:service |
View service configuration |
write:service |
Modify service configuration |
Permission Best Practices:
- Principle of Least Privilege: Grant only the permissions required for the specific use case
- Read-Only Keys: Use read-only permissions (
read:*) for analytics dashboards and monitoring - Separate Keys: Create different keys for different backend services
- Time-Limited: Set expiration dates for keys used in CI/CD or temporary integrations
Endpoints Summary
| Method | Path | Description | Permissions |
|---|---|---|---|
| POST | /api/organizations/:org_slug/services/:service_slug/api-keys |
Create API key | Owner/Admin |
| GET | /api/organizations/:org_slug/services/:service_slug/api-keys |
List API keys | Owner/Admin/Member |
| GET | /api/organizations/:org_slug/services/:service_slug/api-keys/:api_key_id |
Get API key details | Owner/Admin/Member |
| DELETE | /api/organizations/:org_slug/services/:service_slug/api-keys/:api_key_id |
Delete API key | Owner/Admin |
API Key Operations
POST /api/organizations/:org_slug/services/:service_slug/api-keys
Create a new API key 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 | Descriptive name for the API key (e.g., “Production Backend Server”) |
permissions |
string[] | Yes | Array of permissions (at least one required) |
expires_in_days |
integer | No | Number of days until expiration (e.g., 90 for 3 months) |
Example Request:
curl -X POST https://sso.example.com/api/organizations/acme-corp/services/main-app/api-keys \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Production Backend Server",
"permissions": ["read:users", "read:subscriptions", "read:analytics"],
"expires_in_days": 90
}'
Example Response (201 Created):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"service_id": "service-uuid",
"name": "Production Backend Server",
"prefix": "sk_a1b2c3",
"permissions": ["read:users", "read:subscriptions", "read:analytics"],
"expires_at": "2025-04-15T10:30:00Z",
"created_at": "2025-01-15T10:30:00Z",
"created_by": "user-uuid",
"key": "sk_a1b2c3_d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"
}
Error Responses:
400 Bad Request: Invalid permissions, empty name, or no permissions provided401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not an owner or admin, or organization not active404 Not Found: Organization or service not found500 Internal Server Error: Database error
Important Notes:
- Store the key securely: The
keyfield contains the full API key and is only returned once - Cannot be retrieved: If you lose the key, you must create a new one
- Key format:
sk_{prefix}_{random_component}(e.g.,sk_a1b2c3_d4e5f6g7...) - SHA256 hashed: Only the hash is stored in the database
- Constant-time verification: Intended to reduce timing leakage; validate the deployed implementation
- Audit logged: Key creation is logged with permissions and expiration
- Organization must be active: Inactive organizations cannot create API keys
Security Best Practices:
- Store keys in environment variables or secret managers (never in code)
- Use different keys for development, staging, and production
- Set expiration dates for temporary or CI/CD keys
- Rotate keys regularly (e.g., every 90 days)
- Delete unused keys immediately
- Monitor
last_used_atto detect unused keys
Example: Store Key Securely
# Environment variable (recommended)
export SSO_API_KEY="sk_a1b2c3_d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"
# Use in application
curl -X GET https://sso.example.com/api/service/users \
-H "X-Api-Key: $SSO_API_KEY"
GET /api/organizations/:org_slug/services/:service_slug/api-keys
List all API keys for a service.
Permissions: Owner, Admin, or Member
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Maximum number of keys to return (default: 50, max: 100) |
offset |
integer | No | Number of keys to skip for pagination (default: 0) |
Example Request:
curl -X GET "https://sso.example.com/api/organizations/acme-corp/services/main-app/api-keys?limit=50&offset=0" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example Response (200 OK):
{
"api_keys": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"service_id": "service-uuid",
"name": "Production Backend Server",
"prefix": "sk_a1b2c3",
"permissions": ["read:users", "read:subscriptions"],
"last_used_at": "2025-01-20T14:22:00Z",
"expires_at": "2025-04-15T10:30:00Z",
"created_at": "2025-01-15T10:30:00Z",
"created_by": "user-uuid"
},
{
"id": "another-key-uuid",
"service_id": "service-uuid",
"name": "Analytics Dashboard",
"prefix": "sk_d4e5f6",
"permissions": ["read:analytics", "read:service"],
"last_used_at": "2025-01-21T09:15:00Z",
"expires_at": null,
"created_at": "2025-01-16T11:00:00Z",
"created_by": "another-user-uuid"
},
{
"id": "expired-key-uuid",
"service_id": "service-uuid",
"name": "Deprecated Integration",
"prefix": "sk_g7h8i9",
"permissions": ["read:users"],
"last_used_at": "2024-12-01T10:00:00Z",
"expires_at": "2025-01-01T00:00:00Z",
"created_at": "2024-10-01T10:00:00Z",
"created_by": "user-uuid"
}
],
"total": 3
}
Error Responses:
401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not a member, or organization not active404 Not Found: Organization or service not found500 Internal Server Error: Database error
Notes:
- All members can view API keys (but not the actual key value)
last_used_atshows when the key was last used (null if never used)expires_atis null for keys with no expiration- Expired keys are still returned (check
expires_aton client side) - Use
last_used_atto identify inactive keys for cleanup - Pagination supported via
limitandoffset
Example: Identify Unused Keys
// Filter keys not used in the last 90 days
const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
const unusedKeys = apiKeys.filter(key =>
!key.last_used_at || new Date(key.last_used_at) < ninetyDaysAgo
);
GET /api/organizations/:org_slug/services/:service_slug/api-keys/:api_key_id
Get details for a specific API key.
Permissions: Owner, Admin, or Member
Headers:
| Header | Value |
|---|---|
Authorization |
Bearer {jwt} |
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
api_key_id |
string | API key ID (UUID) |
Example Request:
curl -X GET https://sso.example.com/api/organizations/acme-corp/services/main-app/api-keys/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"service_id": "service-uuid",
"name": "Production Backend Server",
"prefix": "sk_a1b2c3",
"permissions": ["read:users", "read:subscriptions", "read:analytics"],
"last_used_at": "2025-01-20T14:22:00Z",
"expires_at": "2025-04-15T10:30:00Z",
"created_at": "2025-01-15T10:30:00Z",
"created_by": "user-uuid"
}
Error Responses:
401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not a member, or organization not active404 Not Found: Organization, service, or API key not found500 Internal Server Error: Database error
Notes:
- Full key value is never returned (only available during creation)
- Use this endpoint to check key details and permissions
- Verify
expires_atto detect expired keys - Check
last_used_atto monitor key activity
DELETE /api/organizations/:org_slug/services/:service_slug/api-keys/:api_key_id
Delete an API key immediately.
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 |
api_key_id |
string | API key ID (UUID) |
Example Request:
curl -X DELETE https://sso.example.com/api/organizations/acme-corp/services/main-app/api-keys/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Example Response (204 No Content):
No response body.
Error Responses:
401 Unauthorized: Invalid or missing JWT403 Forbidden: User is not an owner or admin, or organization not active404 Not Found: Organization, service, or API key not found500 Internal Server Error: Database error
Important Warnings:
- Immediate revocation: Services using this key will lose access immediately
- Cannot be undone: Deleted keys cannot be recovered
- No grace period: Active requests using the key will fail instantly
- Audit logged: Deletion is logged with key name and actor
Best Practices:
- Notify relevant teams before deleting keys used in production
- Update services to use new keys before deleting old ones
- Consider setting expiration dates instead of manual deletion for temporary keys
- Delete keys immediately if compromised
Example: Key Rotation Workflow
# 1. Create new API key
NEW_KEY=$(curl -X POST .../api-keys -d '{"name":"New Key","permissions":[...]}' | jq -r '.key')
# 2. Update service configuration with new key
export SSO_API_KEY="$NEW_KEY"
# 3. Test new key
curl -X GET .../api/service/users -H "X-Api-Key: $SSO_API_KEY"
# 4. Delete old key
curl -X DELETE .../api-keys/old-key-uuid
# 5. Store new key securely
echo "$NEW_KEY" | vault write secret/sso-api-key value=-