openapi: 3.0.3
info:
  title: Sloggr External API
  version: "1.1.0"
  description: |
    Machine-to-machine API for integrations and future read-only AI/agent access.

    Authentication uses Bearer API keys scoped to an account org. Keys with
    `scopes: null` (legacy) are treated as full access (`*`). Create an
    **AI / agent read-only** key in Settings → API Keys for analytics and list access
    without write capabilities.

    Dates are ISO-8601 (UTC naive storage; `YYYY-MM-DD` accepted for day bounds).
    Pagination: `page` (default 1), `per_page` (default 25, max 100).
    Errors: `{ "error": "<code>", "message": "...", "fields"?: {...} }`.
servers:
  - url: https://app.saleslogger.app/api/v1
    description: Production
  - url: /api/v1
    description: Relative (same host as the app)
security:
  - bearerAuth: []
tags:
  - name: Orgs
  - name: Users
  - name: Contacts
  - name: Deals
  - name: Support
  - name: Activity
  - name: Analytics
  - name: Webhooks
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key secret from Settings → API Keys
  parameters:
    Page:
      name: page
      in: query
      schema: { type: integer, minimum: 1, default: 1 }
    PerPage:
      name: per_page
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
    RangePreset:
      name: range
      in: query
      description: Report date preset (e.g. this_month, last_month, this_week, last_90_days)
      schema: { type: string, default: this_month }
    Compare:
      name: compare
      in: query
      description: When 1/true, include previous_period and change blocks
      schema: { type: string, enum: ["1", "true", "yes", "previous"] }
    FromDate:
      name: from
      in: query
      schema: { type: string, description: "YYYY-MM-DD or ISO datetime" }
    ToDate:
      name: to
      in: query
      schema: { type: string }
  schemas:
    Error:
      type: object
      properties:
        error: { type: string }
        message: { type: string }
        fields: { type: object, additionalProperties: { type: string } }
    Pagination:
      type: object
      properties:
        page: { type: integer }
        per_page: { type: integer }
        total: { type: integer }
        pages: { type: integer }
        has_next: { type: boolean }
        has_prev: { type: boolean }
    ChangeMetric:
      type: object
      properties:
        absolute: { type: number, nullable: true }
        percent: { type: number, nullable: true }
        note:
          type: string
          nullable: true
          description: zero_baseline when previous was 0; percent is null in that case unless both zero
