openapi: 3.0.3
info:
  title: MoodTrue API
  description: |
    # MoodTrue REST API v1
    
    The MoodTrue API provides programmatic access to MoodTrue platform features, allowing developers to integrate MoodTrue functionality into their applications.
    
    ## Getting Started
    
    ### Authentication
    All API requests require authentication using a developer token. Include your token in the Authorization header:
    
    ```
    Authorization: Bearer YOUR_DEV_TOKEN
    ```
    
    Developer tokens can be created and managed in your account settings under "Developer Tools". Each token is a 64-character hexadecimal string.
    
    ### Rate Limits
    Rate limits vary based on your subscription level:
    - **Free users**: 10 requests/10 seconds, 30/minute, 100/day, 2,000/month
    - **Premium users**: 30 requests/10 seconds, 180/minute, 3,000/day, 75,000/month
    - **Pro users**: 100 requests/10 seconds, 600/minute, 20,000/day, 500,000/month
    
    Successful authenticated responses include `X-RateLimit-*-Limit` and
    `X-RateLimit-*-Remaining` headers for the burst, minute, daily, and monthly
    windows. A rejected request returns HTTP 429 with `Retry-After`,
    `X-RateLimit-Scope`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and
    `X-RateLimit-Reset` headers. The JSON body also contains an ISO 8601
    `reset_at` timestamp.
    
    ### Base URL
    All API requests should be made to:
    ```
    https://api.moodtrue.com/v1
    ```
    
    ### Response Format
    All responses are returned in JSON format. Successful responses return HTTP status codes in the 2xx range, while errors return appropriate 4xx or 5xx status codes with error details.
    
    ### Error Handling
    Error responses follow a consistent format:
    ```json
    {
      "error": "Error message",
      "code": "ERROR_CODE"
    }
    ```
    
    Common error codes:
    - `AUTH_REQUIRED` - Authentication required
    - `RATE_LIMIT_EXCEEDED` - Rate limit exceeded
    - `NOT_FOUND` - Resource not found
    - `INTERNAL_ERROR` - Internal server error
  version: 1.0.0
  contact:
    name: MoodTrue API Support
    url: https://moodtrue.com/support
    email: support@moodtrue.com
  license:
    name: Proprietary
    url: https://moodtrue.com/terms

servers:
  - url: https://api.moodtrue.com
    description: Production server

tags:
  - name: User
    description: User information and profile endpoints
  - name: Social
    description: Privacy-aware public profiles, posts, and follow relationships
  - name: Developer
    description: Developer token and API usage endpoints
  - name: Playlists
    description: Playlist management endpoints
  - name: Moods
    description: User mood analysis and translation endpoints

