openapi: 3.1.0
info:
  title: Veyra API
  version: 1.0.0
  description: 'REST API for tracked Veyra Discord users and service health.


    **Authentication:** None required. All endpoints are public.


    **Rate limiting:** 200 requests per minute per IP, applied globally to all `/api/*`
    routes. Exceeding this returns `429 RATE_LIMITED`. Standard `RateLimit-*` headers
    are included on every response.'
  x-logo:
    url: /favicon.svg
servers:
- url: https://veyra-db3d.onbelmo.uk
paths:
  /api/user/{userId}:
    get:
      summary: Get tracked user
      description: Returns the current serialized profile and presence snapshot for
        a tracked Discord user. No authentication required. Subject to the global
        rate limit (200 req/min per IP).
      parameters:
      - name: userId
        in: path
        required: true
        schema:
          type: string
          pattern: ^\d{17,20}$
        description: Discord user snowflake.
      responses:
        '200':
          description: User is tracked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSuccess'
              example:
                success: true
                data:
                  kv: {}
                  discord_user:
                    id: '123456789012345678'
                    username: lorb
                    global_name: Lorb
                    display_name: Lorb
                    discriminator: '0'
                    avatar: a_1234567890abcdef1234567890abcdef
                    avatar_url: https://cdn.discordapp.com/avatars/123456789012345678/a_1234567890abcdef1234567890abcdef.png
                    avatar_decoration_data: null
                    banner: null
                    banner_url: null
                    accent_color: null
                    public_flags: 4194432
                    badges:
                    - early_supporter
                    - active_developer
                    primary_guild: null
                  activities:
                  - id: spotify:1
                    name: Spotify
                    type: 2
                    state: Radiohead
                    details: Everything In Its Right Place
                    application_id: '123456789012345678'
                    timestamps:
                      start: 1735689600000
                      end: 1735689780000
                    assets:
                      large_image: spotify:ab67616d0000b273
                      large_text: Kid A
                    party: null
                    sync_id: 4Jz6E3Ie3sYGQ7lHhY0Zz3
                    flags: null
                    created_at: 1735689600000
                    buttons: []
                  discord_status: online
                  active_on_discord_web: false
                  active_on_discord_desktop: true
                  active_on_discord_mobile: false
                  active_on_discord_embedded: false
                  active_on_discord_vr: false
                  listening_to_spotify: true
                  spotify:
                    track_id: 4Jz6E3Ie3sYGQ7lHhY0Zz3
                    timestamps:
                      start: 1735689600000
                      end: 1735689780000
                    song: Everything In Its Right Place
                    artist: Radiohead
                    album: Kid A
                    album_art_url: https://i.scdn.co/image/ab67616d0000b273
                  custom_status:
                    text: shipping veyra
                    emoji: null
                    expires_at: null
                  guild:
                    id: '987654321098765432'
                    name: Veyra HQ
                  last_updated: '2026-09-03T12:00:00.000Z'
                  tracked_since: '2026-01-15T09:30:00.000Z'
        '400':
          description: Invalid Discord user ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: INVALID_ID
                  message: Invalid Discord user ID.
        '404':
          description: User is not tracked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: USER_NOT_TRACKED
                  message: This user is not currently tracked by Veyra.
        '429':
          description: Rate limited.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: RATE_LIMITED
                  message: Too many requests.
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: SERVER_ERROR
                  message: Internal server error.
      x-rateLimit: 200 requests / minute per IP (shared across all /api routes)
      x-auth: None
  /health/json:
    get:
      summary: Get service health
      description: Returns live service health fields. No authentication required.
        Not subject to the /api rate limit.
      responses:
        '200':
          description: Health state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'
              example:
                status: ok
                service: Veyra
                discord: true
                database: true
                badge_api: true
                uptime: 184320
                tracked_users: 42
                ws_connections: 7
                timestamp: '2026-09-03T12:00:00.000Z'
      x-rateLimit: None
      x-auth: None
