> ## Documentation Index
> Fetch the complete documentation index at: https://developers.primulamanagement.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List payments

> Scoped to company_id; the handler does not restrict this read to the session school assignments. Without group_by, returns sparse payment rows. With group_by and metrics, returns nested PaymentAggregate rows. school_id and school_ids are both applied when provided (their intersection); omitted school filters read the company scope. Row queries support family_id/family_ids; these IDs do not filter the aggregate representation. fields and pagination apply only to rows. Aggregate responses use limit, X-Total-Count and X-Truncated, without Link.

An explicit date range requires both bounds and date_to >= date_from. These date checks occur in the data layer; failures currently produce HTTP 500 rather than 400. last_n_days and explicit bounds can both restrict the result. Item and collection school filters are independent of the session school assignments.



## OpenAPI

````yaml /openapi.yaml get /payments
openapi: 3.1.0
info:
  title: Primula Tracker API V3
  version: 3.0.0
  description: >-
    Reference for the 66 operations registered under `/primula/api/v3` in the V3
    release baseline.


    ## Authentication

    Create a session with `POST /sessions`, then send `Authorization: Bearer
    <access_token>` on protected operations. Tokens must remain associated with
    a valid server-side session. The returned refresh token has no refresh
    endpoint in V3. Session company and school restrictions are documented per
    operation; company_id is not inferred from the token.


    ## Responses and errors

    Collections generally return bare JSON arrays and items return bare JSON
    objects. Report objects, nested maps, and update summaries are explicitly
    described below. List metadata travels in response headers rather than an
    envelope. Empty lists written by collection helpers are `[]`; fields backed
    by nullable values may be `null`, and omitted properties are distinguished
    from null properties in schemas. Handlers generally return `{ "error":
    "message" }`; authentication middleware and activity proxy errors use plain
    text. Unsupported methods on a registered path return 405 with Allow after
    any applicable authentication checks.


    ## Pagination and projections

    Only operations documenting page/per_page support pagination. Defaults are
    page=1 and per_page=100. Values exceeding the documented effective cap are
    clamped, not rejected. X-Total-Count describes the full matching set. Link
    contains applicable navigation links and is omitted for a single page.
    Aggregates use limit (default 50, effective cap 200) instead of pagination
    and send X-Truncated only when rows were omitted. The fields parameter,
    where available, selects the exact response properties; unsupported fields
    return 400.


    ## Query encoding

    Multi-value parameters documented as arrays accept repeated keys or
    comma-separated values. Prefer repeated keys for long UUID lists. Except for
    the two activity proxy operations, query parsing drops the entire query
    string above 8192 bytes and truncates each decoded query value to 128 bytes.
    Parameter combinations and interpretation differ by resource and
    representation; consult each operation. Unknown parameters are rejected only
    where explicitly documented.


    ## Dates and writes

    Calendar dates use YYYY-MM-DD, monthly periods use YYYY-MM, and timestamp
    formats are described in the corresponding schema. Defaults such as today
    depend on the server clock. PATCH operations change only the documented
    properties and reject unknown body properties. Clearing values,
    authorization, and partial-write behavior vary by resource and are described
    per operation.


    Examples use synthetic identifiers and placeholder tokens. Replace the
    server host before using an interactive API client.
servers:
  - url: https://{host}/primula/api/v3
    description: V3 API deployment. Replace the placeholder hostname.
    variables:
      host:
        default: api.example.com
security:
  - bearerAuth: []
tags:
  - name: sessions
    description: Sessions.
  - name: schools
    description: Schools.
  - name: companies
    description: Companies.
  - name: rooms
    description: Rooms.
  - name: users
    description: Users.
  - name: gusto_companies
    description: Gusto companies.
  - name: gusto_employees
    description: Gusto employees.
  - name: gusto_contractors
    description: Gusto contractors.
  - name: gusto_payrolls
    description: Gusto payrolls.
  - name: procare_staff
    description: Procare staff.
  - name: efficiency_ratios
    description: Efficiency ratios.
  - name: enrollment_tracker
    description: Enrollment tracker.
  - name: classroom_attendance
    description: Classroom attendance.
  - name: absences
    description: Absences.
  - name: billing_attendance_audits
    description: Billing attendance audits.
  - name: data_refresh
    description: Data refresh.
  - name: waitlist
    description: Waitlist.
  - name: sibling_discounts
    description: Sibling discounts.
  - name: new_start_tracker
    description: New start tracker.
  - name: withdrawal_tracker
    description: Withdrawal tracker.
  - name: transition_tracker
    description: Transition tracker.
  - name: enrollments
    description: Enrollments.
  - name: pre_registration_fallout
    description: Pre registration fallout.
  - name: procare_messages
    description: Procare messages.
  - name: activities_summary
    description: Activities summary.
  - name: activity_details
    description: Activity details.
  - name: leads
    description: Leads.
  - name: students
    description: Students.
  - name: payments
    description: Payments.
  - name: family_balances
    description: Family balances.
  - name: attendances
    description: Attendances.
  - name: billing_plans
    description: Billing plans.
  - name: spaces
    description: Spaces.
