API Endpoints Reference
API Version: 5.24.0
OAuth 2.0 Authorization
GET /api/oauth/authorize
Start OAuth authorization flow
Redirects user to SSO login page. After authentication and permission check, redirects back with authorization code.
GET https://sso.doneisbetter.com/api/oauth/authorize ?client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/auth/callback &response_type=code &scope=openid+profile+email &state=RANDOM_STATE_STRING // Query Parameters: // client_id (required) - Your OAuth client ID // redirect_uri (required) - Registered callback URL // response_type (required) - Must be "code" // scope (required) - Space-separated scopes // state (recommended) - CSRF protection token // code_challenge (optional) - For PKCE // code_challenge_method (optional) - Should be "S256" // Success Response (redirect): https://yourapp.com/auth/callback?code=AUTH_CODE&state=RANDOM_STATE_STRING // Error Response (redirect): https://yourapp.com/auth/callback?error=access_denied&error_description=User+denied+authorization
POST /api/oauth/token
Exchange authorization code for tokens
Server-side only! Never expose client_secret in browser.
POST https://sso.doneisbetter.com/api/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "AUTH_CODE_FROM_CALLBACK",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_uri": "https://yourapp.com/auth/callback"
}
// Success Response (200 OK):
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGci...",
"id_token": "eyJhbGci..."
}
// Error Response (400 Bad Request):
{
"error": "invalid_grant",
"error_description": "Authorization code expired or invalid"
}POST /api/oauth/token (Refresh)
Refresh access token using refresh token
POST https://sso.doneisbetter.com/api/oauth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "eyJhbGci...",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
// Success Response (200 OK):
{
"access_token": "new_access_token",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "new_refresh_token" // May rotate
}POST /api/oauth/revoke
Revoke access or refresh token
POST https://sso.doneisbetter.com/api/oauth/revoke
Content-Type: application/json
{
"token": "access_token_or_refresh_token",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
// Success Response (200 OK):
{
"success": true
}OpenID Connect Discovery
GET /.well-known/openid-configuration
OIDC discovery document
Returns metadata about the authorization server. Use this to auto-configure OAuth libraries.
GET https://sso.doneisbetter.com/.well-known/openid-configuration
// Response (200 OK):
{
"issuer": "https://sso.doneisbetter.com",
"authorization_endpoint": "https://sso.doneisbetter.com/api/oauth/authorize",
"token_endpoint": "https://sso.doneisbetter.com/api/oauth/token",
"revocation_endpoint": "https://sso.doneisbetter.com/api/oauth/revoke",
"jwks_uri": "https://sso.doneisbetter.com/.well-known/jwks.json",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["HS256"],
"scopes_supported": ["openid", "profile", "email"],
"token_endpoint_auth_methods_supported": ["client_secret_post"],
"code_challenge_methods_supported": ["S256"]
}GET /.well-known/jwks.json
JSON Web Key Set
Public keys for verifying JWT signatures (if using RS256).
GET https://sso.doneisbetter.com/.well-known/jwks.json
// Response (200 OK):
{
"keys": [
{
"kty": "RSA",
"kid": "key-id",
"use": "sig",
"alg": "RS256",
"n": "modulus",
"e": "exponent"
}
]
}Public User API
POST /api/public/register
Register new user account
POST https://sso.doneisbetter.com/api/public/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecureP@ssw0rd", // Optional, can use magic link instead
"name": "John Doe"
}
// Success Response (201 Created):
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2025-10-15T16:22:16.000Z"
}
// Error Response (400 Bad Request):
{
"error": {
"code": "EMAIL_EXISTS",
"message": "User with this email already exists"
}
}POST /api/public/login
Direct email + password login
Alternative to OAuth flow for simple integrations. Returns access token directly.
POST https://sso.doneisbetter.com/api/public/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecureP@ssw0rd",
"clientId": "YOUR_CLIENT_ID" // Check app permissions
}
// Success Response (200 OK):
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"name": "John Doe",
"role": "user" // App-level role if clientId provided
}
}
// Error Response (401 Unauthorized):
{
"error": {
"code": "INVALID_CREDENTIALS",
"message": "Invalid email or password"
}
}GET /api/public/session
Validate Bearer token and get user info
GET https://sso.doneisbetter.com/api/public/session
Authorization: Bearer eyJhbGci...
// Success Response (200 OK):
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"name": "John Doe",
"role": "admin" // If accessed via OAuth with clientId
}
// Error Response (401 Unauthorized):
{
"error": {
"code": "INVALID_TOKEN",
"message": "Token is invalid or expired"
}
}POST /api/public/magic-link
Request passwordless magic link
POST https://sso.doneisbetter.com/api/public/magic-link
Content-Type: application/json
{
"email": "user@example.com"
}
// Success Response (200 OK):
{
"success": true,
"message": "Magic link sent to user@example.com",
"expiresIn": 900 // 15 minutes
}
// User receives email with link:
// https://sso.doneisbetter.com/api/admin/magic-link?t=MAGIC_TOKENPOST /api/public/pin
Request PIN code authentication
POST https://sso.doneisbetter.com/api/public/pin
Content-Type: application/json
{
"email": "user@example.com"
}
// Success Response (200 OK):
{
"success": true,
"message": "PIN code sent to user@example.com",
"expiresIn": 300 // 5 minutes
}
// User receives 6-digit PIN code via emailAdmin API
Note: All admin endpoints require admin session cookie authentication.
POST /api/admin/login
Admin authentication
POST https://sso.doneisbetter.com/api/admin/login
Content-Type: application/json
{
"email": "admin@doneisbetter.com",
"password": "32-hex-token-here"
}
// Success Response (200 OK):
// Sets Cookie: admin-session=...; HttpOnly; SameSite=Lax
{
"success": true,
"user": {
"id": "admin-uuid",
"email": "admin@doneisbetter.com",
"name": "Admin Name",
"role": "super-admin"
}
}GET /api/admin/users
List all users
GET https://sso.doneisbetter.com/api/admin/users
Cookie: admin-session=...
// Success Response (200 OK):
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2025-10-15T16:22:16.000Z",
"updatedAt": "2025-10-15T16:22:16.000Z"
}
]GET /api/admin/app-permissions/[userId]
Get user's app permissions
GET https://sso.doneisbetter.com/api/admin/app-permissions/USER_UUID
Cookie: admin-session=...
// Success Response (200 OK):
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"apps": [
{
"clientId": "launchmass-client-id",
"name": "Launchmass",
"description": "Landing page builder",
"role": "admin",
"status": "approved",
"grantedAt": "2025-10-15T14:30:00.000Z",
"grantedBy": "admin-uuid"
},
{
"clientId": "messmass-client-id",
"name": "Messmass",
"description": "Messaging platform",
"role": "none",
"status": "pending",
"createdAt": "2025-10-15T16:00:00.000Z"
}
]
}POST /api/admin/app-permissions/[userId]
Grant or approve app access
POST https://sso.doneisbetter.com/api/admin/app-permissions/USER_UUID
Cookie: admin-session=...
Content-Type: application/json
{
"clientId": "launchmass-client-id",
"role": "user", // "user" or "admin"
"status": "approved"
}
// Success Response (200 OK):
{
"success": true,
"permission": {
"userId": "550e8400-e29b-41d4-a716-446655440000",
"clientId": "launchmass-client-id",
"role": "user",
"status": "approved",
"grantedAt": "2025-10-15T16:22:16.000Z",
"grantedBy": "admin-uuid"
}
}PATCH /api/admin/app-permissions/[userId]
Update role (approved permissions only)
PATCH https://sso.doneisbetter.com/api/admin/app-permissions/USER_UUID
Cookie: admin-session=...
Content-Type: application/json
{
"clientId": "launchmass-client-id",
"role": "admin" // Change from "user" to "admin"
}
// Success Response (200 OK):
{
"success": true
}DELETE /api/admin/app-permissions/[userId]
Revoke app access
DELETE https://sso.doneisbetter.com/api/admin/app-permissions/USER_UUID
Cookie: admin-session=...
Content-Type: application/json
{
"clientId": "launchmass-client-id"
}
// Success Response (200 OK):
{
"success": true
}Rate Limiting
All endpoints are rate-limited. Limits vary by endpoint type:
| Endpoint Group | Rate Limit | Scope |
|---|---|---|
| OAuth authorization | 10 requests / minute | Per IP address |
| Token exchange | 10 requests / minute | Per client_id |
| Session validation | 60 requests / minute | Per access token |
| User registration | 5 requests / hour | Per IP address |
| Admin API | 30 requests / minute | Per admin session |
Rate Limit Headers
// Included in all responses
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1627399287 // Unix timestamp
// 429 Too Many Requests response
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"error": "rate_limit_exceeded",
"error_description": "Too many requests. Retry after 60 seconds.",
"retry_after": 60
}