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

# Summarise transactions

> Totals over the same transactions `/transactions` lists, using the same filters,
grouped by a period bucket and at most one other dimension.

**What it counts is fixed and cannot be overridden**, so the figures agree with the
totals ThinkOut shows. Transfers between the workspace's own accounts, transactions
excluded by the user, and duplicates are all left out. Split transactions are counted
through their pieces, never through the original, so nothing is counted twice.

None of those distinctions appear on `/transactions`, which reports movements rather
than cash flow. Summing a list will therefore not reproduce these figures, and is not
meant to.

Amounts are converted to `currency`, defaulting to the workspace currency. Each
transaction converts at the rate for its own date. The `conversion` object on the
response says what was done.

Rows grouped by period alone carry what the cash flow screen shows for that period:
the flows, and the starting balance, transfers, excluded movements and final balance
of the accounts in scope. Rows grouped by a second dimension carry the flows only,
since a balance per counterparty or category means nothing. `totals` covers the
whole window, so a caller never adds rows.

Filters stack: `bank_id`, `account_id`, `category_id`, `counterparty_id`, `label_id`
and `direction` may all be given together and combine with AND, and each id filter
takes several ids that combine with OR. Grouping is the narrower thing: a period
bucket and at most one more dimension. Marketing spend on one project per month is
one call with two filters and no `group_by`; spend per category and per label at
once is two calls.

Three shapes of question, three requests:

```
Inflows and outflows per month, with balances
GET /transactions/summary?from=2026-03-01&to=2026-04-30&interval=month

Top counterparties this quarter
GET /transactions/summary?from=2026-07-01&to=2026-09-30&interval=month&group_by=counterparty&direction=outflow

Cash flow by month for one bank's accounts
GET /transactions/summary?from=2026-03-01&to=2026-08-31&interval=month&bank_id=5d6e7f80-9a1b-4c2d-8e3f-4a5b6c7d8e9f
```

The response examples below show the first two.

The window is always explicit. There are no named periods here; a caller that wants
"last quarter" computes the two dates and passes them, and the response echoes them.




## OpenAPI

````yaml /openapi/v1.yaml get /transactions/summary
openapi: 3.1.0
info:
  title: ThinkOut API
  version: '1.0'
  description: >
    Programmatic access to the data of a ThinkOut workspace.


    Every endpoint in version 1 is a `GET`. Lists share one response envelope,
    and page

    with a cursor wherever the list can grow.


    Version 1 changes additively. New endpoints, new optional parameters, new
    response

    fields and new enum values can appear at any time, so tolerate unknown
    fields and

    never treat an enum as a closed set.
  contact:
    name: ThinkOut developer support
    url: https://developers.thinkout.io/guides/support
servers:
  - url: https://api.thinkout.io/v1
security:
  - apiKey: []
tags:
  - name: Banks
    description: The workspace's bank connections and the state of each one.
  - name: Accounts
    description: Manual, bank-synced and delegated accounts with computed balances.
  - name: Transactions
    description: Booked and pending movements on the workspace's accounts.
  - name: Categories
    description: The workspace category tree, split by direction and business activity.
  - name: Counterparties
    description: Customers and suppliers that transactions and forecasts are attributed to.
  - name: Labels
    description: Free-form tags grouped by label type.
  - name: Forecasts
    description: Planned inflows and outflows and the transactions that realised them.
  - name: Reports
    description: >-
      Totals rather than rows. Summaries over transactions and forecasts, and
      the workspace's own saved cash flow views.