paths:
  /payments:
    get:
      tags:
        - payments
      summary: List payments
      description: >-
        Scoped to company_id; the handler does not restrict this read to the
        session school assignments. Without group_by, returns sparse payment
        rows. With group_by and metrics, returns nested PaymentAggregate rows.
        school_id and school_ids are both applied when provided (their
        intersection); omitted school filters read the company scope. Row
        queries support family_id/family_ids; these IDs do not filter the
        aggregate representation. fields and pagination apply only to rows.
        Aggregate responses use limit, X-Total-Count and X-Truncated, without
        Link.


        An explicit date range requires both bounds and date_to >= date_from.
        These date checks occur in the data layer; failures currently produce
        HTTP 500 rather than 400. last_n_days and explicit bounds can both
        restrict the result. Item and collection school filters are independent
        of the session school assignments.
      operationId: GetPaymentsV3
      parameters:
        - $ref: '#/components/parameters/CompanyId'
        - name: school_id
          in: query
          required: false
          description: >-
            School UUID. Omit to use the operation's documented company/session
            scope.
          schema:
            type: string
            format: uuid
          example: 22222222-2222-4222-8222-222222222222
        - name: school_ids
          in: query
          required: false
          description: >-
            Optional school UUID filter. When school_id is also supplied, both
            filters apply. Accepts repeated query parameters or comma-separated
            values; empty entries are dropped and surrounding whitespace is
            trimmed.
          schema:
            type: array
            items:
              type: string
              format: uuid
          style: form
          explode: true
        - name: family_id
          in: query
          required: false
          description: >-
            Row view only: optional family UUID. When family_ids is also
            supplied, the reader first restricts to this family; family_ids
            cannot widen that scope.
          schema:
            type: string
            format: uuid
        - name: family_ids
          in: query
          required: false
          description: >-
            Optional family UUIDs; row view only. Accepts repeated query
            parameters or comma-separated values; empty entries are dropped and
            surrounding whitespace is trimmed.
          schema:
            type: array
            items:
              type: string
              format: uuid
          style: form
          explode: true
        - name: family_name
          in: query
          required: false
          description: Case-insensitive family-name substring filter.
          schema:
            type: string
        - name: date_from
          in: query
          required: false
          description: >-
            Inclusive start date in YYYY-MM-DD. Optional; both date bounds must
            be supplied together and date_to must not precede date_from.
          schema:
            type: string
            format: date
          example: '2026-01-01'
        - name: date_to
          in: query
          required: false
          description: >-
            Inclusive end date in YYYY-MM-DD. Optional; both date bounds must be
            supplied together and date_to must not precede date_from.
          schema:
            type: string
            format: date
          example: '2026-01-31'
        - name: last_n_days
          in: query
          required: false
          description: >-
            Rolling lookback on payment created_at (not transaction_date).
            Positive values add this filter; zero/negative values do not. May be
            combined with a transaction-date range.
          schema:
            type: integer
        - name: state
          in: query
          required: false
          description: Case-insensitive payment state filter.
          schema:
            type: string
        - name: is_posted
          in: query
          required: false
          description: >-
            Filter by posted state; omit for both states. Accepts true/1 and
            false/0, case-insensitive.
          schema:
            type: boolean
        - name: successful_only
          in: query
          required: false
          description: >-
            Include payments that are posted or whose normalized state is
            success, succeeded, completed, paid, posted, processed, settled or
            state_success. Only true/1 enables the filter; other values disable
            it.
          schema:
            type: boolean
            default: false
        - name: payment_mode
          in: query
          required: false
          description: Case-insensitive payment mode filter.
          schema:
            type: string
        - name: payment_method_sub_kind
          in: query
          required: false
          description: Case-insensitive payment method subtype filter.
          schema:
            type: string
        - name: min_amount
          in: query
          required: false
          description: Inclusive minimum payment amount.
          schema:
            type: number
        - name: max_amount
          in: query
          required: false
          description: Inclusive maximum payment amount.
          schema:
            type: number
        - name: fields
          in: query
          required: false
          description: >-
            Select response properties. Unknown names return 400. Default:
            payment_id, family_id, family_name, transaction_date, amount,
            total_amount, state. Row view only. Accepts repeated query
            parameters or comma-separated values; empty entries are dropped and
            surrounding whitespace is trimmed.
          schema:
            type: array
            items:
              type: string
              enum:
                - payment_id
                - transaction_id
                - school_id
                - family_id
                - family_name
                - transaction_date
                - amount
                - fee_amount
                - total_amount
                - balance
                - state
                - receipt_number
                - payment_mode
                - payment_method_sub_kind
                - description
                - is_posted
                - payd_online
                - auto_billing
                - created_at
          style: form
          explode: true
        - name: sort_by
          in: query
          required: false
          description: >-
            Rows: transaction_date, amount, total_amount, created_at or
            family_name with _asc/_desc (default transaction_date_desc).
            Aggregates: group or any supported metric with _asc/_desc.
            Unrecognized sort keys fall back to the backend default. Aggregate
            default: count_desc.
          schema:
            type: string
        - name: group_by
          in: query
          required: false
          description: Select an aggregate representation; metrics must also be supplied.
          schema:
            type: string
            enum:
              - school_id
              - family_id
              - month
              - state
              - payment_mode
              - payment_method_sub_kind
        - name: metrics
          in: query
          required: false
          description: >-
            Metrics to calculate; required with group_by and invalid without it.
            Accepts repeated query parameters or comma-separated values; empty
            entries are dropped and surrounding whitespace is trimmed.
          schema:
            type: array
            items:
              type: string
              enum:
                - count
                - sum_amount
                - sum_total_amount
                - sum_fee_amount
                - avg_amount
                - avg_total_amount
                - avg_fee_amount
          style: form
          explode: true
        - name: limit
          in: query
          required: false
          description: >-
            Aggregate result limit. Defaults to 50 and clamps values above 200
            to 200. Zero and negative values use the default.
          schema:
            type: integer
            default: 50
        - name: page
          in: query
          required: false
          description: One-based page number. Row view only.
          schema:
            type: integer
            minimum: 1
            default: 1
          example: 1
        - name: per_page
          in: query
          required: false
          description: >-
            Items per page; values greater than 200 are clamped to 200. Row view
            only.
          schema:
            type: integer
            minimum: 1
            default: 100
          example: 100
      responses:
        '200':
          description: Successful response.
          content:
            application/json:
              schema:
                anyOf:
                  - type: array
                    items:
                      $ref: '#/components/schemas/Payment'
                  - type: array
                    items:
                      $ref: '#/components/schemas/PaymentAggregate'
              examples:
                rows:
                  summary: Default payment fields
                  value:
                    - payment_id: 66666666-6666-4666-8666-666666666666
                      family_id: 77777777-7777-4777-8777-777777777777
                      family_name: Example family
                      transaction_date: '2026-01-15'
                      amount: 100
                      total_amount: 100
                      state: paid
                aggregate:
                  summary: group_by=school_id&metrics=count,sum_amount
                  value:
                    - group:
                        school_id: 22222222-2222-4222-8222-222222222222
                      metrics:
                        count: 2
                        sum_amount: 200
          headers:
            X-Total-Count:
              $ref: '#/components/headers/TotalCount'
            Link:
              $ref: '#/components/headers/Link'
              description: Only for rows.
            X-Truncated:
              $ref: '#/components/headers/Truncated'
              description: Only for aggregates.
        '400':
          $ref: '#/components/responses/400'
        '401':
          $ref: '#/components/responses/401'
        '403':
          $ref: '#/components/responses/403'
        '405':
          $ref: '#/components/responses/405'
        '500':
          $ref: '#/components/responses/500'
