openapi: 3.0.3
info:
  title: TrackSalez API
  version: 1.4.0
  description: |
    The TrackSalez API lets you programmatically manage leads, tasks, proposals,
    WhatsApp conversations, attendance, and analytics.

    ## Authentication

    Use a scoped API key in the `X-API-Key` header:

    ```
    curl https://tracksalez.com/api/leads \
      -H "X-API-Key: tsk_live_..."
    ```

    Generate keys under Settings → Security → API keys.

    ## Rate limits

    - Public endpoints: 100 req / minute / IP.
    - Authenticated endpoints: 600 req / minute / tenant.
    - Sensitive endpoints (login, checkout, 2FA verify): 10 req / minute / IP.

    Each response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`.

    ## Pagination

    Standard `?page=1&limit=50&fields=id,name,email&sort=createdAt:desc` query params apply to every collection endpoint. Responses are wrapped in:

    ```json
    { "rows": [...], "page": 1, "limit": 50, "total": 1234, "hasNext": true, "hasPrev": false }
    ```
  contact:
    name: TrackSalez developer support
    email: developers@tracksalez.com
  license:
    name: Proprietary

servers:
  - url: https://tracksalez.com/api
    description: Production
  - url: https://sandbox.tracksalez.com/api
    description: Sandbox (test data)

security:
  - ApiKeyAuth: []

tags:
  - name: leads
  - name: tasks
  - name: webhooks
  - name: subscriptions
  - name: analytics
  - name: changelog
  - name: health

paths:
  /health:
    get:
      tags: [health]
      security: []
      summary: Liveness probe
      responses:
        '200': { description: Healthy }

  /health/advanced:
    get:
      tags: [health]
      security: []
      summary: Subsystem probe
      responses:
        '200':
          description: Healthy or degraded
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Health' }
        '503':
          description: One or more subsystems are down

  /changelog:
    get:
      tags: [changelog]
      security: []
      summary: Public changelog (JSON)
      responses:
        '200':
          description: Entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  entries:
                    type: array
                    items: { $ref: '#/components/schemas/ChangelogEntry' }

  /leads:
    get:
      tags: [leads]
      summary: List leads
      operationId: listLeads
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Fields'
        - in: query
          name: status
          schema:
            type: string
            enum: [new, contacted, qualified, proposal, negotiation, won, lost]
      responses:
        '200':
          description: Page of leads
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LeadList' }
    post:
      tags: [leads]
      summary: Create a lead
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LeadInput' }
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Lead' }

  /leads/{id}:
    get:
      tags: [leads]
      summary: Fetch a single lead
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: Lead
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Lead' }
        '404': { description: Not found }

  /tasks:
    get:
      tags: [tasks]
      summary: List tasks
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Page of tasks
          content:
            application/json:
              schema:
                type: object
                properties:
                  rows: { type: array, items: { $ref: '#/components/schemas/Task' } }

  /settings/webhook-endpoints:
    get:
      tags: [webhooks]
      summary: List webhook endpoints
      responses:
        '200':
          description: Endpoints
    post:
      tags: [webhooks]
      summary: Create a webhook endpoint
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url: { type: string, format: uri }
                events:
                  type: array
                  items: { type: string, example: lead.won }
      responses:
        '201':
          description: Created (returns the secret exactly once)

  /analytics/forecast/revenue:
    get:
      tags: [analytics]
      summary: Forecast next-N weeks of revenue
      parameters:
        - in: query
          name: weeks
          schema: { type: integer, default: 8, minimum: 1, maximum: 26 }
      responses:
        '200':
          description: History + predicted points

  /subscriptions/plans:
    get:
      tags: [subscriptions]
      security: []
      summary: List public plans (no auth required)
      responses:
        '200':
          description: Plans

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      name: X-API-Key
      in: header

  parameters:
    Page:
      name: page
      in: query
      schema: { type: integer, minimum: 1, default: 1 }
    Limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
    Fields:
      name: fields
      in: query
      description: Comma-separated columns to project (sparse fields)
      schema: { type: string }

  schemas:
    Lead:
      type: object
      required: [id, name, status]
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        company: { type: string, nullable: true }
        email: { type: string, format: email, nullable: true }
        phone: { type: string, nullable: true }
        status: { type: string, enum: [new, contacted, qualified, proposal, negotiation, won, lost] }
        valueEstimate: { type: number, nullable: true }
        source: { type: string, nullable: true }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    LeadList:
      type: object
      properties:
        rows: { type: array, items: { $ref: '#/components/schemas/Lead' } }
        page: { type: integer }
        limit: { type: integer }
        total: { type: integer }
        hasNext: { type: boolean }
        hasPrev: { type: boolean }

    LeadInput:
      type: object
      required: [name]
      properties:
        name: { type: string }
        company: { type: string }
        email: { type: string, format: email }
        phone: { type: string }
        status: { type: string }
        valueEstimate: { type: number }

    Task:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        status: { type: string, enum: [pending, in_progress, completed, cancelled] }
        dueDate: { type: string, format: date-time, nullable: true }
        assignedTo: { type: string, format: uuid, nullable: true }

    Health:
      type: object
      properties:
        status: { type: string, enum: [healthy, degraded, unhealthy] }
        checks:
          type: object
          additionalProperties:
            type: object
            properties:
              status: { type: string }
              latencyMs: { type: integer }
        version: { type: string }
        uptimeSeconds: { type: integer }

    ChangelogEntry:
      type: object
      properties:
        version: { type: string }
        date: { type: string, format: date }
        category: { type: string, enum: [feature, improvement, fix, security] }
        title: { type: string }
        body: { type: string }
