Product Revenues Query
Per-product revenue analytics for the Loopwise Admin API
Note: The Loopwise Admin API is currently under development and not yet available for public use. This documentation is provided for preview purposes only.
Overview
The Product Revenues API returns revenue metrics broken down by product, ranked by revenue. Where revenueSummary returns a single school-wide aggregate, productRevenues attributes revenue to each individual course, membership plan, digital product, event, or order bump.
Revenue is computed from paid line items (payment states paid, refunding, refunded) and windowed by payment.paidAt. Line items roll up to their canonical product:
- CurriculumPlan line items roll up to their parent Course.
- Ticket line items roll up to their parent Event.
- OrderBump is treated as its own product category.
This query requires the analytics:read scope.
Available Queries
productRevenues
Returns a ranked list of products with their revenue metrics for the given period. Unlike most list queries, productRevenues is not paginated — it returns a plain array capped by limit.
Parameters:
| Parameter | Type | Description |
|---|---|---|
since | Int | Start of the period as a Unix timestamp. Defaults to 30 days ago. |
until | Int | End of the period as a Unix timestamp. Defaults to now. |
productType | AdminProductType | Restrict results to a single product category (see Product Types). |
productIds | [ID!] | Restrict results to specific canonical product ids. Requires productType — omitting it returns an error. |
paymentFilter | AdminPaymentFilter | Payment-level filters applied to the underlying payments (see Payment Filtering). |
orderBy | AdminProductRevenueOrderBy | Sort key. Defaults to TOTAL_REVENUE_DESC. |
limit | Int | Maximum number of products to return. Default 50, clamped to a maximum of 200. |
Returns:
An array of AdminProductRevenue objects, ordered by orderBy.
Example:
query {
productRevenues(
since: 1704067200,
until: 1735689600,
productType: COURSE,
limit: 10
) {
productId
productType
productName
totalRevenue
refundedAmount
ordersCount
currency
periodStart
periodEnd
}
}AdminProductRevenue Object
| Field | Type | Description |
|---|---|---|
productId | ID! | Canonical product identifier (Course / Event / MembershipPlan / DigitalProduct / OrderBump). |
productType | String! | Canonical product class name (Course, Event, MembershipPlan, DigitalProduct, or OrderBump). |
productName | String! | Human-readable product name. |
totalRevenue | Float! | Sum of post-discount line item amounts. Does not subtract refundedAmount — matches revenueSummary.totalRevenue semantics. Subtract refundedAmount yourself for net-of-refund revenue. |
refundedAmount | Float! | Sum of refunded line item amounts. Windowed by payment.paidAt like totalRevenue — this diverges from revenueSummary, which windows refunds by refundedAt. |
ordersCount | Int! | Number of distinct payments containing this product. |
currency | String! | Currency code in ISO 4217 format (e.g., TWD, USD). |
periodStart | String! | Start of the requested period (ISO 8601). |
periodEnd | String! | End of the requested period (ISO 8601). |
Net revenue:
totalRevenueis gross of refunds. To compute net revenue for a product, subtractrefundedAmountfromtotalRevenueon the client.
Product Types
The productType argument and the productType field use the AdminProductType enum:
| Value | Description |
|---|---|
COURSE | Course, aggregated across all of its curriculum plans. |
MEMBERSHIP_PLAN | Membership plan. |
DIGITAL_PRODUCT | Digital download product. |
EVENT | Event, aggregated across all of its ticket types. |
ORDER_BUMP | Order bump upsell SKU (its own product, not rolled into the underlying course/event). |
Sorting
The orderBy argument uses the AdminProductRevenueOrderBy enum:
| Value | Description |
|---|---|
TOTAL_REVENUE_DESC | Order by totalRevenue, highest first. This is the default. |
Payment Filtering
The paymentFilter argument reuses the same AdminPaymentFilter input type as the payments query, applied to the payments underlying each product's line items. This lets you compute revenue for a slice of payments — for example, only those attributed to a given affiliate, or only a specific payment method.
| Filter Field | Type | Description |
|---|---|---|
id | StringOperator | Filter by payment ID. |
amount | FloatOperator | Filter by payment amount. |
paymentState | StringOperator | Filter by payment state. |
paymentType | StringOperator | Filter by payment method (e.g., credit, atm, cvs, web_atm, barcode, line_pay). Only eq, neq, in, and nin are supported; values must be valid payment types. |
affiliateCode | StringOperator | Filter by affiliate tracking code. |
paidAt | IntOperator | Filter by payment timestamp (Unix). |
refundedAt | IntOperator | Filter by refund timestamp (Unix). |
createdAt | IntOperator | Filter by creation timestamp (Unix). |
tradeNo | StringOperator | Filter by trade/transaction number. |
See the Payments query for the full operator reference (StringOperator, FloatOperator, IntOperator).
Note: The
since/untilarguments already constrain payments bypaidAt. UsepaymentFilterfor the non-time attributes above.
Filter Examples
Revenue per course attributed to a specific affiliate:
query {
productRevenues(
productType: COURSE,
paymentFilter: {
affiliateCode: { eq: "summer-promo" }
}
) {
productId
productName
totalRevenue
ordersCount
}
}Top-selling products paid by credit card or LINE Pay:
query {
productRevenues(
paymentFilter: {
paymentType: { in: ["credit", "line_pay"] }
},
limit: 20
) {
productId
productType
productName
totalRevenue
refundedAmount
}
}Revenue for specific courses by id (requires productType):
query {
productRevenues(
productType: COURSE,
productIds: ["123", "456"]
) {
productId
productName
totalRevenue
refundedAmount
ordersCount
}
}Related Queries
revenueSummary: Returns school-wide aggregated revenue metrics for a period. UseproductRevenueswhen you need the per-product breakdown.
Detailed Query Structure
query {
productRevenues(
since: 1704067200, # Period start, Unix timestamp (Int) — default 30 days ago
until: 1735689600, # Period end, Unix timestamp (Int) — default now
productType: COURSE, # Product category (AdminProductType)
productIds: ["123"], # Canonical ids; requires productType ([ID!])
paymentFilter: { # Payment-level filters (AdminPaymentFilter)
affiliateCode: { eq: "summer-promo" }
paymentType: { in: ["credit", "line_pay"] }
},
orderBy: TOTAL_REVENUE_DESC, # Sort key (AdminProductRevenueOrderBy)
limit: 50 # Max products, clamped to 200 (Int)
) {
productId # Canonical product id (ID!)
productType # Canonical class name (String!)
productName # Product name (String!)
totalRevenue # Gross-of-refund revenue (Float!)
refundedAmount # Refunded amount (Float!)
ordersCount # Distinct payment count (Int!)
currency # ISO 4217 currency code (String!)
periodStart # Period start, ISO 8601 (String!)
periodEnd # Period end, ISO 8601 (String!)
}
}