API Response Formats

API Version: 5.24.0

OAuth 2.0 Token Response

Response from POST /api/oauth/token endpoint:

// Success Response (200 OK)
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQw...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQw...",
  "id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQw..."
}

// Field descriptions:
// access_token - JWT for API authentication (1 hour expiry)
// token_type - Always "Bearer"
// expires_in - Seconds until access_token expires
// refresh_token - JWT for refreshing access token (30 days)
// id_token - JWT containing user identity and role

JWT Token Structures

Access Token Payload

Decoded JWT structure for access_token:

// Header
{
  "alg": "HS256",
  "typ": "JWT"
}

// Payload
{
  "sub": "550e8400-e29b-41d4-a716-446655440000",  // User UUID
  "iss": "https://sso.doneisbetter.com",            // Issuer
  "aud": "your-client-id",                          // Audience (your app)
  "exp": 1734274936,                                 // Expiration (Unix timestamp)
  "iat": 1734271336,                                 // Issued at (Unix timestamp)
  "jti": "token-unique-id"                           // JWT ID (for revocation)
}

// Usage:
Authorization: Bearer <access_token>

// Validation:
// - Check exp > current time
// - Verify iss matches expected issuer
// - Verify aud matches your client_id

ID Token Payload

Decoded JWT structure for id_token (contains user info + app role):

// Header
{
  "alg": "HS256",
  "typ": "JWT"
}

// Payload
{
  "sub": "550e8400-e29b-41d4-a716-446655440000",  // User UUID
  "email": "user@example.com",
  "name": "John Doe",
  "role": "admin",                                   // App-level role: 'user' or 'admin'
  "iss": "https://sso.doneisbetter.com",
  "aud": "your-client-id",
  "exp": 1734274936,
  "iat": 1734271336
}

// Usage:
// Decode (no verification needed if from /token endpoint)
const jwt = require('jsonwebtoken');
const userInfo = jwt.decode(id_token);
console.log(userInfo.email, userInfo.role);

// Store in your app's session
req.session.userId = userInfo.sub;
req.session.email = userInfo.email;
req.session.role = userInfo.role;  // Use for authorization

Refresh Token Payload

Decoded JWT structure for refresh_token:

// Opaque to clients - treat as string, don't decode
// Internal structure (for reference only):
{
  "sub": "550e8400-e29b-41d4-a716-446655440000",
  "type": "refresh",
  "client_id": "your-client-id",
  "iss": "https://sso.doneisbetter.com",
  "exp": 1736863336,  // 30 days from issuance
  "iat": 1734271336,
  "jti": "refresh-token-unique-id"
}

// Usage:
// POST /api/oauth/token with grant_type=refresh_token
// Returns new access_token (and may rotate refresh_token)

Standard API Response Format

Public and admin API endpoints return JSON in this format:

Success Response

// Example: POST /api/public/register
{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "name": "John Doe",
  "createdAt": "2025-10-15T16:22:16.000Z"
}

// Example: GET /api/admin/users
[
  {
    "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"
  }
]

Error Response

// Standard error format
{
  "error": {
    "code": "APP_PERMISSION_DENIED",
    "message": "User does not have permission to access this application",
    "status": "pending"  // Additional context (optional)
  }
}

// OAuth error format (different!)
{
  "error": "invalid_grant",
  "error_description": "Authorization code expired or already used"
}

HTTP Status Codes

The API uses standard HTTP status codes to indicate the result of requests:

  • 200 OK

    Request successful. Response body contains requested data.

  • 201 Created

    Resource successfully created (e.g., user registration).

  • 302 Found

    Redirect (used in OAuth authorization endpoint).

  • 400 Bad Request

    Invalid request parameters, malformed JSON, or validation errors.

  • 401 Unauthorized

    Authentication required or invalid/expired token.

  • 403 Forbidden

    Valid authentication but insufficient permissions (e.g., app permission denied).

  • 404 Not Found

    Requested resource does not exist.

  • 429 Too Many Requests

    Rate limit exceeded. Check Retry-After header.

  • 500 Internal Server Error

    Unexpected server error occurred.

Response Headers

Important information is conveyed through response headers:

  • Content-Type: application/json

    All API responses are JSON (except OAuth redirects).

  • X-RateLimit-Limit: 60

    Maximum requests allowed in current time window.

  • X-RateLimit-Remaining: 58

    Remaining requests in current time window.

  • X-RateLimit-Reset: 1627399287

    Unix timestamp when rate limit resets.

  • Retry-After: 60

    Seconds to wait before retrying (429 responses).

  • Set-Cookie: admin-session=...; HttpOnly; SameSite=Lax

    Admin session cookie (admin login endpoint only).

  • Access-Control-Allow-Origin: https://yourapp.com

    CORS header indicating allowed origin.

  • Access-Control-Allow-Credentials: true

    Indicates cookies are allowed in cross-origin requests.

App Permission Responses

Special response format for app permission management:

GET /api/admin/app-permissions/[userId]

{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "apps": [
    {
      "clientId": "launchmass-client-id",
      "name": "Launchmass",
      "description": "Landing page builder",
      "role": "admin",       // Current role: 'none', 'user', or 'admin'
      "status": "approved",  // 'pending', 'approved', 'revoked', 'none'
      "grantedAt": "2025-10-15T14:30:00.000Z",
      "grantedBy": "admin-user-uuid",
      "createdAt": "2025-10-15T12:00:00.000Z",
      "updatedAt": "2025-10-15T14:30:00.000Z"
    },
    {
      "clientId": "messmass-client-id",
      "name": "Messmass",
      "description": "Messaging platform",
      "role": "none",
      "status": "pending",
      "createdAt": "2025-10-15T16:00:00.000Z",
      "updatedAt": "2025-10-15T16:00:00.000Z"
    }
  ]
}

Permission Status Meanings

  • none - No permission record exists (never requested)
  • pending - User requested access, awaiting admin approval
  • approved - User has active access with assigned role
  • revoked - Access was granted but has been revoked

Timestamp Format

All timestamps in API responses use ISO 8601 UTC with milliseconds:

// Format: YYYY-MM-DDTHH:MM:SS.sssZ
"createdAt": "2025-10-15T16:22:16.000Z"
"updatedAt": "2025-10-15T16:22:16.000Z"
"grantedAt": "2025-10-15T14:30:00.000Z"

// Parsing in JavaScript:
const date = new Date("2025-10-15T16:22:16.000Z");
console.log(date.toLocaleString());  // Local time

// Parsing in other languages:
// Python: datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
// Go: time.Parse(time.RFC3339Nano, timestamp)
// Java: Instant.parse(timestamp)

Related Documentation