paths:
  /v1/user:
    get:
      tags:
        - User
      summary: Get current user information
      description: Returns basic information about the authenticated user
      operationId: getUserInfo
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User information retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserInfo'
              example:
                id: 123
                username: "johndoe"
                email: "user@example.com"
                premium: 1
                token:
                  id: 5
                  name: "My API Token"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/user/profile:
    get:
      tags:
        - User
      summary: Get detailed user profile
      description: Returns detailed profile information including Spotify connection, subscription status, and developer token count
      operationId: getUserProfile
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User profile retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
              example:
                id: 123
                username: "johndoe"
                email: "user@example.com"
                premium: 1
                token:
                  id: 5
                  name: "My API Token"
                created_at: "2024-01-15T10:30:00Z"
                username_last_changed: "2024-02-10T14:20:00Z"
                spotify:
                  connected: true
                  username: "spotify_user_123"
                two_factor_enabled: false
                language: "en"
                subscription:
                  plan_id: 1
                  end_date: "2024-12-31T23:59:59Z"
                developer:
                  active_token_count: 2
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/user/mood:
    get:
      tags:
        - Moods
      summary: Get current user mood
      description: Returns the user's current mood based on recent listening activity, including emoji and translations in all available languages
      operationId: getUserMood
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User mood retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserMood'
              example:
                mood: "Sad"
                emoji: "😢"
                translations:
                  tr: "hüzünlü"
                  en: "sad"
                  da: "trist"
                  jp: "悲しい"
                analyzed_at: "2024-01-15T10:30:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/user/mood/history:
    get:
      tags:
        - Moods
      summary: Get user mood history
      description: Returns a paginated list of the user's mood history with optional date filtering
      operationId: getUserMoodHistory
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          description: Number of records to return (1-100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          description: Number of records to skip for pagination
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: start_date
          in: query
          description: Start date for filtering (ISO 8601 format)
          required: false
          schema:
            type: string
            format: date-time
        - name: end_date
          in: query
          description: End date for filtering (ISO 8601 format)
          required: false
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Mood history retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MoodHistory'
              example:
                count: 2
                total: 50
                limit: 50
                offset: 0
                current_page: 1
                total_pages: 1
                last_page: 1
                has_next_page: false
                has_prev_page: false
                history:
                  - mood: "Sad"
                    emoji: "😢"
                    translations:
                      tr: "hüzünlü"
                      en: "sad"
                      da: "trist"
                      jp: "悲しい"
                    created_at: "2024-01-15T10:30:00Z"
                  - mood: "Happy"
                    emoji: "😊"
                    translations:
                      tr: "mutlu"
                      en: "happy"
                      da: "Lykkelig"
                      jp: "幸せ"
                    created_at: "2024-01-14T08:20:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/user/mood/statistics:
    get:
      tags:
        - Moods
      summary: Get user mood statistics
      description: Returns statistical analysis of the user's moods over a specified time period
      operationId: getUserMoodStatistics
      security:
        - bearerAuth: []
      parameters:
        - name: period
          in: query
          description: Time period for statistics
          required: false
          schema:
            type: string
            enum: [daily, weekly, monthly, yearly]
            default: monthly
        - name: limit
          in: query
          description: Number of records to return (1-100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: offset
          in: query
          description: Number of records to skip for pagination
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Mood statistics retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MoodStatistics'
              example:
                period: "monthly"
                start_date: "2023-12-16T00:00:00Z"
                end_date: "2024-01-15T23:59:59Z"
                total_records: 50
                count: 2
                total: 5
                limit: 20
                offset: 0
                current_page: 1
                total_pages: 1
                last_page: 1
                has_next_page: false
                has_prev_page: false
                most_common_moods:
                  - mood: "Sad"
                    emoji: "😢"
                    count: 15
                    percentage: 30.0
                    translations:
                      tr: "hüzünlü"
                      en: "sad"
                      da: "trist"
                      jp: "悲しい"
                  - mood: "Happy"
                    emoji: "😊"
                    count: 10
                    percentage: 20.0
                    translations:
                      tr: "mutlu"
                      en: "happy"
                      da: "Lykkelig"
                      jp: "幸せ"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/users/{username}:
    get:
      tags:
        - Social
      summary: Get a user's public profile
      description: |
        Returns profile information after applying private-account, block, and
        surname visibility rules. Private profiles are available only to the
        account owner or an existing approved follower. Use `me` as the
        username to retrieve the developer-token owner's social profile.
      operationId: getPublicUserProfile
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
      responses:
        '200':
          description: Profile retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicProfileResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/users/{username}/posts:
    get:
      tags:
        - Social
      summary: List a user's posts
      description: |
        Returns posts only when the authenticated token owner is allowed to
        view the target profile. Stored image locations are not exposed.
      operationId: listUserPosts
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
        - $ref: '#/components/parameters/PageLimit'
        - $ref: '#/components/parameters/BeforeId'
      responses:
        '200':
          description: Posts retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicPostList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/users/{username}/mood:
    get:
      tags:
        - Social
      summary: Get a user's current mood
      description: |
        Returns the current mood only when the authenticated token owner may
        view the profile and the user has not hidden Spotify/mood activity.
      operationId: getPublicUserMood
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
      responses:
        '200':
          description: Current mood retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicUserMood'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/users/{username}/followers:
    get:
      tags:
        - Social
      summary: List a user's followers
      description: Honors private-account, hidden-followers, surname, and block settings.
      operationId: listUserFollowers
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
        - $ref: '#/components/parameters/PageLimit'
        - $ref: '#/components/parameters/BeforeId'
      responses:
        '200':
          description: Followers retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicUserList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/users/{username}/following:
    get:
      tags:
        - Social
      summary: List users followed by a user
      description: Honors private-account, hidden-following, surname, and block settings.
      operationId: listUserFollowing
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
        - $ref: '#/components/parameters/PageLimit'
        - $ref: '#/components/parameters/BeforeId'
      responses:
        '200':
          description: Followed users retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicUserList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/users/{username}/follow:
    post:
      tags:
        - Social
      summary: Follow a public account
      description: |
        Creates a follow relationship owned by the authenticated token user.
        Request bodies cannot override the follower. Private-account approval
        cannot be bypassed through this endpoint.
      operationId: followUser
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
      responses:
        '200':
          description: Follow state updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FollowResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Social
      summary: Unfollow an account
      description: Removes only the authenticated token user's own follow relationship.
      operationId: unfollowUser
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
      responses:
        '200':
          description: Follow state updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FollowResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/usage:
    get:
      tags:
        - Developer
      summary: Get API usage
      description: |
        Returns account-wide limits and usage for the developer token used to
        authenticate this request. It does not accept a user or token id.
        This request itself counts toward the returned usage.
      operationId: getApiUsage
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Usage retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/playlists:
    get:
      tags:
        - Playlists
      summary: List all playlists
      description: Returns a list of all playlists belonging to the authenticated user
      operationId: listPlaylists
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Playlists retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaylistList'
              example:
                count: 2
                playlists:
                  - id: 1
                    name: "My Favorite Songs"
                    description: "A collection of my favorite tracks"
                    cover_image: "https://cdn.moodtrue.com/covers/playlist_1.jpg"
                    is_public: true
                    spotify_playlist_id: "37i9dQZF1DXcBWIGoYBM5M"
                    track_count: 25
                    created_at: "2024-01-15T10:30:00Z"
                    updated_at: "2024-01-20T15:45:00Z"
                  - id: 2
                    name: "Workout Mix"
                    description: null
                    cover_image: null
                    is_public: false
                    spotify_playlist_id: null
                    track_count: 15
                    created_at: "2024-01-18T08:20:00Z"
                    updated_at: "2024-01-18T08:20:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/playlists/{id}:
    get:
      tags:
        - Playlists
      summary: Get playlist details
      description: Returns detailed information about a specific playlist including all tracks
      operationId: getPlaylist
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/PlaylistId'
      responses:
        '200':
          description: Playlist retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaylistDetail'
              example:
                id: 1
                name: "My Favorite Songs"
                description: "A collection of my favorite tracks"
                cover_image: "https://cdn.moodtrue.com/covers/playlist_1.jpg"
                is_public: true
                spotify_playlist_id: "37i9dQZF1DXcBWIGoYBM5M"
                track_count: 2
                created_at: "2024-01-15T10:30:00Z"
                updated_at: "2024-01-20T15:45:00Z"
                tracks:
                  - id: 456
                    spotify_track_id: "4uLU6hMCjMI75M1A2tKUQC"
                    name: "Bohemian Rhapsody"
                    artist: "Queen"
                    album: "A Night at the Opera"
                    cover_image: "https://i.scdn.co/image/ab67616d0000b273ba5db46f4b838ef6027e6f96"
                    preview_url: "https://p.scdn.co/mp3-preview/..."
                    duration_ms: 355000
                    position: 1
                    added_at: "2024-01-15T10:30:00Z"
                  - id: 457
                    spotify_track_id: "5JunxkcjWySYzQY4xccw0V"
                    name: "Stairway to Heaven"
                    artist: "Led Zeppelin"
                    album: "Led Zeppelin IV"
                    cover_image: null
                    preview_url: null
                    duration_ms: 482000
                    position: 2
                    added_at: "2024-01-16T14:20:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Playlists
      summary: Delete playlist
      description: |
        Deletes the playlist and its tracks from MoodTrue. This operation does
        not delete or modify a corresponding playlist in Spotify.
      operationId: deletePlaylist
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/PlaylistId'
      responses:
        '200':
          description: Playlist deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Opaque MoodTrue developer token (64-character hexadecimal string). This is not a JWT.

  parameters:
    Username:
      name: username
      in: path
      required: true
      description: MoodTrue username, or `me` for the authenticated token owner
      schema:
        type: string
        maxLength: 100
      example: johndoe

    PageLimit:
      name: limit
      in: query
      required: false
      description: Number of records to return
      schema:
        type: integer
        minimum: 1
        maximum: 50
        default: 20

    BeforeId:
      name: before_id
      in: query
      required: false
      description: Return records older than this cursor id
      schema:
        type: integer
        minimum: 1

    PlaylistId:
      name: id
      in: path
      required: true
      description: Playlist ID
      schema:
        type: integer
        minimum: 1

  schemas:
    PublicUser:
      type: object
      required: [id, username, display_name, avatar, verified]
      properties:
        id:
          type: integer
          example: 123
        username:
          type: string
          example: johndoe
        display_name:
          type: string
          example: John Doe
        first_name:
          type: string
          nullable: true
          example: John
        last_name:
          type: string
          nullable: true
          description: Null when the user has enabled surname privacy
          example: Doe
        avatar:
          type: string
          format: uri
        verified:
          type: boolean

    PublicProfileResponse:
      type: object
      required: [user, stats, relationship, privacy]
      properties:
        user:
          allOf:
            - $ref: '#/components/schemas/PublicUser'
            - type: object
              properties:
                bio:
                  type: string
                created_at:
                  type: string
                  format: date-time
                plan:
                  type: string
                  enum: [free, premium, pro]
                is_private:
                  type: boolean
        stats:
          type: object
          properties:
            followers: {type: integer, minimum: 0}
            following: {type: integer, minimum: 0}
            posts: {type: integer, minimum: 0}
        relationship:
          type: object
          properties:
            is_self: {type: boolean}
            is_following: {type: boolean}
        privacy:
          type: object
          properties:
            followers_hidden: {type: boolean}
            following_hidden: {type: boolean}

    PublicUserList:
      type: object
      required: [data, count, next_before_id]
      properties:
        data:
          type: array
          items:
            allOf:
              - $ref: '#/components/schemas/PublicUser'
              - type: object
                properties:
                  is_following: {type: boolean}
        count:
          type: integer
          minimum: 0
        next_before_id:
          type: integer
          nullable: true

    PublicPost:
      type: object
      required: [id, type, like_count, comment_count, liked_by_me, created_at]
      properties:
        id: {type: integer}
        type: {type: string}
        content: {type: string, nullable: true}
        mood: {type: string, nullable: true}
        mood_emoji: {type: string, nullable: true}
        image_url: {type: string, format: uri, nullable: true}
        music:
          type: object
          nullable: true
          properties:
            cover: {type: string, nullable: true}
            title: {type: string}
            artist: {type: string, nullable: true}
        like_count: {type: integer, minimum: 0}
        comment_count: {type: integer, minimum: 0}
        liked_by_me: {type: boolean}
        created_at: {type: string, format: date-time}

    PublicPostList:
      type: object
      required: [data, count, next_before_id]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PublicPost'
        count: {type: integer, minimum: 0}
        next_before_id: {type: integer, nullable: true}

    PublicUserMood:
      type: object
      required: [has_mood, mood, mood_original, emoji, translations, analyzed_at]
      properties:
        has_mood: {type: boolean}
        mood: {type: string, nullable: true, description: English mood label when available}
        mood_original: {type: string, nullable: true}
        emoji: {type: string}
        translations:
          type: object
          additionalProperties: {type: string}
        analyzed_at: {type: string, format: date-time}

    FollowResponse:
      type: object
      required: [success, username, is_following]
      properties:
        success: {type: boolean}
        username: {type: string}
        is_following: {type: boolean}

    UsageWindow:
      type: object
      required: [used, limit]
      properties:
        used: {type: integer, minimum: 0}
        limit: {type: integer, minimum: 0}
        resets_on: {type: string, format: date}

    UsageResponse:
      type: object
      required: [plan, token, account]
      properties:
        plan:
          type: string
          enum: [free, premium, pro]
        token:
          type: object
          required: [id, name, daily, monthly]
          properties:
            id: {type: integer}
            name: {type: string}
            daily: {$ref: '#/components/schemas/UsageWindow'}
            monthly: {$ref: '#/components/schemas/UsageWindow'}
        account:
          type: object
          required: [burst, minute, daily, monthly]
          properties:
            burst:
              allOf:
                - $ref: '#/components/schemas/UsageWindow'
                - type: object
                  properties:
                    window_seconds: {type: integer, minimum: 1}
            minute: {$ref: '#/components/schemas/UsageWindow'}
            daily: {$ref: '#/components/schemas/UsageWindow'}
            monthly: {$ref: '#/components/schemas/UsageWindow'}

    UserInfo:
      type: object
      required:
        - id
        - username
        - email
        - premium
        - token
      properties:
        id:
          type: integer
          description: User ID
          example: 123
        username:
          type: string
          description: Username
          example: "johndoe"
        email:
          type: string
          format: email
          description: User email address
          example: "user@example.com"
        premium:
          type: integer
          enum: [0, 1]
          description: Premium status determined from active subscription (0 = no active subscription, 1 = has active subscription)
          example: 1
        token:
          type: object
          required:
            - id
            - name
          properties:
            id:
              type: integer
              description: Developer token ID used for this request
              example: 5
            name:
              type: string
              description: Developer token name
              example: "My API Token"

    UserProfile:
      allOf:
        - $ref: '#/components/schemas/UserInfo'
        - type: object
          required:
            - id
            - username
            - email
            - premium
            - created_at
            - username_last_changed
            - spotify
            - two_factor_enabled
            - language
            - subscription
            - developer
          properties:
            created_at:
              type: string
              format: date-time
              description: Account creation timestamp
              example: "2024-01-15T10:30:00Z"
            username_last_changed:
              type: string
              nullable: true
              format: date-time
              description: Timestamp when username was last changed. null if never changed.
              example: "2024-02-10T14:20:00Z"
            spotify:
              type: object
              required:
                - connected
              properties:
                connected:
                  type: boolean
                  description: Whether Spotify account is connected
                  example: true
                username:
                  type: string
                  nullable: true
                  description: Spotify username
                  example: "spotify_user_123"
            two_factor_enabled:
              type: boolean
              description: Whether two-factor authentication is enabled
              example: false
            language:
              type: string
              description: User's preferred language code (ISO 639-1)
              example: "en"
              default: "en"
            subscription:
              type: object
              required:
                - plan_id
                - end_date
              properties:
                plan_id:
                  type: integer
                  nullable: true
                  description: Active subscription plan ID. 1 = Premium, 2 = Pro. null if no active subscription.
                  enum: [1, 2]
                  example: 1
                end_date:
                  type: string
                  nullable: true
                  format: date-time
                  description: Subscription end date. null if no active subscription.
                  example: "2024-12-31T23:59:59Z"
            developer:
              type: object
              required:
                - active_token_count
              properties:
                active_token_count:
                  type: integer
                  description: Number of active developer tokens for this user
                  minimum: 0
                  example: 2

    Playlist:
      type: object
      required:
        - id
        - name
        - is_public
        - track_count
        - created_at
        - updated_at
      properties:
        id:
          type: integer
          description: Playlist ID
          example: 123
        name:
          type: string
          description: Playlist name
          maxLength: 255
          example: "My Favorite Songs"
        description:
          type: string
          nullable: true
          description: Playlist description
          example: "A collection of my favorite tracks"
        cover_image:
          type: string
          nullable: true
          description: Cover image URL
          example: "https://cdn.moodtrue.com/covers/playlist_123.jpg"
        is_public:
          type: boolean
          description: Whether playlist is publicly visible
          example: true
        spotify_playlist_id:
          type: string
          nullable: true
          description: Associated Spotify playlist ID (if synced)
          example: "37i9dQZF1DXcBWIGoYBM5M"
        track_count:
          type: integer
          description: Number of tracks in playlist
          minimum: 0
          example: 25
        created_at:
          type: string
          format: date-time
          description: Playlist creation timestamp
          example: "2024-01-15T10:30:00Z"
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp
          example: "2024-01-20T15:45:00Z"

    PlaylistDetail:
      allOf:
        - $ref: '#/components/schemas/Playlist'
        - type: object
          properties:
            tracks:
              type: array
              items:
                $ref: '#/components/schemas/Track'

    PlaylistList:
      type: object
      required:
        - count
        - playlists
      properties:
        count:
          type: integer
          description: Total number of playlists
          minimum: 0
          example: 5
        playlists:
          type: array
          description: List of playlists
          items:
            $ref: '#/components/schemas/Playlist'

    Track:
      type: object
      required:
        - id
        - spotify_track_id
        - name
        - artist
        - position
        - added_at
      properties:
        id:
          type: integer
          description: Track ID in playlist
          example: 456
        spotify_track_id:
          type: string
          description: Spotify track identifier
          example: "4uLU6hMCjMI75M1A2tKUQC"
        name:
          type: string
          description: Track name
          example: "Bohemian Rhapsody"
        artist:
          type: string
          description: Artist name
          example: "Queen"
        album:
          type: string
          nullable: true
          description: Album name
          example: "A Night at the Opera"
        cover_image:
          type: string
          nullable: true
          description: Album cover image URL
          example: "https://i.scdn.co/image/ab67616d0000b273ba5db46f4b838ef6027e6f96"
        preview_url:
          type: string
          nullable: true
          description: 30-second preview audio URL
          example: "https://p.scdn.co/mp3-preview/..."
        duration_ms:
          type: integer
          nullable: true
          description: Track duration in milliseconds
          example: 355000
        position:
          type: integer
          description: Position in playlist (1-based)
          minimum: 1
          example: 1
        added_at:
          type: string
          format: date-time
          description: Timestamp when track was added to playlist
          example: "2024-01-15T10:30:00Z"

    SuccessResponse:
      type: object
      required:
        - success
        - message
      properties:
        success:
          type: boolean
          description: Indicates if the operation was successful
          example: true
        message:
          type: string
          description: Success message
          example: "Operation completed successfully"

    Error:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: Human-readable error message
          example: "Internal server error"
        code:
          type: string
          description: Machine-readable error code
          example: "INTERNAL_ERROR"
        limit:
          type: integer
          description: Limit for the exceeded window (only present for rate-limit errors)
          example: 30
        used:
          type: integer
          description: Requests used in the exceeded window (only present for rate-limit errors)
          example: 30
        retry_after:
          type: integer
          description: Seconds until another request may be attempted
          example: 18
        reset_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the exceeded window resets
          example: "2026-08-07T00:00:00Z"
        limit_type:
          type: string
          enum: [burst, minute, daily, monthly]
          description: Rate-limit window that rejected the request
          example: minute

    UserMood:
      type: object
      required:
        - mood
        - emoji
        - translations
        - analyzed_at
      properties:
        mood:
          type: string
          nullable: true
          description: Current mood name in English. null if mood cannot be determined.
          example: "Sad"
        emoji:
          type: string
          description: Emoji representation of the mood
          example: "😢"
        translations:
          type: object
          description: Mood translations in different languages (language code => translation)
          additionalProperties:
            type: string
          example:
            tr: "hüzünlü"
            en: "sad"
            da: "trist"
            jp: "悲しい"
        analyzed_at:
          type: string
          format: date-time
          description: Timestamp when mood was analyzed
          example: "2024-01-15T10:30:00Z"

    MoodHistoryItem:
      type: object
      required:
        - mood
        - emoji
        - translations
        - created_at
      properties:
        mood:
          type: string
          description: Mood name in English
          example: "Sad"
        emoji:
          type: string
          description: Emoji representation of the mood
          example: "😢"
        translations:
          type: object
          description: Mood translations in different languages
          additionalProperties:
            type: string
          example:
            tr: "hüzünlü"
            en: "sad"
            da: "trist"
            jp: "悲しい"
        created_at:
          type: string
          format: date-time
          description: Timestamp when mood was recorded
          example: "2024-01-15T10:30:00Z"

    MoodHistory:
      type: object
      required:
        - count
        - total
        - limit
        - offset
        - current_page
        - total_pages
        - last_page
        - has_next_page
        - has_prev_page
        - history
      properties:
        count:
          type: integer
          description: Number of records in this response
          minimum: 0
          example: 2
        total:
          type: integer
          description: Total number of records available
          minimum: 0
          example: 50
        limit:
          type: integer
          description: Maximum number of records requested (max 100)
          minimum: 1
          maximum: 100
          example: 50
        offset:
          type: integer
          description: Number of records skipped
          minimum: 0
          example: 0
        current_page:
          type: integer
          description: Current page number (1-based)
          minimum: 1
          example: 1
        total_pages:
          type: integer
          description: Total number of pages
          minimum: 1
          example: 1
        last_page:
          type: integer
          description: Last page number (same as total_pages)
          minimum: 1
          example: 1
        has_next_page:
          type: boolean
          description: Whether there is a next page
          example: false
        has_prev_page:
          type: boolean
          description: Whether there is a previous page
          example: false
        history:
          type: array
          description: List of mood history records
          items:
            $ref: '#/components/schemas/MoodHistoryItem'

    MoodStatistics:
      type: object
      required:
        - period
        - start_date
        - end_date
        - total_records
        - count
        - total
        - limit
        - offset
        - current_page
        - total_pages
        - last_page
        - has_next_page
        - has_prev_page
        - most_common_moods
      properties:
        period:
          type: string
          enum: [daily, weekly, monthly, yearly]
          description: Time period for statistics
          example: "monthly"
        start_date:
          type: string
          format: date-time
          description: Start date of the statistics period
          example: "2023-12-16T00:00:00Z"
        end_date:
          type: string
          format: date-time
          description: End date of the statistics period
          example: "2024-01-15T23:59:59Z"
        total_records:
          type: integer
          description: Total number of mood records in the period
          minimum: 0
          example: 50
        count:
          type: integer
          description: Number of moods in this response
          minimum: 0
          example: 2
        total:
          type: integer
          description: Total number of unique moods available
          minimum: 0
          example: 5
        limit:
          type: integer
          description: Maximum number of records requested (max 100)
          minimum: 1
          maximum: 100
          example: 20
        offset:
          type: integer
          description: Number of records skipped
          minimum: 0
          example: 0
        current_page:
          type: integer
          description: Current page number (1-based)
          minimum: 1
          example: 1
        total_pages:
          type: integer
          description: Total number of pages
          minimum: 1
          example: 1
        last_page:
          type: integer
          description: Last page number (same as total_pages)
          minimum: 1
          example: 1
        has_next_page:
          type: boolean
          description: Whether there is a next page
          example: false
        has_prev_page:
          type: boolean
          description: Whether there is a previous page
          example: false
        most_common_moods:
          type: array
          description: List of most common moods ordered by frequency
          items:
            $ref: '#/components/schemas/MoodStatisticItem'

    MoodStatisticItem:
      type: object
      required:
        - mood
        - emoji
        - count
        - percentage
        - translations
      properties:
        mood:
          type: string
          description: Mood name in English
          example: "Sad"
        emoji:
          type: string
          description: Emoji representation of the mood
          example: "😢"
        count:
          type: integer
          description: Number of times this mood appeared
          minimum: 0
          example: 15
        percentage:
          type: number
          format: float
          description: Percentage of total records this mood represents
          minimum: 0
          maximum: 100
          example: 30.0
        translations:
          type: object
          description: Mood translations in different languages
          additionalProperties:
            type: string
          example:
            tr: "hüzünlü"
            en: "sad"
            da: "trist"
            jp: "悲しい"

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    Unauthorized:
      description: Unauthorized - Invalid or missing developer token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    Forbidden:
      description: Forbidden - Premium subscription required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    RateLimitExceeded:
      description: Rate limit exceeded
      headers:
        Retry-After:
          description: Seconds until another request may be attempted
          schema:
            type: integer
        X-RateLimit-Scope:
          description: Window that rejected the request
          schema:
            type: string
            enum: [burst, minute, daily, monthly]
        X-RateLimit-Limit:
          description: Limit for the exceeded window
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Remaining requests in the exceeded window
          schema:
            type: integer
        X-RateLimit-Reset:
          description: Unix timestamp when the exceeded window resets
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

