---
openapi: 3.0.1
info:
  title: Toolboks API V1
  version: v1
  description: RESTful API for the Toolboks CRM platform. All endpoints require Bearer
    token authentication and operate within the authenticated tenant's scope.
  contact:
    email: hello@toolboks.app
paths:
  "/api/v1/activities":
    get:
      summary: List activities
      tags:
      - Activities
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
      - name: q[activity_type_eq]
        in: query
        required: false
        schema:
          type: string
      - name: q[linkable_type_eq]
        in: query
        required: false
        schema:
          type: string
      responses:
        '200':
          description: activities retrieved
    post:
      summary: Create an activity
      tags:
      - Activities
      parameters: []
      responses:
        '201':
          description: activity created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/activity_input"
  "/api/v1/activities/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve an activity
      tags:
      - Activities
      responses:
        '200':
          description: activity found
  "/api/v1/analytics/arr_waterfall":
    get:
      summary: ARR waterfall
      tags:
      - Analytics
      description: Returns ARR movements grouped by month and movement type (new,
        expansion, contraction, churn).
      parameters:
      - name: from
        in: query
        format: date
        required: false
        description: 'Start date (default: 12 months ago)'
        schema:
          type: string
      - name: to
        in: query
        format: date
        required: false
        description: 'End date (default: today)'
        schema:
          type: string
      responses:
        '200':
          description: ARR waterfall data
  "/api/v1/analytics/revenue_movements":
    get:
      summary: Revenue movements
      tags:
      - Analytics
      description: Returns the last 100 revenue movement records, ordered by date
        descending.
      parameters:
      - name: from
        in: query
        format: date
        required: false
        schema:
          type: string
      - name: to
        in: query
        format: date
        required: false
        schema:
          type: string
      responses:
        '200':
          description: revenue movements data
  "/api/v1/analytics/pipeline_summary":
    get:
      summary: Pipeline summary
      tags:
      - Analytics
      description: Returns open opportunities grouped by pipeline stage, with counts
        and total weighted value.
      responses:
        '200':
          description: pipeline summary data
  "/api/v1/contacts":
    get:
      summary: List contacts
      tags:
      - Contacts
      description: Returns a paginated list of contacts. Supports Ransack filtering
        via q[] params and sorting via q[s] (e.g. q[s]=name+asc). Pagination is indicated
        via the X-Total, X-Per-Page, X-Page, X-Next-Page, and X-Prev-Page response
        headers.
      parameters:
      - name: page
        in: query
        required: false
        description: Page number (1-based)
        schema:
          type: integer
      - name: per_page
        in: query
        required: false
        description: Items per page (default 25)
        schema:
          type: integer
      - name: q[s]
        in: query
        required: false
        description: Sort expression, e.g. 'name asc' or 'created_at desc'
        schema:
          type: string
      - name: q[name_cont]
        in: query
        required: false
        description: Filter by name (contains)
        schema:
          type: string
      - name: q[email_cont]
        in: query
        required: false
        description: Filter by email (contains)
        schema:
          type: string
      - name: q[email_eq]
        in: query
        required: false
        description: Filter by exact email
        schema:
          type: string
      - name: q[phone_cont]
        in: query
        required: false
        description: Filter by phone (contains)
        schema:
          type: string
      - name: q[title_cont]
        in: query
        required: false
        description: Filter by title (contains)
        schema:
          type: string
      - name: q[role_type_eq]
        in: query
        required: false
        description: Filter by role type (economic_buyer, champion, technical, procurement_officer,
          end_user, other)
        schema:
          type: string
      - name: q[is_primary_eq]
        in: query
        required: false
        description: Filter primary contacts when true
        schema:
          type: boolean
      - name: q[tags_include]
        in: query
        required: false
        description: Filter by tag (array contains)
        schema:
          type: string
      - name: q[created_at_gteq]
        in: query
        required: false
        description: Filter by creation date ≥ (ISO 8601)
        schema:
          type: string
      - name: q[created_at_lteq]
        in: query
        required: false
        description: Filter by creation date ≤ (ISO 8601)
        schema:
          type: string
      - name: q[customer_id_eq]
        in: query
        required: false
        description: Filter by customer id
        schema:
          type: string
      - name: q[customer_name_cont]
        in: query
        required: false
        description: Filter by customer name (contains)
        schema:
          type: string
      responses:
        '200':
          description: contacts retrieved
    post:
      summary: Create a contact
      tags:
      - Contacts
      parameters: []
      responses:
        '201':
          description: contact created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/contact_input"
  "/api/v1/contacts/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a contact
      tags:
      - Contacts
      responses:
        '200':
          description: contact found
    patch:
      summary: Update a contact
      tags:
      - Contacts
      parameters: []
      responses:
        '200':
          description: contact updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/contact_input"
    delete:
      summary: Delete a contact
      tags:
      - Contacts
      responses:
        '204':
          description: contact deleted
  "/api/v1/contracts":
    get:
      summary: List contracts
      tags:
      - Contracts
      description: Returns a paginated list of contracts. Supports Ransack filtering
        via q[] params and sorting via q[s] (e.g. q[s]=end_date+asc). Pagination is
        indicated via the X-Total, X-Per-Page, X-Page, X-Next-Page, and X-Prev-Page
        response headers.
      parameters:
      - name: page
        in: query
        required: false
        description: Page number (1-based)
        schema:
          type: integer
      - name: per_page
        in: query
        required: false
        description: Items per page (default 25)
        schema:
          type: integer
      - name: q[s]
        in: query
        required: false
        description: Sort expression, e.g. 'end_date asc' or 'arr_cents desc'
        schema:
          type: string
      - name: q[name_cont]
        in: query
        required: false
        description: Filter by name (contains)
        schema:
          type: string
      - name: q[status_eq]
        in: query
        required: false
        description: Filter by status (draft, active, renewed, expired, cancelled)
        schema:
          type: string
      - name: q[status_in][]
        in: query
        items:
          type: string
        required: false
        description: Filter by multiple statuses
        schema:
          type: array
      - name: q[currency_eq]
        in: query
        required: false
        description: Filter by currency code (ISO 4217)
        schema:
          type: string
      - name: q[billing_model_eq]
        in: query
        required: false
        description: Filter by billing model
        schema:
          type: string
      - name: q[tags_include]
        in: query
        required: false
        description: Filter by tag (array contains)
        schema:
          type: string
      - name: q[start_date_gteq]
        in: query
        required: false
        description: Filter by start date ≥ (YYYY-MM-DD)
        schema:
          type: string
      - name: q[start_date_lteq]
        in: query
        required: false
        description: Filter by start date ≤ (YYYY-MM-DD)
        schema:
          type: string
      - name: q[end_date_gteq]
        in: query
        required: false
        description: Filter by end date ≥ (YYYY-MM-DD)
        schema:
          type: string
      - name: q[end_date_lteq]
        in: query
        required: false
        description: Filter by end date ≤ (YYYY-MM-DD)
        schema:
          type: string
      - name: q[arr_cents_gteq]
        in: query
        required: false
        description: Filter by ARR ≥ (cents)
        schema:
          type: integer
      - name: q[arr_cents_lteq]
        in: query
        required: false
        description: Filter by ARR ≤ (cents)
        schema:
          type: integer
      - name: q[tcv_cents_gteq]
        in: query
        required: false
        description: Filter by TCV ≥ (cents)
        schema:
          type: integer
      - name: q[tcv_cents_lteq]
        in: query
        required: false
        description: Filter by TCV ≤ (cents)
        schema:
          type: integer
      - name: q[created_at_gteq]
        in: query
        required: false
        description: Filter by creation date ≥ (ISO 8601)
        schema:
          type: string
      - name: q[is_framework_eq]
        in: query
        required: false
        description: Filter framework agreements
        schema:
          type: boolean
      - name: q[has_renewals_eq]
        in: query
        required: false
        description: Filter contracts with renewals tracking
        schema:
          type: boolean
      - name: q[has_deliverables_eq]
        in: query
        required: false
        description: Filter contracts with deliverables tracking
        schema:
          type: boolean
      - name: q[has_time_tracking_eq]
        in: query
        required: false
        description: Filter contracts with time tracking
        schema:
          type: boolean
      - name: q[has_usage_billing_eq]
        in: query
        required: false
        description: Filter contracts with usage billing
        schema:
          type: boolean
      - name: q[customer_id_eq]
        in: query
        required: false
        description: Filter by customer id
        schema:
          type: string
      - name: q[owner_id_eq]
        in: query
        required: false
        description: Filter by owner (user) id
        schema:
          type: string
      - name: q[owner_id_null]
        in: query
        required: false
        description: Filter unowned contracts when true
        schema:
          type: boolean
      responses:
        '200':
          description: contracts retrieved
    post:
      summary: Create a contract
      tags:
      - Contracts
      parameters: []
      responses:
        '201':
          description: contract created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/contract_input"
  "/api/v1/contracts/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a contract
      tags:
      - Contracts
      responses:
        '200':
          description: contract found
    patch:
      summary: Update a contract
      tags:
      - Contracts
      parameters: []
      responses:
        '200':
          description: contract updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/contract_input"
  "/api/v1/contracts/{id}/activate":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Activate a contract
      tags:
      - Contracts
      description: Transitions a draft contract to active status, creating line items
        and revenue records.
      responses:
        '200':
          description: contract activated
  "/api/v1/contracts/{id}/cancel":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Cancel a contract
      tags:
      - Contracts
      description: Cancels an active contract, setting the cancellation date.
      responses:
        '200':
          description: contract cancelled
  "/api/v1/customers":
    get:
      summary: List customers
      tags:
      - Customers
      description: Returns a paginated list of customers. Supports Ransack filtering
        via q[] params and sorting via q[s] (e.g. q[s]=name+asc). Pagination is indicated
        via the X-Total, X-Per-Page, X-Page, X-Next-Page, and X-Prev-Page response
        headers.
      parameters:
      - name: page
        in: query
        required: false
        description: Page number (1-based)
        schema:
          type: integer
      - name: per_page
        in: query
        required: false
        description: Items per page (default 25)
        schema:
          type: integer
      - name: q[s]
        in: query
        required: false
        description: Sort expression, e.g. 'name asc' or 'arr_total_cents desc'
        schema:
          type: string
      - name: q[name_cont]
        in: query
        required: false
        description: Filter by name (contains)
        schema:
          type: string
      - name: q[name_eq]
        in: query
        required: false
        description: Filter by name (exact match)
        schema:
          type: string
      - name: q[org_number_eq]
        in: query
        required: false
        description: Filter by organization number
        schema:
          type: string
      - name: q[industry_eq]
        in: query
        required: false
        description: Filter by industry
        schema:
          type: string
      - name: q[industry_cont]
        in: query
        required: false
        description: Filter by industry (contains)
        schema:
          type: string
      - name: q[segments_include]
        in: query
        required: false
        description: Filter by segment (array contains)
        schema:
          type: string
      - name: q[tags_include]
        in: query
        required: false
        description: Filter by tag (array contains)
        schema:
          type: string
      - name: q[arr_total_cents_gteq]
        in: query
        required: false
        description: Filter by ARR ≥ value (cents)
        schema:
          type: integer
      - name: q[arr_total_cents_lteq]
        in: query
        required: false
        description: Filter by ARR ≤ value (cents)
        schema:
          type: integer
      - name: q[created_at_gteq]
        in: query
        required: false
        description: Filter by creation date ≥ (ISO 8601)
        schema:
          type: string
      - name: q[created_at_lteq]
        in: query
        required: false
        description: Filter by creation date ≤ (ISO 8601)
        schema:
          type: string
      - name: q[parent_customer_id_eq]
        in: query
        required: false
        description: Filter by parent customer id
        schema:
          type: string
      - name: q[parent_customer_id_null]
        in: query
        required: false
        description: Filter for top-level customers (no parent) when true
        schema:
          type: boolean
      - name: q[contacts_email_cont]
        in: query
        required: false
        description: Filter by any contact's email (contains)
        schema:
          type: string
      - name: q[contacts_name_cont]
        in: query
        required: false
        description: Filter by any contact's name (contains)
        schema:
          type: string
      responses:
        '200':
          description: customers retrieved
        '401':
          description: unauthorized
    post:
      summary: Create a customer
      tags:
      - Customers
      parameters: []
      responses:
        '201':
          description: customer created
        '422':
          description: validation error
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/customer_input"
  "/api/v1/customers/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a customer
      tags:
      - Customers
      responses:
        '200':
          description: customer found
        '404':
          description: customer not found
    patch:
      summary: Update a customer
      tags:
      - Customers
      parameters: []
      responses:
        '200':
          description: customer updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/customer_input"
    delete:
      summary: Delete a customer
      tags:
      - Customers
      responses:
        '204':
          description: customer soft-deleted
        '422':
          description: customer has blockers that prevent deletion
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  reasons:
                    type: array
                    items:
                      type: string
                required:
                - error
                - reasons
  "/api/v1/document_templates":
    get:
      summary: List document templates
      tags:
      - Document Templates
      description: Returns all active document templates, ordered by name.
      responses:
        '200':
          description: templates retrieved
  "/api/v1/document_templates/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a document template
      tags:
      - Document Templates
      responses:
        '200':
          description: template found
  "/api/v1/invoice_drafts":
    get:
      summary: List invoice drafts
      tags:
      - Invoice Drafts
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: invoice drafts retrieved
  "/api/v1/invoice_drafts/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve an invoice draft
      tags:
      - Invoice Drafts
      responses:
        '200':
          description: invoice draft found
  "/api/v1/invoice_drafts/{id}/approve":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Approve an invoice draft
      tags:
      - Invoice Drafts
      description: Approves a draft invoice, marking it ready for export.
      responses:
        '200':
          description: invoice draft approved
  "/api/v1/invoice_drafts/{id}/export":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Export an invoice draft
      tags:
      - Invoice Drafts
      description: Exports an approved invoice to the connected accounting system
        (e.g. Tripletex).
      responses:
        '200':
          description: invoice draft exported
  "/api/v1/leads":
    get:
      summary: List leads
      tags:
      - Leads
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
      - name: per_page
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: leads retrieved
  "/api/v1/leads/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a lead
      tags:
      - Leads
      responses:
        '200':
          description: lead found
  "/api/v1/leads/{id}/assign":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Assign a lead to a user
      tags:
      - Leads
      parameters: []
      responses:
        '200':
          description: lead assigned
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                user_id:
                  type: string
                  format: uuid
              required:
              - user_id
  "/api/v1/leads/{id}/forward":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Forward a lead
      tags:
      - Leads
      parameters: []
      responses:
        '200':
          description: lead forwarded
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                target_id:
                  type: string
                  format: uuid
                  nullable: true
                email:
                  type: string
                name:
                  type: string
                comment:
                  type: string
  "/api/v1/leads/{id}/dismiss":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Dismiss a lead
      tags:
      - Leads
      parameters: []
      responses:
        '200':
          description: lead dismissed
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
  "/api/v1/leads/{id}/convert":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Convert a lead to an opportunity
      tags:
      - Leads
      parameters: []
      responses:
        '200':
          description: lead converted
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_name:
                  type: string
                pipeline_id:
                  type: string
                  format: uuid
                pipeline_stage_id:
                  type: string
                  format: uuid
              required:
              - customer_name
              - pipeline_id
              - pipeline_stage_id
  "/api/v1/opportunities":
    get:
      summary: List opportunities
      tags:
      - Opportunities
      description: Returns a paginated list of opportunities. Supports Ransack filtering
        via q[] params and sorting via q[s] (e.g. q[s]=expected_close_date+asc). Pagination
        is indicated via the X-Total, X-Per-Page, X-Page, X-Next-Page, and X-Prev-Page
        response headers. To list only open deals for a customer, combine q[customer_id_eq]
        with q[status_eq]=open; for a stricter open filter that also excludes won/lost
        stages, add q[pipeline_stage_stage_type_eq]=open.
      parameters:
      - name: page
        in: query
        required: false
        description: Page number (1-based)
        schema:
          type: integer
      - name: per_page
        in: query
        required: false
        description: Items per page (default 25)
        schema:
          type: integer
      - name: q[s]
        in: query
        required: false
        description: Sort expression, e.g. 'expected_close_date asc' or 'expected_value_cents
          desc'
        schema:
          type: string
      - name: q[name_cont]
        in: query
        required: false
        description: Filter by name (contains)
        schema:
          type: string
      - name: q[status_eq]
        in: query
        required: false
        description: Filter by status (draft, open, won, lost)
        schema:
          type: string
      - name: q[status_in][]
        in: query
        items:
          type: string
        required: false
        description: Filter by multiple statuses
        schema:
          type: array
      - name: q[deal_type_eq]
        in: query
        required: false
        description: Filter by deal type (new_business, expansion, renewal, one_off,
          mixed)
        schema:
          type: string
      - name: q[tags_include]
        in: query
        required: false
        description: Filter by tag (array contains)
        schema:
          type: string
      - name: q[expected_close_date_gteq]
        in: query
        required: false
        description: Filter by expected close date ≥ (YYYY-MM-DD)
        schema:
          type: string
      - name: q[expected_close_date_lteq]
        in: query
        required: false
        description: Filter by expected close date ≤ (YYYY-MM-DD)
        schema:
          type: string
      - name: q[created_at_gteq]
        in: query
        required: false
        description: Filter by creation date ≥ (ISO 8601)
        schema:
          type: string
      - name: q[closed_at_gteq]
        in: query
        required: false
        description: Filter by closed-at ≥ (ISO 8601)
        schema:
          type: string
      - name: q[closed_at_lteq]
        in: query
        required: false
        description: Filter by closed-at ≤ (ISO 8601)
        schema:
          type: string
      - name: q[probability_gteq]
        in: query
        required: false
        description: Filter by probability ≥ (0–100)
        schema:
          type: number
      - name: q[probability_lteq]
        in: query
        required: false
        description: Filter by probability ≤ (0–100)
        schema:
          type: number
      - name: q[expected_value_cents_gteq]
        in: query
        required: false
        description: Filter by expected value ≥ (cents)
        schema:
          type: integer
      - name: q[expected_value_cents_lteq]
        in: query
        required: false
        description: Filter by expected value ≤ (cents)
        schema:
          type: integer
      - name: q[currency_eq]
        in: query
        required: false
        description: Filter by currency code (ISO 4217)
        schema:
          type: string
      - name: q[customer_id_eq]
        in: query
        required: false
        description: Filter by customer id
        schema:
          type: string
      - name: q[owner_id_eq]
        in: query
        required: false
        description: Filter by owner (user) id
        schema:
          type: string
      - name: q[owner_id_null]
        in: query
        required: false
        description: Filter unowned opportunities when true
        schema:
          type: boolean
      - name: q[pipeline_id_eq]
        in: query
        required: false
        description: Filter by pipeline id
        schema:
          type: string
      - name: q[pipeline_stage_id_eq]
        in: query
        required: false
        description: Filter by pipeline stage id
        schema:
          type: string
      - name: q[pipeline_stage_id_in][]
        in: query
        items:
          type: string
        required: false
        description: Filter by multiple pipeline stage ids
        schema:
          type: array
      - name: q[pipeline_stage_stage_type_eq]
        in: query
        required: false
        description: Filter by pipeline stage type (open, won, lost)
        schema:
          type: string
      - name: q[contacts_id_eq]
        in: query
        required: false
        description: Filter by linked contact id
        schema:
          type: string
      responses:
        '200':
          description: opportunities retrieved
    post:
      summary: Create an opportunity
      tags:
      - Opportunities
      parameters: []
      responses:
        '201':
          description: opportunity created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/opportunity_input"
  "/api/v1/opportunities/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve an opportunity
      tags:
      - Opportunities
      responses:
        '200':
          description: opportunity found
    patch:
      summary: Update an opportunity
      tags:
      - Opportunities
      parameters: []
      responses:
        '200':
          description: opportunity updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/opportunity_input"
  "/api/v1/orders":
    get:
      summary: List orders
      tags:
      - Orders
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
      - name: q[status_eq]
        in: query
        required: false
        schema:
          type: string
      responses:
        '200':
          description: orders retrieved
    post:
      summary: Create an order
      tags:
      - Orders
      parameters: []
      responses:
        '201':
          description: order created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/order_input"
  "/api/v1/orders/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve an order
      tags:
      - Orders
      responses:
        '200':
          description: order found
    patch:
      summary: Update an order
      tags:
      - Orders
      parameters: []
      responses:
        '200':
          description: order updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/order_input"
  "/api/v1/orders/{id}/confirm":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Confirm an order
      tags:
      - Orders
      description: Transitions a draft order to confirmed status.
      responses:
        '200':
          description: order confirmed
  "/api/v1/orders/{id}/fulfill":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Fulfill an order
      tags:
      - Orders
      description: Marks a confirmed order as fulfilled with a fulfillment date.
      responses:
        '200':
          description: order fulfilled
  "/api/v1/pipelines":
    get:
      summary: List pipelines
      tags:
      - Pipelines
      responses:
        '200':
          description: pipelines retrieved
    post:
      summary: Create a pipeline
      tags:
      - Pipelines
      parameters: []
      responses:
        '201':
          description: pipeline created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/pipeline_input"
  "/api/v1/pipelines/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a pipeline
      tags:
      - Pipelines
      responses:
        '200':
          description: pipeline found
    patch:
      summary: Update a pipeline
      tags:
      - Pipelines
      parameters: []
      responses:
        '200':
          description: pipeline updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/pipeline_input"
    delete:
      summary: Delete a pipeline
      tags:
      - Pipelines
      responses:
        '204':
          description: pipeline deleted
  "/api/v1/pipelines/{pipeline_id}/stages":
    parameters:
    - name: pipeline_id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: List pipeline stages
      tags:
      - Pipeline Stages
      responses:
        '200':
          description: stages retrieved
    post:
      summary: Create a pipeline stage
      tags:
      - Pipeline Stages
      parameters: []
      responses:
        '201':
          description: stage created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/pipeline_stage_input"
  "/api/v1/products":
    get:
      summary: List products
      tags:
      - Products
      description: Returns a paginated list of products. Supports Ransack filtering
        via q[] params and sorting via q[s] (e.g. q[s]=name+asc). Pagination is indicated
        via the X-Total, X-Per-Page, X-Page, X-Next-Page, and X-Prev-Page response
        headers.
      parameters:
      - name: page
        in: query
        required: false
        description: Page number (1-based)
        schema:
          type: integer
      - name: per_page
        in: query
        required: false
        description: Items per page (default 25)
        schema:
          type: integer
      - name: q[s]
        in: query
        required: false
        description: Sort expression, e.g. 'name asc' or 'created_at desc'
        schema:
          type: string
      - name: q[name_cont]
        in: query
        required: false
        description: Filter by name (contains)
        schema:
          type: string
      - name: q[name_eq]
        in: query
        required: false
        description: Filter by exact name
        schema:
          type: string
      - name: q[sku_cont]
        in: query
        required: false
        description: Filter by SKU (contains)
        schema:
          type: string
      - name: q[sku_eq]
        in: query
        required: false
        description: Filter by exact SKU
        schema:
          type: string
      - name: q[billing_type_eq]
        in: query
        required: false
        description: Filter by billing type (recurring, one_time, usage, hourly)
        schema:
          type: string
      - name: q[billing_type_in][]
        in: query
        items:
          type: string
        required: false
        description: Filter by multiple billing types
        schema:
          type: array
      - name: q[active_eq]
        in: query
        required: false
        description: Filter by active status
        schema:
          type: boolean
      - name: q[created_at_gteq]
        in: query
        required: false
        description: Filter by creation date ≥ (ISO 8601)
        schema:
          type: string
      - name: q[created_at_lteq]
        in: query
        required: false
        description: Filter by creation date ≤ (ISO 8601)
        schema:
          type: string
      - name: q[product_category_id_eq]
        in: query
        required: false
        description: Filter by product category id
        schema:
          type: string
      - name: q[product_category_id_null]
        in: query
        required: false
        description: Filter uncategorized products when true
        schema:
          type: boolean
      - name: q[product_category_name_cont]
        in: query
        required: false
        description: Filter by product category name (contains)
        schema:
          type: string
      responses:
        '200':
          description: products retrieved
    post:
      summary: Create a product
      tags:
      - Products
      parameters: []
      responses:
        '201':
          description: product created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/product_input"
  "/api/v1/products/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a product
      tags:
      - Products
      responses:
        '200':
          description: product found
    patch:
      summary: Update a product
      tags:
      - Products
      parameters: []
      responses:
        '200':
          description: product updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/product_input"
    delete:
      summary: Deactivate a product
      tags:
      - Products
      responses:
        '200':
          description: product deactivated
  "/api/v1/proposals":
    get:
      summary: List proposals
      tags:
      - Proposals
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: proposals retrieved
    post:
      summary: Create a proposal
      tags:
      - Proposals
      description: Creates a proposal by rendering a document template for the given
        customer. The system generates a Document from the template automatically.
      parameters: []
      responses:
        '201':
          description: proposal created
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                document_template_id:
                  type: string
                  format: uuid
                  description: Template to render
                customer_id:
                  type: string
                  format: uuid
                opportunity_id:
                  type: string
                  format: uuid
                  description: Optional opportunity to link
                pricing_data:
                  type: object
                  description: Pricing variables for template merge
              required:
              - document_template_id
              - customer_id
  "/api/v1/proposals/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a proposal
      tags:
      - Proposals
      responses:
        '200':
          description: proposal found
    patch:
      summary: Update a proposal
      tags:
      - Proposals
      parameters: []
      responses:
        '200':
          description: proposal updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/proposal_input"
  "/api/v1/proposals/{id}/send_proposal":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Send a proposal
      tags:
      - Proposals
      description: Marks the proposal as sent and records the sent_at timestamp.
      responses:
        '200':
          description: proposal sent
  "/api/v1/proposals/{id}/accept":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Accept a proposal
      tags:
      - Proposals
      description: Accepts a sent proposal, optionally creating a contract or order.
      parameters: []
      responses:
        '200':
          description: proposal accepted
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                create_type:
                  type: string
                  enum:
                  - contract
                  - order
                  description: 'What to create from the accepted proposal (default:
                    contract)'
  "/api/v1/renewals":
    get:
      summary: List renewals
      tags:
      - Renewals
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
      - name: q[status_eq]
        in: query
        required: false
        schema:
          type: string
      responses:
        '200':
          description: renewals retrieved
  "/api/v1/renewals/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a renewal
      tags:
      - Renewals
      responses:
        '200':
          description: renewal found
    patch:
      summary: Update a renewal
      tags:
      - Renewals
      parameters: []
      responses:
        '200':
          description: renewal updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/renewal_input"
  "/api/v1/time_entries":
    get:
      summary: List time entries
      tags:
      - Time Entries
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
      - name: q[contract_id_eq]
        in: query
        required: false
        schema:
          type: string
      - name: q[status_eq]
        in: query
        required: false
        schema:
          type: string
      responses:
        '200':
          description: time entries retrieved
    post:
      summary: Create a time entry
      tags:
      - Time Entries
      parameters: []
      responses:
        '201':
          description: time entry created
        '422':
          description: email missing or user not in tenant
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/time_entry_input"
  "/api/v1/time_entries/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a time entry
      tags:
      - Time Entries
      responses:
        '200':
          description: time entry found
    patch:
      summary: Update a time entry
      tags:
      - Time Entries
      parameters: []
      responses:
        '200':
          description: time entry updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/time_entry_input"
  "/api/v1/time_entries/{id}/approve":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Approve a time entry
      tags:
      - Time Entries
      description: Approves a submitted time entry.
      responses:
        '200':
          description: time entry approved
  "/api/v1/webhook_endpoints":
    get:
      summary: List webhook endpoints
      tags:
      - Webhooks
      responses:
        '200':
          description: webhook endpoints retrieved
    post:
      summary: Create a webhook endpoint
      tags:
      - Webhooks
      parameters: []
      responses:
        '201':
          description: webhook endpoint created
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/webhook_endpoint_input"
  "/api/v1/webhook_endpoints/{id}":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a webhook endpoint
      tags:
      - Webhooks
      responses:
        '200':
          description: webhook endpoint found
    patch:
      summary: Update a webhook endpoint
      tags:
      - Webhooks
      parameters: []
      responses:
        '200':
          description: webhook endpoint updated
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/webhook_endpoint_input"
    delete:
      summary: Delete a webhook endpoint
      tags:
      - Webhooks
      responses:
        '204':
          description: webhook endpoint soft-deleted
  "/api/v1/webhook_endpoints/{id}/test":
    parameters:
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    post:
      summary: Send a test webhook
      tags:
      - Webhooks
      description: Creates a test delivery and queues it for immediate dispatch.
      responses:
        '202':
          description: test delivery queued
  "/api/v1/webhook_endpoints/{webhook_endpoint_id}/deliveries":
    parameters:
    - name: webhook_endpoint_id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: List webhook deliveries
      tags:
      - Webhooks
      parameters:
      - name: page
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: deliveries retrieved
  "/api/v1/webhook_endpoints/{webhook_endpoint_id}/deliveries/{id}":
    parameters:
    - name: webhook_endpoint_id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    - name: id
      in: path
      format: uuid
      required: true
      schema:
        type: string
    get:
      summary: Retrieve a webhook delivery
      tags:
      - Webhooks
      responses:
        '200':
          description: delivery found
