Skip to main content
GET /admin/monetization/platform-fees returns a revenue rollup of every droplinked-monetized transaction in the requested window, aggregated by PSP / merchant / day. Operators use it to answer: “how much is droplinked actually earning, and from which surfaces?”
P1 of the Monetization Pillar. This endpoint is the operator’s primary visibility into droplinked revenue across the trust + commerce stack. The merchant-facing billing history endpoint (P2) ships separately so the read paths stay cleanly scoped per audience.

When to use this

  • The operator wants a real-time snapshot of droplinked revenue across all PSPs
  • A finance review needs the gross / fees / net split per merchant or per PSP
  • An ops investigation needs to confirm the fee_breakdown projector is healthy (degraded: false)

Authentication

JwtAuthGuard + SuperAdminGuard. A non-admin JWT returns 403.

Request

GET /admin/monetization/platform-fees?from=ISO&to=ISO&groupBy=psp|merchant|day
ParamTypeInNotes
fromISO-8601 stringquery, optionalWindow start (inclusive). Default: 30 days ago (UTC now − 30d).
toISO-8601 stringquery, optionalWindow end (exclusive). Default: UTC now.
groupByenumquery, optionalpsp (default) | merchant | day.

Curl example

curl -s "https://apiv3.droplinked.com/admin/monetization/platform-fees?from=2026-06-01&to=2026-06-30&groupBy=psp" \
  -H "Authorization: Bearer $ADMIN_JWT" | jq .

Response (200)

{
  "windowStart": "2026-06-01T00:00:00.000Z",
  "windowEnd": "2026-06-30T00:00:00.000Z",
  "groupBy": "psp",
  "totals": {
    "grossUsd": 12480.50,
    "feesUsd": 624.03,
    "droplinkedRevenueUsd": 124.81,
    "merchantNetUsd": 11856.47,
    "txCount": 312
  },
  "rows": [
    {
      "key": "stripe",
      "label": "Stripe",
      "grossUsd": 9800.00,
      "feesUsd": 490.00,
      "droplinkedRevenueUsd": 98.00,
      "merchantNetUsd": 9310.00,
      "txCount": 244
    },
    {
      "key": "paypal",
      "label": "Paypal",
      "grossUsd": 2680.50,
      "feesUsd": 134.03,
      "droplinkedRevenueUsd": 26.81,
      "merchantNetUsd": 2546.47,
      "txCount": 68
    }
  ],
  "partialErrors": [],
  "degraded": false
}

Field reference

FieldTypeNotes
windowStart / windowEndISO-8601 stringEffective window (defaults filled in if not supplied).
groupByenumEchoes the request’s groupBy.
totals.grossUsdnumberSum of amount_usd_equivalent across rows in the window.
totals.feesUsdnumberSum of amount_fees_usd (PSP + MoR + droplinked combined).
totals.droplinkedRevenueUsdnumberSum of feeBreakdown.droplinked_gmv_fee_usd + droplinked_funding_fee_usd + droplinked_saas_fee_usd. This is droplinked’s actual revenue slice — distinct from the wider fees pool.
totals.merchantNetUsdnumberSum of amount_net_usd (net to merchant after all fees).
totals.txCountintCompleted-transaction count in the window.
rows[]arrayPer-slice rollup. Sorted by droplinkedRevenueUsd desc except when groupBy=day (chronological).
rows[].keystringGroup key — PSP name / merchant_id hex / YYYY-MM-DD.
rows[].labelstringDisplay label for the row.
partialErrors[]arrayRows that could not be processed (e.g. malformed fee_breakdown). Each entry: { slice: string, error: string }.
degradedbooleantrue iff partialErrors.length > 0. UI surfaces show a “partial data” pill when set.

Money fields are decimal USD (NOT cents)

Unlike the abandoned-cart list endpoint (which serializes integer minor units), this endpoint returns all USD values as decimals. This matches the canonical unified_transactions collection’s persisted shape — the projector’s contract (ProjectionInputDto) is explicit: “decimal, NOT minor units (canonical schema convention).” Clients pass values straight into Intl.NumberFormat({ style: 'currency', currency: 'USD' }) without dividing by 100.

Fail-open posture

A row with a malformed fee_breakdown is NOT a 500. The service emits a partialErrors entry naming the offending row’s PSP transaction id; the response still ships the rollup across the rows that succeeded. degraded: true lets the UI render a visible “showing partial data” badge. This matches the broader Stripe Head of Platform fail-open backbone discipline — never crash a read path on a single bad row. UI consumers should always check degraded and surface it to the operator.

Group-by behaviors

groupByWhat rows look like
pspOne row per PSP (stripe, paypal, telr, bonum, paymob). Most-monetizing PSP first. Excludes Coinbase (sunset).
merchantOne row per merchant_id (hex). Most-monetizing merchant first. Use the SUPER_ADMIN merchant directory to resolve hex IDs to display names.
dayOne row per YYYY-MM-DD (UTC) in the window. Chronological order — read as a time series.

Error responses

StatusWhen
400Malformed from / to (not ISO-8601).
401Missing / invalid JWT.
403Caller is not a SUPER_ADMIN.

Architecture notes

  • Reads canonical unified_transactions collection via Mongoose connection — no new schema introduced (every field already projected at PSP-webhook landing time).
  • Sums use banker’s rounding to 2 decimals (Stripe Billing Head of Product discipline).
  • completed status only — pending / failed / chargeback / refunded rows excluded. Refund + chargeback rollups will surface as separate slices in P2 follow-ups (Stripe Billing convention: refund / chargeback paths are first-class, never an exception path).
  • Coinbase is excluded from the providers enum per the Coinbase sunset memo.