paths:
  /orgs:
    get:
      tags: [Orgs]
      summary: List customer (target) organizations
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - name: q
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Paginated orgs
        "401":
          description: Unauthorized
        "403":
          description: Missing orgs:read scope
  /orgs/resolve:
    post:
      tags: [Orgs]
      summary: Resolve external org id to sloggr org (write)
      responses:
        "200": { description: Linked or mapped }
  /users:
    get:
      tags: [Users]
      summary: List account team members
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - name: active_only
          in: query
          schema: { type: string, default: "1" }
      responses:
        "200": { description: Paginated users (no passwords/secrets) }
  /contacts:
    get:
      tags: [Contacts]
      summary: List contacts across target orgs
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - name: org_id
          in: query
          schema: { type: integer }
        - name: email
          in: query
          schema: { type: string }
        - name: q
          in: query
          schema: { type: string }
      responses:
        "200": { description: Paginated contacts }
  /deals:
    get:
      tags: [Deals]
      summary: List sales deals
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - name: status
          in: query
          schema: { type: string }
        - name: org_id
          in: query
          schema: { type: integer }
        - name: owner_user_id
          in: query
          schema: { type: integer }
        - name: q
          in: query
          schema: { type: string }
        - name: created_from
          in: query
          schema: { type: string }
        - name: created_to
          in: query
          schema: { type: string }
        - name: updated_from
          in: query
          schema: { type: string }
        - name: updated_to
          in: query
          schema: { type: string }
        - name: sort
          in: query
          schema: { type: string, enum: [created_at, updated_at, id, title, status] }
        - name: direction
          in: query
          schema: { type: string, enum: [asc, desc], default: desc }
      responses:
        "200": { description: Paginated deals }
  /deals/search:
    get:
      tags: [Deals]
      summary: Lookup deals by stripe customer, email, or org
      responses:
        "200": { description: Matching deals }
  /deals/{deal_id}:
    get:
      tags: [Deals]
      summary: Get one sales deal
      parameters:
        - name: deal_id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: Deal }
        "404": { description: Not found in account scope }
  /deals/{deal_id}/activities:
    get:
      tags: [Deals]
      summary: List timeline activities for a deal
      parameters:
        - name: deal_id
          in: path
          required: true
          schema: { type: integer }
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200": { description: Paginated activities }
    post:
      tags: [Deals]
      summary: Create deal activity (write)
      parameters:
        - name: deal_id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "201": { description: Created }
        "403": { description: Missing deals:write }
  /support/tickets:
    get:
      tags: [Support]
      summary: List support tickets
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - name: status
          in: query
          schema: { type: string }
        - name: open_only
          in: query
          schema: { type: string }
        - name: org_id
          in: query
          schema: { type: integer }
        - name: owner_user_id
          in: query
          schema: { type: integer }
        - name: q
          in: query
          schema: { type: string }
        - name: created_from
          in: query
          schema: { type: string }
        - name: created_to
          in: query
          schema: { type: string }
      responses:
        "200": { description: Paginated tickets }
    post:
      tags: [Support]
      summary: Create support ticket (write)
      responses:
        "201": { description: Created }
  /support/tickets/{ticket_id}:
    get:
      tags: [Support]
      summary: Get one support ticket
      parameters:
        - name: ticket_id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: Ticket }
  /support/tickets/{ticket_id}/logs:
    get:
      tags: [Support]
      summary: List support logs for a ticket
      parameters:
        - name: ticket_id
          in: path
          required: true
          schema: { type: integer }
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200": { description: Paginated logs }
  /activity:
    get:
      tags: [Activity]
      summary: Unified customer-facing activity feed
      description: |
        Normalized view over deal timeline events and support logs.
        Activity ids look like `timeline:123` or `support_log:456`.
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
        - name: org_id
          in: query
          schema: { type: integer }
        - name: employee_user_id
          in: query
          schema: { type: integer }
        - name: deal_id
          in: query
          schema: { type: integer }
        - name: ticket_id
          in: query
          schema: { type: integer }
        - name: source
          in: query
          schema: { type: string, enum: [all, timeline, sales, support_log, support], default: all }
      responses:
        "200": { description: Merged activity list (capped fetch then paginated) }
  /analytics/sales:
    get:
      tags: [Analytics]
      summary: Sales results, funnel, lost reasons, open pipeline follow-ups
      parameters:
        - $ref: "#/components/parameters/RangePreset"
        - $ref: "#/components/parameters/Compare"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
        - name: owner_user_id
          in: query
          schema: { type: integer }
        - name: by
          in: query
          schema: { type: string, enum: [owner, closer] }
      responses:
        "200": { description: Aggregated sales metrics }
  /analytics/support:
    get:
      tags: [Analytics]
      summary: Support volume, open/aged, by employee and customer
      parameters:
        - $ref: "#/components/parameters/RangePreset"
        - $ref: "#/components/parameters/Compare"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
        - name: owner_user_id
          in: query
          schema: { type: integer }
      responses:
        "200": { description: Aggregated support metrics }
  /analytics/team:
    get:
      tags: [Analytics]
      summary: Per-team-member activity totals for the period
      parameters:
        - $ref: "#/components/parameters/RangePreset"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
      responses:
        "200": { description: Team breakdown }
  /analytics/customers:
    get:
      tags: [Analytics]
      summary: Customer health plus orgs with both open support and open deals
      parameters:
        - $ref: "#/components/parameters/RangePreset"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
        - name: risk
          in: query
          schema: { type: string }
        - name: commercial
          in: query
          schema: { type: string }
      responses:
        "200": { description: Customer analytics }
  /analytics/activity:
    get:
      tags: [Analytics]
      summary: Activity volume totals and timeline event type mix
      parameters:
        - $ref: "#/components/parameters/RangePreset"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
      responses:
        "200": { description: Activity aggregates }
  /analytics/trends:
    get:
      tags: [Analytics]
      summary: ISO-week series of deals and tickets created
      parameters:
        - $ref: "#/components/parameters/RangePreset"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
      responses:
        "200": { description: Trend series }
  /webhooks/endpoints:
    get:
      tags: [Webhooks]
      summary: List outbound webhook endpoints
      responses:
        "200": { description: Endpoints }
    post:
      tags: [Webhooks]
      summary: Create webhook endpoint (write)
      responses:
        "201": { description: Created }
  /webhooks/deliveries:
    get:
      tags: [Webhooks]
      summary: List recent webhook deliveries
      responses:
        "200": { description: Deliveries }