servers:
- url: https://{subdomain}.toolboks.app
  variables:
    subdomain:
      default: demo
components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: API token obtained from Settings → API Tokens
  schemas:
    customer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        org_number:
          type: string
          nullable: true
        segments:
          type: array
          items:
            type: string
        industry:
          type: string
          nullable: true
        website:
          type: string
          nullable: true
        currency:
          type: string
          default: NOK
        arr_total_cents:
          type: integer
        total_revenue_cents:
          type: integer
        tags:
          type: array
          items:
            type: string
        address:
          type: object
          nullable: true
          properties:
            street:
              type: string
            postal_code:
              type: string
            city:
              type: string
            country:
              type: string
        parent_customer_id:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
    customer_input:
      type: object
      properties:
        customer:
          type: object
          properties:
            name:
              type: string
            org_number:
              type: string
            segments:
              type: array
              items:
                type: string
            industry:
              type: string
            website:
              type: string
            currency:
              type: string
            parent_customer_id:
              type: string
              format: uuid
            tags:
              type: array
              items:
                type: string
            address:
              type: object
              properties:
                street:
                  type: string
                postal_code:
                  type: string
                city:
                  type: string
                country:
                  type: string
          required:
          - name
    contact:
      type: object
      properties:
        id:
          type: string
          format: uuid
        customer_id:
          type: string
          format: uuid
        name:
          type: string
        email:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        title:
          type: string
          nullable: true
        role_type:
          type: string
          enum:
          - economic_buyer
          - champion
          - technical
          - procurement_officer
          - end_user
          - other
          nullable: true
        is_primary:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - customer_id
    contact_input:
      type: object
      properties:
        contact:
          type: object
          properties:
            name:
              type: string
            email:
              type: string
            phone:
              type: string
            title:
              type: string
            role_type:
              type: string
              enum:
              - economic_buyer
              - champion
              - technical
              - procurement_officer
              - end_user
              - other
            is_primary:
              type: boolean
            customer_id:
              type: string
              format: uuid
          required:
          - name
          - customer_id
    opportunity:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        customer_id:
          type: string
          format: uuid
        owner_id:
          type: string
          format: uuid
        pipeline_id:
          type: string
          format: uuid
        pipeline_stage_id:
          type: string
          format: uuid
        expected_value_cents:
          type: integer
        weighted_value_cents:
          type: integer
        probability:
          type: number
        currency:
          type: string
        expected_close_date:
          type: string
          format: date
          nullable: true
        deal_type:
          type: string
          enum:
          - new_business
          - expansion
          - renewal
          - one_off
          - mixed
          nullable: true
        procurement_path:
          type: string
          enum:
          - direct
          - framework_calloff
          - tender
          nullable: true
        close_reason:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - customer_id
      - pipeline_id
      - pipeline_stage_id
    opportunity_input:
      type: object
      properties:
        opportunity:
          type: object
          properties:
            name:
              type: string
            customer_id:
              type: string
              format: uuid
            pipeline_id:
              type: string
              format: uuid
            pipeline_stage_id:
              type: string
              format: uuid
            expected_value_cents:
              type: integer
            probability:
              type: number
            expected_close_date:
              type: string
              format: date
            deal_type:
              type: string
              enum:
              - new_business
              - expansion
              - renewal
              - one_off
              - mixed
            procurement_path:
              type: string
              enum:
              - direct
              - framework_calloff
              - tender
            currency:
              type: string
            close_reason:
              type: string
          required:
          - name
          - customer_id
          - pipeline_id
          - pipeline_stage_id
    pipeline:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        is_default:
          type: boolean
        position:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
    pipeline_input:
      type: object
      properties:
        pipeline:
          type: object
          properties:
            name:
              type: string
            is_default:
              type: boolean
            position:
              type: integer
          required:
          - name
    pipeline_stage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        pipeline_id:
          type: string
          format: uuid
        name:
          type: string
        position:
          type: integer
        default_probability:
          type: number
          nullable: true
        stage_type:
          type: string
          enum:
          - open
          - won
          - lost
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - pipeline_id
      - position
    pipeline_stage_input:
      type: object
      properties:
        stage:
          type: object
          properties:
            name:
              type: string
            stage_type:
              type: string
              enum:
              - open
              - won
              - lost
            position:
              type: integer
            default_probability:
              type: number
          required:
          - name
          - position
    contract:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        customer_id:
          type: string
          format: uuid
        owner_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - draft
          - active
          - renewed
          - expired
          - cancelled
        arr_cents:
          type: integer
        tcv_cents:
          type: integer
        currency:
          type: string
        start_date:
          type: string
          format: date
          nullable: true
        end_date:
          type: string
          format: date
          nullable: true
        auto_renew:
          type: boolean
        notice_period_days:
          type: integer
          nullable: true
        cancellation_date:
          type: string
          format: date
          nullable: true
        notes:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - customer_id
    contract_input:
      type: object
      properties:
        contract:
          type: object
          properties:
            name:
              type: string
            customer_id:
              type: string
              format: uuid
            status:
              type: string
              enum:
              - draft
              - active
              - renewed
              - expired
              - cancelled
            currency:
              type: string
            start_date:
              type: string
              format: date
            end_date:
              type: string
              format: date
            auto_renew:
              type: boolean
            notice_period_days:
              type: integer
            notes:
              type: string
          required:
          - name
          - customer_id
    order:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        customer_id:
          type: string
          format: uuid
        owner_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - draft
          - confirmed
          - processing
          - fulfilled
          - cancelled
        total_cents:
          type: integer
        currency:
          type: string
        order_date:
          type: string
          format: date
          nullable: true
        fulfillment_date:
          type: string
          format: date
          nullable: true
        notes:
          type: string
          nullable: true
        shipping_address:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - customer_id
    order_input:
      type: object
      properties:
        order:
          type: object
          properties:
            name:
              type: string
            customer_id:
              type: string
              format: uuid
            status:
              type: string
              enum:
              - draft
              - confirmed
              - processing
              - fulfilled
              - cancelled
            currency:
              type: string
            order_date:
              type: string
              format: date
            notes:
              type: string
            shipping_address:
              type: string
          required:
          - name
          - customer_id
    proposal:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        customer_id:
          type: string
          format: uuid
        opportunity_id:
          type: string
          format: uuid
          nullable: true
        contact_id:
          type: string
          format: uuid
          nullable: true
        owner_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - draft
          - sent
          - accepted
          - rejected
          - archived
          - superseded
        notes:
          type: string
          nullable: true
        valid_until:
          type: string
          format: date
          nullable: true
        sent_at:
          type: string
          format: date-time
          nullable: true
        accepted_at:
          type: string
          format: date-time
          nullable: true
        parent_proposal_id:
          type: string
          format: uuid
          nullable: true
        revision_number:
          type: integer
        reference_number:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - customer_id
    proposal_input:
      type: object
      properties:
        proposal:
          type: object
          properties:
            document_template_id:
              type: string
              format: uuid
            customer_id:
              type: string
              format: uuid
            opportunity_id:
              type: string
              format: uuid
            contact_id:
              type: string
              format: uuid
            name:
              type: string
            notes:
              type: string
            valid_until:
              type: string
              format: date
          required:
          - customer_id
          - document_template_id
    renewal:
      type: object
      properties:
        id:
          type: string
          format: uuid
        contract_id:
          type: string
          format: uuid
        customer_id:
          type: string
          format: uuid
        owner_id:
          type: string
          format: uuid
          nullable: true
        status:
          type: string
          enum:
          - upcoming
          - at_risk
          - engaged
          - committed_deal
          - renewed
          - churned
        due_date:
          type: string
          format: date
        arr_at_stake_cents:
          type: integer
        currency:
          type: string
        notes:
          type: string
          nullable: true
        health_score:
          type: number
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - contract_id
      - customer_id
      - due_date
    renewal_input:
      type: object
      properties:
        renewal:
          type: object
          properties:
            status:
              type: string
              enum:
              - upcoming
              - at_risk
              - engaged
              - committed_deal
              - renewed
              - churned
            notes:
              type: string
            health_score:
              type: number
    invoice_draft:
      type: object
      properties:
        id:
          type: string
          format: uuid
        customer_id:
          type: string
          format: uuid
        contract_id:
          type: string
          format: uuid
          nullable: true
        order_id:
          type: string
          format: uuid
          nullable: true
        invoice_number:
          type: string
          nullable: true
        subtotal_cents:
          type: integer
        tax_cents:
          type: integer
        total_cents:
          type: integer
        currency:
          type: string
        issue_date:
          type: string
          format: date
          nullable: true
        due_date:
          type: string
          format: date
          nullable: true
        status:
          type: string
          enum:
          - draft
          - pending_review
          - approved
          - exported
          - paid
          - overdue
          - credited
          - cancelled
        notes:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - customer_id
    time_entry:
      type: object
      properties:
        id:
          type: string
          format: uuid
        contract_id:
          type: string
          format: uuid
        deliverable_id:
          type: string
          format: uuid
          nullable: true
        user_id:
          type: string
          format: uuid
        entry_date:
          type: string
          format: date
        duration_minutes:
          type: integer
          description: Duration of the time entry, in minutes.
        description:
          type: string
          nullable: true
        hourly_rate_cents:
          type: integer
        total_cents:
          type: integer
        currency:
          type: string
        status:
          type: string
          enum:
          - draft
          - submitted
          - approved
          - rejected
          - billed
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - contract_id
      - entry_date
      - duration_minutes
    time_entry_input:
      type: object
      properties:
        time_entry:
          type: object
          properties:
            contract_id:
              type: string
              format: uuid
            deliverable_id:
              type: string
              format: uuid
            entry_date:
              type: string
              format: date
            duration_minutes:
              type: integer
              description: Duration of the time entry, in minutes.
            description:
              type: string
            hourly_rate_cents:
              type: integer
            email:
              type: string
              format: email
              description: Email of the tenant user this entry belongs to. Required
                on create.
            status:
              type: string
              enum:
              - draft
              - submitted
              - approved
              - rejected
              - billed
          required:
          - contract_id
          - entry_date
          - duration_minutes
          - email
    document_template:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        template_type:
          type: string
          enum:
          - proposal
          - contract
          - order_confirmation
          - sow
          - invoice_cover
        locale:
          type: string
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - template_type
    activity:
      type: object
      properties:
        id:
          type: string
          format: uuid
        activity_type:
          type: string
          enum:
          - call
          - email
          - meeting
          - note
          - task
          - status_change
        subject:
          type: string
          nullable: true
        body:
          type: string
          nullable: true
        occurred_at:
          type: string
          format: date-time
          nullable: true
        duration_minutes:
          type: integer
          nullable: true
        linkable_type:
          type: string
          nullable: true
        linkable_id:
          type: string
          format: uuid
          nullable: true
        contact_id:
          type: string
          format: uuid
          nullable: true
        performed_by_id:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - activity_type
    activity_input:
      type: object
      properties:
        activity:
          type: object
          properties:
            activity_type:
              type: string
              enum:
              - call
              - email
              - meeting
              - note
              - task
              - status_change
            subject:
              type: string
            body:
              type: string
            occurred_at:
              type: string
              format: date-time
            duration_minutes:
              type: integer
            linkable_type:
              type: string
            linkable_id:
              type: string
              format: uuid
            contact_id:
              type: string
              format: uuid
          required:
          - activity_type
    webhook_endpoint:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        target_url:
          type: string
        secret:
          type: string
        events:
          type: array
          items:
            type: string
        enabled:
          type: boolean
        description:
          type: string
          nullable: true
        last_delivered_at:
          type: string
          format: date-time
          nullable: true
        failure_count:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
      - target_url
    webhook_endpoint_input:
      type: object
      properties:
        webhook_endpoint:
          type: object
          properties:
            name:
              type: string
            target_url:
              type: string
            secret:
              type: string
            description:
              type: string
            enabled:
              type: boolean
            events:
              type: array
              items:
                type: string
          required:
          - name
          - target_url
    webhook_delivery:
      type: object
      properties:
        id:
          type: string
          format: uuid
        webhook_endpoint_id:
          type: string
          format: uuid
        event_type:
          type: string
        idempotency_key:
          type: string
        payload:
          type: object
        response_status:
          type: integer
          nullable: true
        status:
          type: string
          enum:
          - pending
          - success
          - failed
          - retrying
        attempt_count:
          type: integer
        completed_at:
          type: string
          format: date-time
          nullable: true
        error_message:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - webhook_endpoint_id
      - event_type
    line_item:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        product_id:
          type: string
          format: uuid
          nullable: true
        unit_price_cents:
          type: integer
        currency:
          type: string
        quantity:
          type: number
        unit_label:
          type: string
          nullable: true
        discount_type:
          type: string
          nullable: true
          enum:
          - percentage
          - amount
        discount_percentage:
          type: number
          format: decimal
          nullable: true
        discount_amount_cents:
          type: integer
          nullable: true
        arr_cents:
          type: integer
        total_cents:
          type: integer
        billing_type:
          type: string
          enum:
          - recurring
          - one_time
          - usage
          - hourly
          nullable: true
        billing_cadence:
          type: string
          enum:
          - monthly
          - quarterly
          - semi_annual
          - annual
          nullable: true
        effective_from:
          type: string
          format: date
          nullable: true
        effective_to:
          type: string
          format: date
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - name
    error:
      type: object
      properties:
        error:
          oneOf:
          - type: string
          - type: array
            items:
              type: string
    pagination_meta:
      type: object
      description: 'Pagination is indicated via Kaminari headers: X-Total, X-Per-Page,
        X-Page, X-Next-Page, X-Prev-Page'
      properties: {}
security:
- bearer: []
