Aggregate Transactions

Introduction

POST /transactions/aggregate computes a per-bucket median multiple for a single cohort of deals. You choose one metric (such as ev_revenue or valuation), slice the cohort by time_bucket (month, quarter, year, or a single overall bucket), and scope it with the same filters vocabulary as the transactions list. The response returns {bucket, n, median, q1, q3} arrays plus the methodology constants used, so you can chart valuation trends or benchmark a comparable set without pulling and reducing the raw deals yourself.

Set up

Endpoint

POST https://multiples.vc/api/private/v1/transactions/aggregate

Request

Send a JSON body with the metric to aggregate, an optional time bucket, and a filters object that defines the cohort. metric and filters are required.

Median EV/Revenue per quarter across M&A deals:

{
  "metric": "ev_revenue",
  "time_bucket": "quarter",
  "filters": {
    "deal_type": ["M&A"],
    "date_from": "2021-01-01",
    "date_to": "2025-12-31"
  }
}

Median EV/Revenue per year for European Growth/Venture rounds:

{
  "metric": "ev_revenue",
  "time_bucket": "year",
  "filters": {
    "deal_type": [
      "Growth",
      "Venture"
    ],
    "target_region": [
      "Western Europe",
      "Northern Europe"
    ],
    "date_from": "2020-01-01",
    "date_to": "2024-12-31"
  }
}

Top-level parameters:

field

type

description

metric

string (required)

The value aggregated per bucket. One of ev_revenue, ev_ebitda, valuation, raised, revenue, ebitda.

time_bucket

string

How deals are grouped over time: month, quarter, year, or none. Defaults to none (one overall bucket).

filters

object (required)

Cohort definition. Accepts the full /transactions filter vocabulary (below).

filters fields (all optional):

field

type

description

id

array of uuid

Match specific deals by ID.

target_id

array of uuid

Restrict to deals whose target is one of these companies.

investor_id

array of uuid

Match deals where any of these companies participated as a lead investor or buyer.

deal_type

array of string

One or more of Early, Growth, M&A, Public listing, Secondary, Venture.

deal_subtype

array of string

One or more of Angel, ICO, PE buyout, Pre-seed, Secondary - private, Secondary - public, Seed, Series A-Series J, Strategic M&A, Strategic investment, Undisclosed stage.

date_from

date (YYYY-MM-DD)

Earliest deal date to include. Defaults to 5 years before today when omitted.

date_to

date (YYYY-MM-DD)

Latest deal date to include. Defaults to today when omitted.

valuation_min / valuation_max

number

Bound deal valuation (millions USD).

raised_min / raised_max

number

Bound amount raised.

revenue_min / revenue_max

number

Bound deal revenue.

ebitda_min / ebitda_max

number

Bound deal EBITDA.

ev_revenue_min / ev_revenue_max

number

Bound the EV/Revenue multiple.

ev_ebitda_min / ev_ebitda_max

number

Bound the EV/EBITDA multiple.

target_country

array of string

Target country (ISO 3166-1 alpha-3 codes).

target_region

array of string

Target region (e.g. North America, Western Europe).

target_verticals

array of string

Match deals whose target operates in any of these verticals.

target_themes

array of string

Match deals whose target maps to any of these themes.

target_client_focus

array of string

Match by target client focus (e.g. B2C).

target_revenue_model

array of string

Match by target revenue model.

target_company_type

array of string

Match by target company type.

updated_at

date-time

Change-feed checkpoint: only deals updated at or after this timestamp.

include_deleted

boolean

Include soft-deleted deals. Defaults to false.

The accepted filter fields, metric enums, time buckets, and methodology constants are also discoverable at runtime via GET /lookup/transactions.

Response

Each data entry is one bucket. Buckets with no deals (n: 0) are returned so charts have explicit gaps, and buckets below the small-N threshold report n but suppress the statistics with warning: "insufficient_sample". Example for median EV/Revenue per year for European Growth/Venture rounds (the second request above):

{
  "data": [
    {
      "bucket": "2020",
      "n": 42,
      "median": 10.7718120805369,
      "q1": 5.55991124260355,
      "q3": 17.9166666666667,
      "warning": null
    },
    {
      "bucket": "2021",
      "n": 90,
      "median": 9.88470557582074,
      "q1": 5,
      "q3": 18.7724830128347,
      "warning": null
    },
    {
      "bucket": "2022",
      "n": 68,
      "median": 12.5009557522124,
      "q1": 6.70950704225352,
      "q3": 22.0485210171251,
      "warning": null
    },
    {
      "bucket": "2023",
      "n": 24,
      "median": 6.76799805385352,
      "q1": 4.93762963252847,
      "q3": 11.3538205980066,
      "warning": null
    },
    {
      "bucket": "2024",
      "n": 37,
      "median": 9.5,
      "q1": 4.46632478632479,
      "q3": 17,
      "warning": null
    }
  ],
  "meta": {
    "metric": "ev_revenue",
    "time_bucket": "year",
    "total_deals": 284,
    "outliers_trimmed": 23,
    "methodology": {
      "small_n_threshold": 3,
      "outlier_rule": "iqr_1.5",
      "default_date_range_years": 5,
      "empty_buckets_returned": true,
      "date_range": {
        "from": "2020-01-01",
        "to": "2024-12-31"
      }
    }
  }
}

When time_bucket is none, data holds a single entry whose bucket is null, covering the whole cohort:

{
  "data": [
    { "bucket": null, "n": 211, "median": 390, "q1": 100.05, "q3": 1000, "warning": null }
  ],
  "meta": {
    "metric": "valuation",
    "time_bucket": "none",
    "total_deals": 242,
    "outliers_trimmed": 31,
    "methodology": {
      "small_n_threshold": 3,
      "outlier_rule": "iqr_1.5",
      "default_date_range_years": 5,
      "empty_buckets_returned": true,
      "date_range": { "from": "2021-06-15", "to": "2026-06-15" }
    }
  }
}

Fields

data[] - one object per bucket:

field

type

description

bucket

string | null

The bucket label: YYYY-MM (month), YYYY-Qn (quarter), or YYYY (year). null when time_bucket is none.

n

integer

Number of deals in the bucket after outlier trimming.

median

number | null

Median of the chosen metric for the bucket. null when n is below small_n_threshold or the bucket is empty.

q1

number | null

First quartile (25th percentile) of the metric. null under the same conditions as median.

q3

number | null

Third quartile (75th percentile) of the metric. null under the same conditions as median.

warning

string | null

insufficient_sample when the bucket has too few deals to report statistics; otherwise null.

meta - context and methodology:

field

type

description

metric

string

The metric that was aggregated (echoes the request).

time_bucket

string

The time bucket that was applied (echoes the request).

total_deals

integer

Total matching deals across all buckets, before outlier trimming.

outliers_trimmed

integer

Number of deals removed by the outlier rule before computing statistics.

methodology

object

Constants used for this aggregation (see below).

meta.methodology:

field

type

description

small_n_threshold

integer

Buckets with fewer than this many deals report n but suppress median/q1/q3 and flag insufficient_sample.

outlier_rule

string

The outlier-trimming rule applied, e.g. iqr_1.5 (values outside 1.5×IQR of the quartiles are dropped).

default_date_range_years

integer

Lookback window (in years) used when date_from/date_to are omitted.

empty_buckets_returned

boolean

Whether zero-deal buckets are emitted in data (so time series have explicit gaps).

date_range

object

The effective date window applied, with from and to (YYYY-MM-DD).

date_range.from

string (date)

Start of the effective window (inclusive).

date_range.to

string (date)

End of the effective window (inclusive).