components:
  schemas:
    ErrorResponse:
      type: object
      required:
      - success
      - error
      properties:
        success:
          type: boolean
          const: false
        error:
          type: object
          required:
          - code
          - message
          properties:
            code:
              type: string
              enum:
              - INVALID_ID
              - BAD_REQUEST
              - FORBIDDEN
              - USER_NOT_TRACKED
              - NOT_FOUND
              - RATE_LIMITED
              - SERVER_ERROR
            message:
              type: string
    UserSuccess:
      type: object
      required:
      - success
      - data
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: '#/components/schemas/User'
    User:
      type: object
      required:
      - kv
      - discord_user
      - activities
      - discord_status
      - listening_to_spotify
      - guild
      properties:
        kv:
          type: object
        discord_user:
          $ref: '#/components/schemas/DiscordUser'
        activities:
          type: array
          items:
            $ref: '#/components/schemas/Activity'
        discord_status:
          type: string
          enum:
          - online
          - idle
          - dnd
          - offline
        active_on_discord_web:
          type: boolean
        active_on_discord_desktop:
          type: boolean
        active_on_discord_mobile:
          type: boolean
        active_on_discord_embedded:
          type: boolean
        active_on_discord_vr:
          type: boolean
        listening_to_spotify:
          type: boolean
        spotify:
          anyOf:
          - $ref: '#/components/schemas/Spotify'
          - type: 'null'
        custom_status:
          anyOf:
          - $ref: '#/components/schemas/CustomStatus'
          - type: 'null'
        guild:
          $ref: '#/components/schemas/Guild'
        last_updated:
          type: string
          format: date-time
        tracked_since:
          type: string
          format: date-time
    DiscordUser:
      type: object
      properties:
        id:
          type: string
        username:
          type: string
        global_name:
          type:
          - string
          - 'null'
        display_name:
          type:
          - string
          - 'null'
        discriminator:
          type:
          - string
          - 'null'
        avatar:
          type:
          - string
          - 'null'
        avatar_url:
          type:
          - string
          - 'null'
        avatar_decoration_data:
          type:
          - object
          - 'null'
        banner:
          type:
          - string
          - 'null'
        banner_url:
          type:
          - string
          - 'null'
        accent_color:
          type:
          - integer
          - 'null'
        public_flags:
          type: integer
        badges:
          type: array
          items:
            type: string
        primary_guild:
          type:
          - object
          - 'null'
    Activity:
      type: object
      properties:
        id:
          type:
          - string
          - 'null'
        name:
          type:
          - string
          - 'null'
        type:
          type:
          - integer
          - 'null'
        state:
          type:
          - string
          - 'null'
        details:
          type:
          - string
          - 'null'
        application_id:
          type:
          - string
          - 'null'
        timestamps:
          type:
          - object
          - 'null'
        assets:
          type:
          - object
          - 'null'
        party:
          type:
          - object
          - 'null'
        sync_id:
          type:
          - string
          - 'null'
        flags:
          type:
          - integer
          - 'null'
        created_at:
          type:
          - integer
          - 'null'
        buttons:
          type: array
          items:
            type: string
    Spotify:
      type: object
      properties:
        track_id:
          type:
          - string
          - 'null'
        timestamps:
          type: object
        song:
          type:
          - string
          - 'null'
        artist:
          type:
          - string
          - 'null'
        album:
          type:
          - string
          - 'null'
        album_art_url:
          type:
          - string
          - 'null'
    CustomStatus:
      type: object
      properties:
        text:
          type:
          - string
          - 'null'
        emoji:
          type:
          - string
          - 'null'
        emoji_id:
          type:
          - string
          - 'null'
        emoji_name:
          type:
          - string
          - 'null'
    Guild:
      type: object
      properties:
        id:
          type:
          - string
          - 'null'
        nickname:
          type:
          - string
          - 'null'
        joined_at:
          type:
          - string
          - 'null'
          format: date-time
    Health:
      type: object
      properties:
        status:
          type: string
          enum:
          - ok
          - degraded
        service:
          type: string
        discord:
          type: boolean
        database:
          type: boolean
        badge_api:
          type: boolean
        uptime:
          type: integer
        tracked_users:
          type:
          - integer
          - 'null'
        ws_connections:
          type:
          - integer
          - 'null'
        timestamp:
          type: string
          format: date-time
  securitySchemes: {}
