openapi: 3.0.3
info:
  title: FeedbackKit API
  description: |
    REST API for FeedbackKit - a feedback collection platform for iOS, macOS, and visionOS apps.

    ## Authentication

    SDK clients authenticate using the `X-API-Key` header with a project-specific API key.

    Optionally include `X-User-Id` header to get personalized `hasVoted` state in responses.

    ## Rate Limiting

    API requests are subject to rate limiting. If you exceed the limit, you'll receive a 429 response.

    ## Subscription Tiers

    Some features are limited by subscription tier:
    - **Free**: 1 project, 10 feedback items per project
    - **Pro**: 2 projects, unlimited feedback, integrations
    - **Team**: Unlimited projects, team members, voter notifications
  version: 1.0.0
  contact:
    name: FeedbackKit Support
    email: support@swiftly-developed.com
    url: https://swiftly-developed.com/feedbackkit
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: http://localhost:8080/api/v1
    description: Local development server
  - url: https://api.feedbackkit.dev/api/v1
    description: Development server
  - url: https://api.feedbackkit.testflight/api/v1
    description: TestFlight/Staging server
  - url: https://api.feedbackkit.app/api/v1
    description: Production server

tags:
  - name: Feedback
    description: Create, read, and manage feedback items
  - name: Votes
    description: Vote on feedback items
  - name: Comments
    description: Add and view comments on feedback
  - name: Users
    description: SDK user registration and tracking
  - name: Events
    description: Event tracking and analytics

security:
  - ApiKeyAuth: []

