openapi: 3.1.0
info:
  title: brainX CRM Sync API (PWA consumption)
  version: 0.1.0
  description: Subset of CRM REST endpoints consumed by the Nuxt PWA for offline-first operation.
servers:
  - url: https://jdzaack.tunnel.brain-app.de/api
paths:
  /auth/login:
    post:
      summary: Authenticate user credentials
      operationId: login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                password:
                  type: string
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema:
                type: object
                required: [accessToken, refreshToken, user]
                properties:
                  accessToken:
                    type: string
                  refreshToken:
                    type: string
                  user:
                    $ref: '#/components/schemas/UserProfile'
        '401':
          description: Invalid credentials
  /auth/totp/verify:
    post:
      summary: Verify optional TOTP challenge
      operationId: verifyTotp
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code]
              properties:
                code:
                  type: string
                  description: 6-digit TOTP
      responses:
        '200':
          description: TOTP valid, session established
        '403':
          description: Invalid TOTP
  /health:
    get:
      summary: Lightweight health probe used by heartbeat
      operationId: health
      responses:
        '200':
          description: Service healthy
        '503':
          description: Service unavailable
  /contacts:
    get:
      summary: Fetch contacts with delta sync support
      operationId: listContacts
      parameters:
        - in: query
          name: updatedSince
          schema:
            type: string
            format: date-time
        - in: query
          name: limit
          schema:
            type: integer
            default: 500
      responses:
        '200':
          description: Contacts retrieved
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Contact'
    put:
      summary: Bulk upsert contacts (used for replaying changes)
      operationId: upsertContacts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/ContactUpdate'
      responses:
        '202':
          description: Changes accepted for processing
  /contacts/{id}:
    get:
      summary: Retrieve contact detail
      operationId: getContact
      parameters:
        - $ref: '#/components/parameters/EntityId'
      responses:
        '200':
          description: Contact detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
    patch:
      summary: Apply field-level updates
      operationId: patchContact
      parameters:
        - $ref: '#/components/parameters/EntityId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FieldPatch'
      responses:
        '200':
          description: Contact updated
        '409':
          description: Conflict detected, server returns latest state
  /leads:
    get:
      summary: Fetch leads
      operationId: listLeads
      parameters:
        - in: query
          name: updatedSince
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Leads retrieved
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Lead'
  /leads/{id}:
    get:
      summary: Retrieve lead detail
      operationId: getLead
      parameters:
        - $ref: '#/components/parameters/EntityId'
      responses:
        '200':
          description: Lead detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lead'
    patch:
      summary: Update lead fields
      operationId: patchLead
      parameters:
        - $ref: '#/components/parameters/EntityId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FieldPatch'
      responses:
        '200':
          description: Lead updated
  /organizations:
    get:
      summary: Fetch organizations
      operationId: listOrganizations
      parameters:
        - in: query
          name: updatedSince
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Organizations retrieved
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Organization'
  /organizations/{id}:
    get:
      summary: Retrieve organization detail
      operationId: getOrganization
      parameters:
        - $ref: '#/components/parameters/EntityId'
      responses:
        '200':
          description: Organization detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Organization'
    patch:
      summary: Update organization fields
      operationId: patchOrganization
      parameters:
        - $ref: '#/components/parameters/EntityId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FieldPatch'
      responses:
        '200':
          description: Organization updated
  /reports:
    get:
      summary: Fetch reports summary
      operationId: listReports
      parameters:
        - in: query
          name: updatedSince
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Reports retrieved
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Report'
  /reports/{id}:
    get:
      summary: Retrieve report detail
      operationId: getReport
      parameters:
        - $ref: '#/components/parameters/EntityId'
      responses:
        '200':
          description: Report detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Report'
  /push/subscriptions:
    post:
      summary: Register or update a push subscription
      operationId: registerSubscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PushSubscription'
      responses:
        '201':
          description: Subscription stored
    delete:
      summary: Revoke a push subscription
      operationId: deleteSubscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [endpoint]
              properties:
                endpoint:
                  type: string
      responses:
        '204':
          description: Subscription revoked

components:
  parameters:
    EntityId:
      name: id
      in: path
      required: true
      schema:
        type: string
  schemas:
    UserProfile:
      type: object
      required: [id, username, displayName, locale]
      properties:
        id:
          type: string
        username:
          type: string
        displayName:
          type: string
        initials:
          type: string
        avatarUrl:
          type: string
          nullable: true
        locale:
          type: string
          enum: [en, de, fr, it, es]
        permissions:
          type: array
          items:
            type: string
    Contact:
      type: object
      required: [id, fullName, updatedAt]
      properties:
        id:
          type: string
        fullName:
          type: string
        email:
          type: string
          format: email
          nullable: true
        phone:
          type: string
          nullable: true
        companyId:
          type: string
          nullable: true
        status:
          type: string
        updatedAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
    ContactUpdate:
      type: object
      required: [id, fields]
      properties:
        id:
          type: string
        fields:
          type: object
          additionalProperties: true
    FieldPatch:
      type: object
      required: [operations]
      properties:
        operations:
          type: array
          items:
            type: object
            required: [op, path, value]
            properties:
              op:
                type: string
                enum: [replace]
              path:
                type: string
              value:
                nullable: true
    Lead:
      type: object
      required: [id, name, stage, updatedAt]
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        stage:
          type: string
        value:
          type: number
          nullable: true
        currency:
          type: string
          nullable: true
        updatedAt:
          type: string
          format: date-time
    Organization:
      type: object
      required: [id, name, updatedAt]
      properties:
        id:
          type: string
        name:
          type: string
        industry:
          type: string
          nullable: true
        updatedAt:
          type: string
          format: date-time
    Report:
      type: object
      required: [id, title, generatedAt]
      properties:
        id:
          type: string
        title:
          type: string
        type:
          type: string
        generatedAt:
          type: string
          format: date-time
        ownerId:
          type: string
    PushSubscription:
      type: object
      required: [endpoint, keys]
      properties:
        endpoint:
          type: string
        keys:
          type: object
          required: [p256dh, auth]
          properties:
            p256dh:
              type: string
            auth:
              type: string
        topics:
          type: array
          items:
            type: string