paths:
  /transactions/summary:
    get:
      tags:
        - Reports
      summary: Summarise transactions
      description: >
        Totals over the same transactions `/transactions` lists, using the same
        filters,

        grouped by a period bucket and at most one other dimension.


        **What it counts is fixed and cannot be overridden**, so the figures
        agree with the

        totals ThinkOut shows. Transfers between the workspace's own accounts,
        transactions

        excluded by the user, and duplicates are all left out. Split
        transactions are counted

        through their pieces, never through the original, so nothing is counted
        twice.


        None of those distinctions appear on `/transactions`, which reports
        movements rather

        than cash flow. Summing a list will therefore not reproduce these
        figures, and is not

        meant to.


        Amounts are converted to `currency`, defaulting to the workspace
        currency. Each

        transaction converts at the rate for its own date. The `conversion`
        object on the

        response says what was done.


        Rows grouped by period alone carry what the cash flow screen shows for
        that period:

        the flows, and the starting balance, transfers, excluded movements and
        final balance

        of the accounts in scope. Rows grouped by a second dimension carry the
        flows only,

        since a balance per counterparty or category means nothing. `totals`
        covers the

        whole window, so a caller never adds rows.


        Filters stack: `bank_id`, `account_id`, `category_id`,
        `counterparty_id`, `label_id`

        and `direction` may all be given together and combine with AND, and each
        id filter

        takes several ids that combine with OR. Grouping is the narrower thing:
        a period

        bucket and at most one more dimension. Marketing spend on one project
        per month is

        one call with two filters and no `group_by`; spend per category and per
        label at

        once is two calls.


        Three shapes of question, three requests:


        ```

        Inflows and outflows per month, with balances

        GET /transactions/summary?from=2026-03-01&to=2026-04-30&interval=month


        Top counterparties this quarter

        GET
        /transactions/summary?from=2026-07-01&to=2026-09-30&interval=month&group_by=counterparty&direction=outflow


        Cash flow by month for one bank's accounts

        GET
        /transactions/summary?from=2026-03-01&to=2026-08-31&interval=month&bank_id=5d6e7f80-9a1b-4c2d-8e3f-4a5b6c7d8e9f

        ```


        The response examples below show the first two.


        The window is always explicit. There are no named periods here; a caller
        that wants

        "last quarter" computes the two dates and passes them, and the response
        echoes them.
      operationId: summariseTransactions
      parameters:
        - name: from
          in: query
          required: true
          description: First day of the window, inclusive.
          schema:
            type: string
            format: date
        - name: to
          in: query
          required: true
          description: Last day of the window, inclusive.
          schema:
            type: string
            format: date
        - $ref: '#/components/parameters/interval'
        - name: group_by
          in: query
          description: >
            A second dimension to group by, alongside the period bucket. At most
            one.

            Omit it to get one row per period.
          schema:
            $ref: '#/components/schemas/GroupDimension'
        - $ref: '#/components/parameters/currency'
        - $ref: '#/components/parameters/bank_id'
        - $ref: '#/components/parameters/account_id'
        - $ref: '#/components/parameters/category_id'
        - $ref: '#/components/parameters/label_id'
        - $ref: '#/components/parameters/counterparty_id'
        - $ref: '#/components/parameters/direction'
      responses:
        '200':
          description: The summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Summary'
              examples:
                By month, with balances:
                  summary: >-
                    One row per month; balance fields present because nothing
                    else is grouped
                  value:
                    object: summary
                    period:
                      from: '2026-03-01'
                      to: '2026-04-30'
                      macro: null
                      interval: month
                    conversion:
                      applied: true
                      currency: RON
                      basis: transaction_date
                    group_by: null
                    has_data: true
                    totals:
                      currency: RON
                      inflow: '163900.00'
                      outflow: '-118420.75'
                      net: '45479.25'
                      count: 401
                    data:
                      - period_start: '2026-03-01'
                        period_end: '2026-03-31'
                        group: null
                        currency: RON
                        inflow: '84200.00'
                        outflow: '-61350.25'
                        net: '22849.75'
                        count: 212
                        starting_balance: '128400.10'
                        transfers: '0.00'
                        excluded: '-1500.00'
                        final_balance: '149749.85'
                      - period_start: '2026-04-01'
                        period_end: '2026-04-30'
                        group: null
                        currency: RON
                        inflow: '79700.00'
                        outflow: '-57070.50'
                        net: '22629.50'
                        count: 189
                        starting_balance: '149749.85'
                        transfers: '0.00'
                        excluded: '0.00'
                        final_balance: '172379.35'
                By month and counterparty:
                  summary: >-
                    One row per month per counterparty; balance fields null on
                    grouped rows
                  value:
                    object: summary
                    period:
                      from: '2026-07-01'
                      to: '2026-08-31'
                      macro: null
                      interval: month
                    conversion:
                      applied: true
                      currency: RON
                      basis: transaction_date
                    group_by: counterparty
                    has_data: true
                    totals:
                      currency: RON
                      inflow: '0.00'
                      outflow: '-42461.00'
                      net: '-42461.00'
                      count: 9
                    data:
                      - period_start: '2026-07-01'
                        period_end: '2026-07-31'
                        group:
                          dimension: counterparty
                          id: 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
                          label: Orange Romania
                        currency: RON
                        inflow: '0.00'
                        outflow: '-6115.50'
                        net: '-6115.50'
                        count: 2
                        starting_balance: null
                        transfers: null
                        excluded: null
                        final_balance: null
                      - period_start: '2026-07-01'
                        period_end: '2026-07-31'
                        group:
                          dimension: counterparty
                          id: 4d5e6f70-8192-4a3b-9c4d-5e6f7a8b9c0d
                          label: Regus
                        currency: RON
                        inflow: '0.00'
                        outflow: '-12000.00'
                        net: '-12000.00'
                        count: 1
                        starting_balance: null
                        transfers: null
                        excluded: null
                        final_balance: null
                      - period_start: '2026-08-01'
                        period_end: '2026-08-31'
                        group:
                          dimension: counterparty
                          id: 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
                          label: Orange Romania
                        currency: RON
                        inflow: '0.00'
                        outflow: '-6115.50'
                        net: '-6115.50'
                        count: 2
                        starting_balance: null
                        transfers: null
                        excluded: null
                        final_balance: null
                      - period_start: '2026-08-01'
                        period_end: '2026-08-31'
                        group:
                          dimension: counterparty
                          id: 4d5e6f70-8192-4a3b-9c4d-5e6f7a8b9c0d
                          label: Regus
                        currency: RON
                        inflow: '0.00'
                        outflow: '-18230.00'
                        net: '-18230.00'
                        count: 4
                        starting_balance: null
                        transfers: null
                        excluded: null
                        final_balance: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/RequestTooLarge'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  parameters:
    interval:
      name: interval
      in: query
      description: The period bucket rows are grouped into.
      schema:
        $ref: '#/components/schemas/Interval'
    currency:
      name: currency
      in: query
      description: >
        Currency to report totals in. Defaults to the workspace currency. The
        `conversion`

        object on the response says whether conversion happened and on what
        basis.
      schema:
        $ref: '#/components/schemas/Currency'
    bank_id:
      name: bank_id
      in: query
      description: >-
        Only rows on the accounts fed by these bank connections. Comma-separated
        for several.
      style: form
      explode: false
      schema:
        type: array
        items:
          type: string
          format: uuid
    account_id:
      name: account_id
      in: query
      description: Only rows on these accounts. Comma-separated for several.
      style: form
      explode: false
      schema:
        type: array
        items:
          type: string
          format: uuid
    category_id:
      name: category_id
      in: query
      description: >
        Only rows in these categories. Comma-separated for several. On
        transactions this

        also matches a split transaction whose pieces are in one of them, even
        though the

        row itself carries no category.
      style: form
      explode: false
      schema:
        type: array
        items:
          type: string
          format: uuid
    label_id:
      name: label_id
      in: query
      description: >-
        Only rows carrying at least one of these labels. Comma-separated for
        several.
      style: form
      explode: false
      schema:
        type: array
        items:
          type: string
          format: uuid
    counterparty_id:
      name: counterparty_id
      in: query
      description: >-
        Only rows attributed to these counterparties. Comma-separated for
        several.
      style: form
      explode: false
      schema:
        type: array
        items:
          type: string
          format: uuid
    direction:
      name: direction
      in: query
      schema:
        $ref: '#/components/schemas/Direction'
  schemas:
    GroupDimension:
      type: string
      description: A dimension to group report rows by, alongside the period bucket.
      enum:
        - category
        - counterparty
        - label
        - account
        - activity
        - direction
    Summary:
      type: object
      required:
        - object
        - period
        - conversion
        - group_by
        - has_data
        - totals
        - data
      properties:
        object:
          type: string
          const: summary
        period:
          $ref: '#/components/schemas/ResolvedPeriod'
        conversion:
          $ref: '#/components/schemas/Conversion'
        group_by:
          oneOf:
            - $ref: '#/components/schemas/GroupDimension'
            - type: 'null'
        has_data:
          type: boolean
          description: >
            `false` when nothing matched. Distinguishes "no transactions here"
            from totals

            that genuinely came to zero.
        totals:
          $ref: '#/components/schemas/SummaryTotals'
        data:
          type: array
          items:
            $ref: '#/components/schemas/SummaryRow'
      example:
        object: summary
        period:
          from: '2026-03-01'
          to: '2026-04-30'
          macro: null
          interval: month
        conversion:
          applied: true
          currency: RON
          basis: transaction_date
        group_by: null
        has_data: true
        totals:
          currency: RON
          inflow: '163900.00'
          outflow: '-118420.75'
          net: '45479.25'
          count: 401
        data:
          - period_start: '2026-03-01'
            period_end: '2026-03-31'
            group: null
            currency: RON
            inflow: '84200.00'
            outflow: '-61350.25'
            net: '22849.75'
            count: 212
            starting_balance: '128400.10'
            transfers: '0.00'
            excluded: '-1500.00'
            final_balance: '149749.85'
          - period_start: '2026-04-01'
            period_end: '2026-04-30'
            group: null
            currency: RON
            inflow: '79700.00'
            outflow: '-57070.50'
            net: '22629.50'
            count: 189
            starting_balance: '149749.85'
            transfers: '0.00'
            excluded: '0.00'
            final_balance: '172379.35'
    Interval:
      type: string
      description: >-
        A period bucket for report rows. The same buckets the ThinkOut cash flow
        screen offers.
      enum:
        - day
        - week
        - month
      default: month
    Currency:
      type: string
      description: ISO 4217 alphabetic currency code.
      pattern: ^[A-Z]{3}$
      example: RON
    Direction:
      type: string
      description: >
        Whether money comes in or goes out.


        On a transaction or a forecast it follows the sign of `amount` and can
        never

        disagree with it. It is present so that responses can be grouped and
        read without

        inspecting signs.
      enum:
        - inflow
        - outflow
    ResolvedPeriod:
      type: object
      description: >-
        The window actually computed, after resolving any macro or stored
        relative dates.
      required:
        - from
        - to
        - macro
        - interval
      properties:
        from:
          type: string
          format: date
        to:
          type: string
          format: date
        macro:
          oneOf:
            - $ref: '#/components/schemas/PeriodMacro'
            - type: 'null'
          description: >-
            The macro that produced these dates, or `null` when they were given
            directly.
        interval:
          $ref: '#/components/schemas/Interval'
    Conversion:
      type: object
      description: What was done to make amounts comparable.
      required:
        - applied
        - currency
        - basis
      properties:
        applied:
          type: boolean
          description: '`false` when every amount was already in `currency`.'
        currency:
          $ref: '#/components/schemas/Currency'
        basis:
          type: string
          description: >
            `transaction_date` converts each movement at the rate for its own
            date, which is

            what totals over movements use. `balance_date` converts a whole
            balance at one

            rate, which is what balances use because an account holds a single
            currency.
          enum:
            - transaction_date
            - balance_date
            - none
    SummaryTotals:
      type: object
      description: The whole window added up, after the same exclusions as the rows.
      required:
        - currency
        - inflow
        - outflow
        - net
        - count
      properties:
        currency:
          $ref: '#/components/schemas/Currency'
        inflow:
          $ref: '#/components/schemas/Money'
        outflow:
          $ref: '#/components/schemas/Money'
        net:
          $ref: '#/components/schemas/Money'
        count:
          type: integer
    SummaryRow:
      type: object
      required:
        - period_start
        - period_end
        - group
        - currency
        - inflow
        - outflow
        - net
        - count
        - starting_balance
        - transfers
        - excluded
        - final_balance
      properties:
        period_start:
          type: string
          format: date
        period_end:
          type: string
          format: date
        group:
          oneOf:
            - $ref: '#/components/schemas/ReportGroup'
            - type: 'null'
        currency:
          $ref: '#/components/schemas/Currency'
        inflow:
          $ref: '#/components/schemas/Money'
        outflow:
          $ref: '#/components/schemas/Money'
        net:
          $ref: '#/components/schemas/Money'
        count:
          type: integer
          description: Transactions behind this row, after the report's exclusions.
        starting_balance:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: >
            Balance of the accounts in scope at the start of the period. Present
            on rows

            grouped by period alone; `null` on rows grouped by a second
            dimension.
        transfers:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: >-
            Net of transfers between the workspace's own accounts in the period.
            Not part of the flows. `null` on grouped rows.
        excluded:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: >-
            Net of movements the user excluded from cash flow in the period. Not
            part of the flows. `null` on grouped rows.
        final_balance:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: >
            Balance at the end of the period: starting balance plus net,
            transfers and

            excluded. `null` on grouped rows.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
            - param
            - request_id
          properties:
            type:
              type: string
              enum:
                - invalid_request
                - authentication
                - not_found
                - rate_limit
                - server
            code:
              type: string
              description: >
                Stable machine-readable reason. Branch on this, not on
                `message`.

                Report-specific codes are `too_many_groups`, `period_too_long`,

                `as_of_in_future` and `invalid_expand`.
            message:
              type: string
              description: Human-readable explanation. Wording may change.
            param:
              type:
                - string
                - 'null'
              description: The query or path parameter at fault, when there is one.
            request_id:
              type: string
              description: Quote this when contacting support.
    PeriodMacro:
      type: string
      description: >
        A named window resolved on the server. The dates it resolved to are
        always reported

        back, so a caller can check it asked for the window it meant.
      enum:
        - this_month
        - last_month
        - this_quarter
        - last_quarter
        - this_year
        - last_year
        - month_to_date
        - quarter_to_date
        - year_to_date
        - last_30_days
        - last_90_days
        - last_12_months
    Money:
      type: string
      description: >
        Decimal amount as a string. Signed on transactions, forecasts and report
        figures,

        where negative means an outflow. The sign is authoritative: `direction`
        is derived

        from it and the two can never disagree.
      pattern: ^-?[0-9]+(\.[0-9]+)?$
      example: '-1250.00'
    ReportGroup:
      type: object
      description: What a report row is grouped by. `null` on rows grouped by period alone.
      required:
        - dimension
        - id
        - label
      properties:
        dimension:
          type: string
          example: category
        id:
          type:
            - string
            - 'null'
          format: uuid
          description: >
            The grouped object's id, so a total can be followed back to its rows
            in one

            call. `null` for dimensions that are not objects, such as
            `direction`.
        label:
          type: string
          description: Human-readable name of the group.
          example: Telecom
  responses:
    BadRequest:
      description: A parameter is invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: invalid_request
              code: invalid_parameter
              message: Parameter 'from' must be a date in YYYY-MM-DD format.
              param: from
              request_id: req_01J8ZT4Q7Y6M3K
    Unauthorized:
      description: The API key is missing, unknown or revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: authentication
              code: unauthorized
              message: Invalid API key.
              param: null
              request_id: req_01J8ZT4Q7Y6M3K
    RequestTooLarge:
      description: >
        The request is valid but would compute more than a report is allowed to.
        The message

        names both what was asked for and the ceiling, so the request can be
        narrowed without

        guessing.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: invalid_request
              code: too_many_groups
              message: >-
                Grouping by counterparty over this period produces 4,182 groups.
                The maximum is 1,000. Narrow the period or the filters.
              param: group_by
              request_id: req_01J8ZT4Q7Y6M3K
    RateLimited:
      description: Too many requests. Wait for the number of seconds in `Retry-After`.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: rate_limit
              code: rate_limited
              message: Rate limit exceeded. Retry after 12 seconds.
              param: null
              request_id: req_01J8ZT4Q7Y6M3K
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: >-
        A workspace API key created in ThinkOut settings. One key reads one
        workspace.

````