paths:
  /feedbacks:
    get:
      tags:
        - Feedback
      summary: List feedback
      description: |
        Retrieves all feedback for the project. Results are sorted by vote count (descending).

        Merged feedback items are excluded by default.
      operationId: listFeedback
      parameters:
        - name: status
          in: query
          description: Filter by feedback status
          schema:
            $ref: '#/components/schemas/FeedbackStatus'
        - name: category
          in: query
          description: Filter by feedback category
          schema:
            $ref: '#/components/schemas/FeedbackCategory'
        - name: includeMerged
          in: query
          description: Include merged feedback items
          schema:
            type: boolean
            default: false
        - $ref: '#/components/parameters/UserIdHeader'
      responses:
        '200':
          description: List of feedback items
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FeedbackResponse'
              example:
                - id: "550e8400-e29b-41d4-a716-446655440000"
                  title: "Add dark mode support"
                  description: "It would be great to have a dark mode option."
                  status: "approved"
                  category: "feature_request"
                  userId: "user_12345"
                  userEmail: null
                  voteCount: 42
                  hasVoted: false
                  commentCount: 5
                  totalMrr: 499.50
                  createdAt: "2026-02-01T10:30:00Z"
                  updatedAt: "2026-02-05T14:20:00Z"
                  rejectionReason: null
                  mergedIntoId: null
                  mergedAt: null
                  mergedFeedbackIds: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

    post:
      tags:
        - Feedback
      summary: Submit feedback
      description: |
        Creates a new feedback item. The creator automatically receives a vote (voteCount starts at 1).

        Triggers notifications to project members and configured integrations.
      operationId: createFeedback
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateFeedbackRequest'
            example:
              title: "Add dark mode support"
              description: "It would be great to have a dark mode option for the app."
              category: "feature_request"
              userId: "user_12345"
              userEmail: "user@example.com"
      responses:
        '200':
          description: Feedback created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeedbackResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'

  /feedbacks/{feedbackId}:
    get:
      tags:
        - Feedback
      summary: Get feedback by ID
      description: Retrieves a single feedback item by its ID.
      operationId: getFeedback
      parameters:
        - $ref: '#/components/parameters/FeedbackId'
        - $ref: '#/components/parameters/UserIdHeader'
      responses:
        '200':
          description: Feedback details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeedbackResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /feedbacks/{feedbackId}/votes:
    post:
      tags:
        - Votes
      summary: Vote for feedback
      description: |
        Adds a vote to the feedback item. Each user can only vote once per feedback.

        **Restrictions:**
        - Cannot vote on archived projects (403)
        - Cannot vote on completed or rejected feedback (403)
        - Cannot vote twice (409)

        **Notifications:**
        - Set `notifyStatusChange: true` with an email to receive status update emails
        - Voter notifications require Team tier subscription
      operationId: vote
      parameters:
        - $ref: '#/components/parameters/FeedbackId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateVoteRequest'
            example:
              userId: "user_12345"
              email: "user@example.com"
              notifyStatusChange: true
      responses:
        '200':
          description: Vote recorded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VoteResponse'
              example:
                feedbackId: "550e8400-e29b-41d4-a716-446655440000"
                voteCount: 43
                hasVoted: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

    delete:
      tags:
        - Votes
      summary: Remove vote
      description: Removes the user's vote from the feedback item.
      operationId: unvote
      parameters:
        - $ref: '#/components/parameters/FeedbackId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeleteVoteRequest'
            example:
              userId: "user_12345"
      responses:
        '200':
          description: Vote removed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VoteResponse'
              example:
                feedbackId: "550e8400-e29b-41d4-a716-446655440000"
                voteCount: 42
                hasVoted: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /feedbacks/{feedbackId}/comments:
    get:
      tags:
        - Comments
      summary: List comments
      description: Retrieves all comments for a feedback item, sorted by creation time (ascending).
      operationId: listComments
      parameters:
        - $ref: '#/components/parameters/FeedbackId'
      responses:
        '200':
          description: List of comments
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CommentResponse'
              example:
                - id: "660e8400-e29b-41d4-a716-446655440001"
                  content: "Thanks for the suggestion! We're looking into this."
                  userId: "admin_user"
                  isAdmin: true
                  createdAt: "2026-02-02T09:15:00Z"
                - id: "660e8400-e29b-41d4-a716-446655440002"
                  content: "This would be really helpful for night time use."
                  userId: "user_67890"
                  isAdmin: false
                  createdAt: "2026-02-03T16:45:00Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    post:
      tags:
        - Comments
      summary: Add comment
      description: |
        Adds a comment to the feedback item.

        **Restrictions:**
        - Cannot comment on archived projects (403)
      operationId: createComment
      parameters:
        - $ref: '#/components/parameters/FeedbackId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCommentRequest'
            example:
              content: "This would be really helpful for night time use."
              userId: "user_67890"
              isAdmin: false
      responses:
        '200':
          description: Comment created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommentResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /users/register:
    post:
      tags:
        - Users
      summary: Register SDK user
      description: |
        Registers or updates an SDK user. Use this to track user activity and associate MRR (Monthly Recurring Revenue).

        If the user already exists, their `lastSeenAt` timestamp and MRR will be updated.
      operationId: registerUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterUserRequest'
            example:
              userId: "user_12345"
              mrr: 9.99
      responses:
        '200':
          description: User registered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
              example:
                id: "770e8400-e29b-41d4-a716-446655440000"
                userId: "user_12345"
                mrr: 9.99
                firstSeenAt: "2026-01-15T08:00:00Z"
                lastSeenAt: "2026-02-08T12:30:00Z"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /events/track:
    post:
      tags:
        - Events
      summary: Track event
      description: |
        Tracks a custom event for analytics. Use this to measure user engagement with the feedback system.

        **Common events:**
        - `feedback_list` - User viewed feedback list
        - `feedback_detail` - User viewed feedback details
        - `submit_feedback` - User opened submit form
        - Custom events for your app
      operationId: trackEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TrackEventRequest'
            example:
              eventName: "feedback_list"
              userId: "user_12345"
              properties:
                filter: "feature_request"
                sort: "votes"
      responses:
        '200':
          description: Event tracked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventResponse'
              example:
                id: "880e8400-e29b-41d4-a716-446655440000"
                eventName: "feedback_list"
                userId: "user_12345"
                properties:
                  filter: "feature_request"
                  sort: "votes"
                createdAt: "2026-02-08T12:35:00Z"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Project API key for SDK authentication

  parameters:
    FeedbackId:
      name: feedbackId
      in: path
      required: true
      description: Unique identifier of the feedback item
      schema:
        type: string
        format: uuid

    UserIdHeader:
      name: X-User-Id
      in: header
      required: false
      description: Current user ID to determine `hasVoted` state in responses
      schema:
        type: string

  schemas:
    # Enums
    FeedbackStatus:
      type: string
      enum:
        - pending
        - approved
        - in_progress
        - testflight
        - completed
        - rejected
      description: |
        Status of the feedback item:
        - `pending` - New, awaiting review
        - `approved` - Accepted for consideration
        - `in_progress` - Currently being worked on
        - `testflight` - Available in beta/TestFlight
        - `completed` - Shipped and available
        - `rejected` - Won't be implemented

    FeedbackCategory:
      type: string
      enum:
        - feature_request
        - bug_report
        - improvement
        - other
      description: |
        Category of the feedback:
        - `feature_request` - New functionality
        - `bug_report` - Issue or problem
        - `improvement` - Enhancement to existing feature
        - `other` - General feedback

    # Request schemas
    CreateFeedbackRequest:
      type: object
      required:
        - title
        - description
        - category
        - userId
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: Brief title of the feedback
        description:
          type: string
          minLength: 1
          maxLength: 5000
          description: Detailed description of the feedback
        category:
          $ref: '#/components/schemas/FeedbackCategory'
        userId:
          type: string
          minLength: 1
          description: Unique identifier of the user submitting feedback
        userEmail:
          type: string
          format: email
          description: Optional email for status update notifications

    CreateVoteRequest:
      type: object
      required:
        - userId
      properties:
        userId:
          type: string
          minLength: 1
          description: Unique identifier of the voting user
        email:
          type: string
          format: email
          description: Email for status change notifications (optional)
        notifyStatusChange:
          type: boolean
          default: false
          description: Opt-in to receive email notifications when status changes

    DeleteVoteRequest:
      type: object
      required:
        - userId
      properties:
        userId:
          type: string
          minLength: 1
          description: Unique identifier of the user removing their vote

    CreateCommentRequest:
      type: object
      required:
        - content
        - userId
      properties:
        content:
          type: string
          minLength: 1
          maxLength: 2000
          description: Comment text
        userId:
          type: string
          minLength: 1
          description: Unique identifier of the commenting user
        isAdmin:
          type: boolean
          default: false
          description: Whether this comment is from an admin/developer

    RegisterUserRequest:
      type: object
      required:
        - userId
      properties:
        userId:
          type: string
          minLength: 1
          description: Unique identifier of the SDK user
        mrr:
          type: number
          format: double
          minimum: 0
          description: Monthly Recurring Revenue for this user (optional)

    TrackEventRequest:
      type: object
      required:
        - eventName
        - userId
      properties:
        eventName:
          type: string
          minLength: 1
          maxLength: 100
          description: Name of the event to track
        userId:
          type: string
          minLength: 1
          description: Unique identifier of the user
        properties:
          type: object
          additionalProperties: true
          description: Optional key-value properties for the event

    # Response schemas
    FeedbackResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier
        title:
          type: string
          description: Feedback title
        description:
          type: string
          description: Feedback description
        status:
          $ref: '#/components/schemas/FeedbackStatus'
        category:
          $ref: '#/components/schemas/FeedbackCategory'
        userId:
          type: string
          description: ID of the user who submitted the feedback
        userEmail:
          type: string
          format: email
          nullable: true
          description: Email of the submitter (if provided)
        voteCount:
          type: integer
          minimum: 0
          description: Total number of votes
        hasVoted:
          type: boolean
          description: Whether the current user (from X-User-Id header) has voted
        commentCount:
          type: integer
          minimum: 0
          description: Total number of comments
        totalMrr:
          type: number
          format: double
          nullable: true
          description: Combined MRR of all voters (if tracked)
        createdAt:
          type: string
          format: date-time
          description: When the feedback was created
        updatedAt:
          type: string
          format: date-time
          description: When the feedback was last updated
        rejectionReason:
          type: string
          nullable: true
          maxLength: 500
          description: Explanation for rejection (only for rejected status)
        mergedIntoId:
          type: string
          format: uuid
          nullable: true
          description: ID of the feedback this was merged into
        mergedAt:
          type: string
          format: date-time
          nullable: true
          description: When this feedback was merged
        mergedFeedbackIds:
          type: array
          items:
            type: string
            format: uuid
          nullable: true
          description: IDs of feedback items merged into this one

    VoteResponse:
      type: object
      properties:
        feedbackId:
          type: string
          format: uuid
          description: ID of the feedback item
        voteCount:
          type: integer
          minimum: 0
          description: Updated vote count
        hasVoted:
          type: boolean
          description: Whether the user has voted after this action

    CommentResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier
        content:
          type: string
          description: Comment text
        userId:
          type: string
          description: ID of the commenting user
        isAdmin:
          type: boolean
          description: Whether this is an admin comment
        createdAt:
          type: string
          format: date-time
          description: When the comment was created

    UserResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Internal unique identifier
        userId:
          type: string
          description: SDK user identifier
        mrr:
          type: number
          format: double
          nullable: true
          description: Monthly Recurring Revenue
        firstSeenAt:
          type: string
          format: date-time
          description: When the user was first registered
        lastSeenAt:
          type: string
          format: date-time
          description: When the user was last seen

    EventResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier
        eventName:
          type: string
          description: Name of the tracked event
        userId:
          type: string
          description: User who triggered the event
        properties:
          type: object
          additionalProperties: true
          nullable: true
          description: Event properties
        createdAt:
          type: string
          format: date-time
          description: When the event was tracked

    # Error schemas
    ErrorResponse:
      type: object
      required:
        - error
        - reason
      properties:
        error:
          type: boolean
          default: true
        reason:
          type: string
          description: Human-readable error message

  responses:
    BadRequest:
      description: Bad Request - Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: true
            reason: "Title is required"

    Unauthorized:
      description: Unauthorized - Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: true
            reason: "Invalid API key"

    PaymentRequired:
      description: Payment Required - Subscription tier limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: true
            reason: "Feedback limit reached. Upgrade to Pro for unlimited feedback."

    Forbidden:
      description: Forbidden - Action not allowed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: true
            reason: "Cannot vote on archived projects"

    NotFound:
      description: Not Found - Resource does not exist
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: true
            reason: "Feedback not found"

    Conflict:
      description: Conflict - Duplicate action
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: true
            reason: "User has already voted for this feedback"
