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_TOKEN

POST /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 email

Admin 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 GroupRate LimitScope
OAuth authorization10 requests / minutePer IP address
Token exchange10 requests / minutePer client_id
Session validation60 requests / minutePer access token
User registration5 requests / hourPer IP address
Admin API30 requests / minutePer 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
}