components:
  parameters:
    CompanyId:
      name: company_id
      in: query
      required: true
      description: >-
        Company UUID. Must equal the authenticated session company when that
        session has a company assigned; the equality check is skipped for
        sessions without one.
      schema:
        type: string
        format: uuid
      example: 11111111-1111-4111-8111-111111111111
  schemas:
    Payment:
      type: object
      description: >-
        A sparse row containing only the selected fields. Default fields:
        payment_id, family_id, family_name, transaction_date, amount,
        total_amount, state.
      properties:
        payment_id:
          type: string
          description: Payment id.
        transaction_id:
          type: string
          description: Transaction id.
        school_id:
          type: string
          description: School id.
        family_id:
          type: string
          description: Family id.
        family_name:
          type: string
        transaction_date:
          type: string
          format: date
        amount:
          type: number
        fee_amount:
          type:
            - number
            - 'null'
        total_amount:
          type: number
        balance:
          type: number
        state:
          type: string
        receipt_number:
          type: string
        payment_mode:
          type: string
        payment_method_sub_kind:
          type: string
        description:
          type: string
        is_posted:
          type: boolean
        payd_online:
          type: boolean
        auto_billing:
          type: boolean
        created_at:
          type: string
          description: Local timestamp in YYYY-MM-DD HH:mm:ss format; no timezone suffix.
          examples:
            - '2026-01-15 09:30:00'
      additionalProperties: false
    PaymentAggregate:
      type: object
      properties:
        group:
          type: object
          properties:
            school_id:
              type: string
              description: >-
                Grouping value; missing data may be represented by an empty or
                unknown label.
            family_id:
              type: string
              description: >-
                Grouping value; missing data may be represented by an empty or
                unknown label.
            month:
              type: string
              description: >-
                Grouping value; missing data may be represented by an empty or
                unknown label.
            state:
              type: string
              description: >-
                Grouping value; missing data may be represented by an empty or
                unknown label.
            payment_mode:
              type: string
              description: >-
                Grouping value; missing data may be represented by an empty or
                unknown label.
            payment_method_sub_kind:
              type: string
              description: >-
                Grouping value; missing data may be represented by an empty or
                unknown label.
          additionalProperties: false
        metrics:
          type: object
          properties:
            count:
              type: integer
            sum_amount:
              type: number
            sum_total_amount:
              type: number
            sum_fee_amount:
              type: number
            avg_amount:
              type: number
            avg_total_amount:
              type: number
            avg_fee_amount:
              type: number
          additionalProperties: false
      required:
        - group
        - metrics
      additionalProperties: false
      description: >-
        Contains the selected grouping key and requested metrics only. Grouping
        keys are nested under group; metric values are nested under metrics.
    Error:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message.
      required:
        - error
      description: Error.
  headers:
    TotalCount:
      description: >-
        Total matching rows before pagination, or matching groups before the
        aggregate limit. For unpaginated arrays, the number of returned rows.
      schema:
        type: integer
        minimum: 0
      example: 125
    Link:
      description: >-
        Pagination links using rel="next", "last", "prev", and "first". Only
        applicable links are sent; absent when all results fit on one page.
        Links preserve the original query parameters.
      schema:
        type: string
      example: >-
        </primula/api/v3/students?company_id=11111111-1111-4111-8111-111111111111&page=2>;
        rel="next"
    Truncated:
      description: Present with value true only when an aggregate result exceeds its limit.
      schema:
        type: boolean
      example: true
  responses:
    '400':
      description: Invalid or missing parameters or request body.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Invalid or missing parameters or request body.
    '401':
      description: >-
        Missing or invalid Bearer token, or an absent, invalid or expired
        server-side session. The authentication middleware returns plain text
        with a trailing newline.
      content:
        text/plain:
          schema:
            type: string
          examples:
            missing:
              value: |
                missing authorization header
            invalid:
              value: |
                Unauthorized
            expired:
              value: |
                Session expired
    '403':
      description: The authenticated session is not allowed to perform this operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: >-
              The authenticated session is not allowed to perform this
              operation.
    '405':
      description: The path exists but does not support this HTTP method.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: The path exists but does not support this HTTP method.
      headers:
        Allow:
          description: Comma-separated HTTP methods accepted by this path.
          schema:
            type: string
    '500':
      description: The operation could not be completed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: The operation could not be completed.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        Access token issued by POST /sessions and associated with a valid
        server-side session.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.