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.

OpenAPI Specification

sageox-users-api-openapi.yml Raw ↑
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.

        '