SageOx Users API
Manage user profiles, preferences, API keys, and account settings. Preferences control notification delivery, theme, and feature opt-ins. API keys authenticate CLI and programmatic access.
Manage user profiles, preferences, API keys, and account settings. Preferences control notification delivery, theme, and feature opt-ins. API keys authenticate CLI and programmatic access.
openapi: 3.1.0
info:
title: SageOx Admin Users API
version: 1.0.0
description: "# API Reference\n\n## Overview\n\nSageOx is a platform that captures team knowledge from discussions, decisions, and work context into a Ledger (per-repo historical record) and Team Context (team-wide shared knowledge). The SageOx API provides programmatic access to manage repositories, recordings, notifications, and team data with high performance and reliability.\n\n## Base URLs\n\n| Environment | URL |\n|---|---|\n| Local Development | `http://localhost:3000` |\n| Test | `https://test.sageox.ai` |\n| Production | `https://sageox.ai` |\n\n## Authentication\n\nAll requests to the SageOx API require authentication via JWT tokens issued by the Better Auth service.\n\nInclude your token in the `Authorization` header:\n```\nAuthorization: Bearer <token>\n```\n\n**Initial Auth Flow (CLI)**\n- CLI initiates device flow to obtain initial credentials\n- Exchange device code for JWT token\n- Store token securely for subsequent API requests\n- Refresh token when expired\n\n**Authenticated Endpoints**\n- Pass JWT in `Authorization: Bearer <token>` header\n- Tokens contain user identity and team membership\n- Invalid or expired tokens return 401 Unauthorized\n\n## Response Format\n\n### Success\nStandard response format with optional pagination metadata:\n```json\n{\n \"success\": true,\n \"data\": { ... }\n}\n```\n\n### Error\nAll errors follow a consistent format:\n```json\n{\n \"success\": false,\n \"error\": \"Human-readable error description\"\n}\n```\n\n**Status Codes**\n- `400` — Bad Request: Invalid parameters or malformed request\n- `401` — Unauthorized: Missing or invalid authentication token\n- `403` — Forbidden: Insufficient permissions for this resource\n- `404` — Not Found: Resource does not exist\n- `409` — Conflict: Request conflicts with current state (e.g., duplicate)\n- `429` — Rate Limited: Too many requests; retry with backoff\n- `500` — Internal Error: Server error; contact support if persistent\n\n## Pagination\n\nList endpoints support cursor-based pagination via query parameters:\n\n**Query Parameters**\n- `limit` — Number of results per page (default: 20, max: 100)\n- `offset` — Number of results to skip (default: 0)\n\n**Response Metadata**\nList responses include pagination info:\n```json\n{\n \"success\": true,\n \"data\": [ ... ],\n \"meta\": {\n \"total\": 150,\n \"limit\": 20,\n \"offset\": 0,\n \"hasMore\": true\n }\n}\n```\n\n## ID Formats\n\nResources use standardized ID formats for easy identification:\n\n| Resource | Format | Length | Example |\n|---|---|---|---|\n| Team | `team_<cuid2>` | 10 chars | `team_abc1234567` |\n| User | `usr_<cuid2>` | 10 chars | `usr_xyz9876543` |\n| Repository | `repo_<uuidv7>` | 36 chars | `repo_01934f5a-8b9c-7def-abcd-0123456789ab` |\n| Recording | `rec_<uuidv7>` | 36 chars | `rec_01934f5a-8b9c-7def-abcd-0123456789ab` |\n| Image | `img_<uuidv7>` | 36 chars | `img_01934f5a-8b9c-7def-abcd-0123456789ab` |\n| Notification | `notif_<cuid2>` | 14 chars | `notif_ab1cd2ef3g` |\n\nUse these IDs for all API operations. IDs are unique across environments.\n\n## Rate Limiting\n\nThe API applies rate limiting to protect against abuse and ensure fair access:\n\n**Authenticated Endpoints**\n- Limited per user: depends on subscription tier\n- Quota resets hourly\n\n**Unauthenticated Endpoints**\n- Limited per IP address\n- More restrictive than authenticated limits\n\nWhen rate limited, the API returns `429 Too Many Requests`. Retry requests with exponential backoff:\n```\nwait = min(2^attempt * 100ms, 10s)\n```\n\n## Timestamps\n\nAll timestamps in API responses use RFC 3339 / ISO 8601 format in UTC:\n```\n2025-12-03T10:30:00Z\n```\n\nUse this format when providing timestamps in requests as well. The API rejects timestamps in other formats.\n\n## SDKs & Tools\n\n- **OpenAPI Spec** — Full schema available at `/api/openapi.json`\n- **Postman Collection** — Import from `/api/postman.json` for interactive exploration\n- **CLI** — Use `ox` CLI for command-line access\n"
contact:
name: SageOx Team
license:
name: MIT
servers:
- url: http://localhost:3000
description: Devcontainer
- url: https://test.sageox.ai
description: Test
- url: https://sageox.ai
description: Production
security:
- bearerAuth: []
tags:
- name: Users
description: 'Manage user profiles, preferences, API keys, and account settings. Preferences control notification delivery, theme, and feature opt-ins. API keys authenticate CLI and programmatic access.
'
paths:
/api/v1/users/me:
get:
operationId: getCurrentUser
summary: Get current user profile
description: 'Retrieves the authenticated user''s profile including name, email, avatar,
tier, ownership counts, and subscription status.
'
tags:
- Users
security:
- BearerAuth: []
responses:
'200':
description: User profile retrieved successfully
content:
application/json:
schema:
type: object
required:
- id
- email
- emailVerified
- tier
- createdAt
- updatedAt
- ownedOrgsCount
- ownedTeamsCount
- newsletter_subscribed
properties:
id:
type: string
description: Unique user identifier
example: usr_2n3k4m5l6j7h8g9f
name:
type: string
description: User's display name
example: Alex Engineer
email:
type: string
format: email
description: User's email address
example: alex@example.com
emailVerified:
type: boolean
description: Whether email has been verified
example: true
avatar_url:
type: string
format: uri
nullable: true
description: URL to user's avatar image
example: https://storage.example.com/avatars/usr_2n3k4m5l6j7h8g9f/abc123.jpg
tier:
type: string
description: User's subscription tier
enum:
- free
- pro
- enterprise
example: pro
createdAt:
type: string
format: date-time
description: Account creation timestamp
example: '2025-01-15T10:30:00Z'
updatedAt:
type: string
format: date-time
description: Profile last update timestamp
example: '2025-01-20T14:22:00Z'
deletedAt:
type: string
format: date-time
nullable: true
description: Account deletion timestamp (null if not deleted)
example: null
ownedOrgsCount:
type: integer
description: Number of organizations solely owned by user
example: 1
ownedTeamsCount:
type: integer
description: Number of teams solely owned by user
example: 3
newsletter_subscribed:
type: boolean
description: Whether user is subscribed to newsletter
example: true
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
put:
operationId: updateCurrentUser
summary: Update user profile
description: 'Updates the authenticated user''s profile. Only name and avatar_url can be
modified. Email changes require verification flow and are rejected here.
'
tags:
- Users
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: User's new display name (1-255 characters)
example: Alex Senior Engineer
avatar_url:
type: string
format: uri
description: URL to new avatar image
example: https://storage.example.com/avatars/new-avatar.jpg
examples:
update_name:
summary: Update name only
value:
name: Alex Senior Engineer
update_avatar:
summary: Update avatar only
value:
avatar_url: https://storage.example.com/avatars/new-avatar.jpg
update_both:
summary: Update name and avatar
value:
name: Alex Senior Engineer
avatar_url: https://storage.example.com/avatars/new-avatar.jpg
responses:
'200':
description: Profile updated successfully
content:
application/json:
schema:
type: object
required:
- id
- email
- emailVerified
- tier
- createdAt
- updatedAt
properties:
id:
type: string
example: usr_2n3k4m5l6j7h8g9f
name:
type: string
example: Alex Senior Engineer
email:
type: string
format: email
example: alex@example.com
emailVerified:
type: boolean
example: true
avatar_url:
type: string
format: uri
nullable: true
example: https://storage.example.com/avatars/new-avatar.jpg
tier:
type: string
enum:
- free
- pro
- enterprise
example: pro
createdAt:
type: string
format: date-time
example: '2025-01-15T10:30:00Z'
updatedAt:
type: string
format: date-time
example: '2025-01-20T15:45:00Z'
'400':
description: Invalid request body or validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
empty_name:
value:
success: false
error: name cannot be empty
email_rejected:
value:
success: false
error: email cannot be changed directly, please use email verification flow
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
delete:
operationId: deleteCurrentUser
summary: Initiate account deletion
description: 'Soft-deletes the user account with a 30-day grace period. User must not be
the sole owner of any teams or organizations. After deletion, the account can
be recovered by calling POST /api/v1/users/me/cancel-deletion within 30 days.
'
tags:
- Users
security:
- BearerAuth: []
responses:
'204':
description: Account deletion initiated successfully (grace period started)
'400':
description: Cannot delete account due to ownership constraints
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
has_teams:
value:
success: false
error: cannot delete account while you are the sole owner of one or more teams
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/users/me/cancel-deletion:
post:
operationId: cancelUserDeletion
summary: Cancel account deletion
description: 'Cancels a pending account deletion during the 30-day grace period.
If no deletion is pending, returns 400 Bad Request.
'
tags:
- Users
security:
- BearerAuth: []
responses:
'204':
description: Deletion cancellation successful
'400':
description: No pending deletion found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
no_pending_deletion:
value:
success: false
error: no pending deletion found
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/users/me/export:
get:
operationId: exportUserData
summary: Export user data (GDPR)
description: 'Exports all user data in JSON format for GDPR compliance. Returns a
comprehensive archive of the user''s profile, preferences, and associated data.
The response includes a Content-Disposition header for download.
'
tags:
- Users
security:
- BearerAuth: []
responses:
'200':
description: User data export successful
headers:
Content-Disposition:
description: Download filename
schema:
type: string
example: attachment; filename="user-data-export-2025-01-20.json"
content:
application/json:
schema:
type: object
required:
- profile
properties:
profile:
type: object
required:
- id
- email
properties:
id:
type: string
example: usr_2n3k4m5l6j7h8g9f
name:
type: string
example: Alex Engineer
email:
type: string
format: email
example: alex@example.com
createdAt:
type: string
format: date-time
example: '2025-01-15T10:30:00Z'
preferences:
type: object
nullable: true
description: User preferences and settings
additionalProperties: true
teams:
type: array
items:
type: object
properties:
id:
type: string
example: team_abc123xyz
name:
type: string
example: Engineering Team
description: Teams the user belongs to
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/users/me/avatar:
post:
operationId: uploadUserAvatar
summary: Upload user avatar
description: 'Uploads a user avatar image. Accepts JPEG, PNG, or WebP files up to 5MB.
Image is automatically resized to 256x256 and stored. Returns the URL of
the stored avatar.
'
tags:
- Users
security:
- BearerAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- avatar
properties:
avatar:
type: string
format: binary
description: Avatar image file (JPEG, PNG, or WebP, max 5MB)
examples:
avatar_upload:
value:
avatar: (binary image data)
responses:
'200':
description: Avatar uploaded successfully
content:
application/json:
schema:
type: object
required:
- avatar_url
properties:
avatar_url:
type: string
format: uri
description: URL to the uploaded avatar
example: https://storage.example.com/avatars/usr_2n3k4m5l6j7h8g9f/abc123def456.jpg
'400':
description: Invalid file or request error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
missing_file:
value:
success: false
error: avatar file is required
invalid_type:
value:
success: false
error: invalid file type, only jpeg, png, and webp are allowed
too_large:
value:
success: false
error: request body too large or invalid multipart form
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/users/me/adoptable-repos:
get:
operationId: getAdoptableRepos
summary: List adoptable repositories
description: 'Returns repositories that were created with the user''s email address but
are not yet linked to their account. These are repositories potentially
created before the user had a SageOx account. Note: Git email can be forged,
so these are "potential" matches only - verify before claiming.
'
tags:
- Users
security:
- BearerAuth: []
responses:
'200':
description: List of adoptable repositories
content:
application/json:
schema:
type: object
required:
- repos
- warning
properties:
repos:
type: array
items:
type: object
required:
- id
- type
- created_at
properties:
id:
type: string
description: Repository identifier
example: repo_01934f5a-8b9c-7def-b012-3456789abcde
type:
type: string
description: VCS type
enum:
- git
example: git
created_by_email:
type: string
format: email
nullable: true
description: Email from git config when repo was initialized
example: alex@example.com
created_by_name:
type: string
nullable: true
description: Name from git config when repo was initialized
example: Alex Engineer
created_at:
type: string
format: date-time
description: Repository creation timestamp
example: '2025-01-10T09:15:00Z'
team_ids:
type: array
items:
type: string
description: Team IDs currently associated with this repo
example:
- team_abc123xyz
- team_def456uvw
warning:
type: string
description: Warning about verifying claimed repositories
example: These repos were created with your email address but are not yet linked to your account. Git email can be configured by anyone, so verify these are actually yours before claiming.
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/users/me/newsletter/subscribe:
post:
operationId: subscribeNewsletter
summary: Subscribe to newsletter
description: 'Subscribes the authenticated user to the SageOx newsletter.
Updates the user''s newsletter subscription preference.
'
tags:
- Users
security:
- BearerAuth: []
responses:
'200':
description: Newsletter subscription successful
content:
application/json:
schema:
type: object
required:
- success
- newsletter_subscribed
properties:
success:
type: boolean
example: true
newsletter_subscribed:
type: boolean
example: true
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/users/me/newsletter:
get:
operationId: getNewsletterStatus
summary: Get newsletter subscription status
description: 'Returns the authenticated user''s newsletter subscription status.
'
tags:
- Users
security:
- BearerAuth: []
responses:
'200':
description: Newsletter status retrieved successfully
content:
application/json:
schema:
type: object
required:
- newsletter_subscribed
properties:
newsletter_subscribed:
type: boolean
description: Whether user is subscribed to newsletter
example: true
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/v1/users/me/onboarding:
get:
operationId: getOnboardingStatus
summary: Get onboarding progress
description: 'Returns the authenticated user''s onboarding step completion status.
Indicates which key steps (team creation, repo initialization, recording)
have been completed.
'
tags:
- Users
security:
- BearerAuth: []
responses:
'200':
description: Onboarding status retrieved successfully
content:
application/json:
schema:
type: object
required:
- has_team
- has_repo
- has_recording
properties:
has_team:
type: boolean
description: Whether user has created a team
example: true
has_repo:
type: boolean
description: Whether user has initialized a repository
example: true
has_recording:
type: boolean
description: Whether user has created a recording
example: false
'401':
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
schemas:
ErrorResponse:
type: object
description: Standard error response returned by all endpoints on failure.
required:
- success
- error
properties:
success:
type: boolean
description: Always `false` for error responses.
enum:
- false
error:
type: string
description: Human-readable error message describing what went wrong. Do not parse this programmatically — use HTTP status codes for control flow.
example: Invalid request parameters
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: 'JWT token obtained from the auth service (/api/auth/token).
Token is validated using JWKS from the auth service.
Required claims: sub (user_id), email, name, tier.
'