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

# Revenue

# Revenue

> **Endpoint reference: ✅ Verified live against production on 10 May 2026.**
> Revenue endpoints surface the holder's earnings from a *product-level* view — what each strain has sold and earned. Distinct from `Commissions` (per-order earnings) and `Dashboard` (top-line aggregates).

***

## Endpoints

| Method | Path                       | Auth          | Description                    |
| ------ | -------------------------- | ------------- | ------------------------------ |
| `GET`  | `/dapp/revenue`            | API-key + sig | Revenue per strain, paginated  |
| `GET`  | `/dapp/revenue/summary`    | API-key + sig | Aggregate sales and profit     |
| `GET`  | `/dapp/revenue/chart-data` | API-key + sig | Time-series for revenue charts |

***

## `GET /dapp/revenue` — revenue per strain

Returns each strain along with its sales totals over the holder's lifetime. Useful for "top sellers" tables.

### Query parameters

| Name     | Type    | Default | Notes                                                        |
| -------- | ------- | ------- | ------------------------------------------------------------ |
| `page`   | integer | `1`     |                                                              |
| `limit`  | integer | `10`    |                                                              |
| `sortBy` | string  | —       | Likely `totalRevenue`, `totalQuantity` (verify with backend) |
| `order`  | string  | —       | `asc` or `desc`                                              |

### Canonical payload

`urlencode(query)` if any present, else `"{}"`.

### Response shape (verified)

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "strains": [
      {
        "strainId": "c0a31701-a2b5-42c0-8ed2-59636e981338",
        "strainName": "Peanut Butter Breath",
        "wholeSalePrice": 6,
        "retailPrice": 10,
        "totalQuantity": 5,
        "totalSales": 50,
        "totalRevenue": 50
      }
    ],
    "pageMetaDto": {
      "page": "1", "take": 10, "itemCount": 2,
      "pageCount": 1, "hasPreviousPage": false, "hasNextPage": false
    }
  }
}
```

| Field            | Type          | Notes                                                   |
| ---------------- | ------------- | ------------------------------------------------------- |
| `strainId`       | string (UUID) | Cross-reference to [Strains](./strains.md)              |
| `strainName`     | string        | Denormalised strain name (snapshot at sale time)        |
| `wholeSalePrice` | number (USD)  | Per-unit wholesale (current value)                      |
| `retailPrice`    | number (USD)  | Per-unit retail (current value)                         |
| `totalQuantity`  | int           | Total units (grams) sold of this strain                 |
| `totalSales`     | number (USD)  | Total revenue (`totalQuantity * retailPrice` typically) |
| `totalRevenue`   | number (USD)  | Same as `totalSales` in observed data                   |

> 🪲 **`totalSales` and `totalRevenue` are equal** in the verified data — duplicate fields. Pick one.

> 🪲 **The wrapper field is `strains`, not `revenue`** — consistent with the Strains endpoint but a slightly odd choice given this endpoint is `/revenue/`. Cross-link via `strainId`.

> ⚠️ **`wholeSalePrice` and `retailPrice` are the *current* prices**, not the price at which historical sales occurred. If Dr Green changes a strain's price, this endpoint will show the new price even for old sales. For an accurate per-sale price, use [Orders](./orders.md) which captures the price snapshot.

### Worked example (Node)

```typescript theme={null}
async function topSellers(limit = 5) {
  const q = { page: '1', limit: String(limit), sortBy: 'totalRevenue', order: 'desc' };
  const payload = canonicalPayload('GET', null, q);
  const headers = authHeaders(API_KEY, SECRET_KEY, payload);
  const url = new URL('https://api.drgreennft.com/api/v1/dapp/revenue');
  Object.entries(q).forEach(([k, v]) => url.searchParams.set(k, v));
  const res = await fetch(url, { headers });
  return (await res.json()).data.strains;
}
```

***

## `GET /dapp/revenue/summary` — aggregate

The headline numbers across all strains.

### Canonical payload

`{}`

### Response shape (verified)

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "summary": {
      "totalSales": 70,
      "totalProfit": 70
    }
  }
}
```

> 🪲 **`totalSales` and `totalProfit` are equal in observed data** — `70` and `70`. This is unexpected; profit should be sales minus wholesale cost. Likely a backend issue or an interpretation difference. **Don't trust `totalProfit` from this endpoint** — cross-check against `dashboard/summary.totalProfit`. 🔒 Flag for backend confirmation.

***

## `GET /dapp/revenue/chart-data` — time-series

For plotting revenue over a date range.

### Required query parameters

| Name        | Type   | Format       | Notes     |
| ----------- | ------ | ------------ | --------- |
| `startDate` | string | `YYYY-MM-DD` | Inclusive |
| `endDate`   | string | `YYYY-MM-DD` | Inclusive |

If omitted, returns `400`:

```json theme={null}
{
  "statusCode": 400,
  "message": "startdate must be a valid date. ex: YYYY-MM-DD or MM-DD-YYYY, startDate should not be empty, ..."
}
```

### Canonical payload

`startDate=2026-04-10&endDate=2026-05-10`

### Response shape (verified, but empty for the test holder)

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": []
}
```

🔒 **Shape with populated data not yet captured** — the test holder had no revenue in the probe window. Based on the parallel `/orders/chart-data` shape (which uses the same response format), expect:

```json theme={null}
{
  "data": [
    { "name": "Friday", "value": 50, "date": "2026-04-24T00:00:00.000Z" }
  ]
}
```

Where `value` is the day's revenue in USD. **Defensively handle the empty array** — if no data in the window, the array is empty (not absent or null).

> 🪲 **Same gap-filling caveat as dashboard analytics** — empty days are not returned. Fill in the client to render a continuous chart. See [Dashboard § Empty days](./dashboard.md#response-shape-verified) for the helper function.

***

## Common patterns

### Top-sellers leaderboard

```ts theme={null}
async function topSellersWidget() {
  const top = await topSellers(5);
  return top.map(s => ({
    name: s.strainName,
    units: s.totalQuantity,
    revenue: s.totalRevenue,
    avgPrice: s.totalQuantity > 0 ? s.totalRevenue / s.totalQuantity : 0,
  }));
}
```

### Monthly revenue trend

```python theme={null}
import datetime as dt

async def monthly_trend(months_back: int = 6):
    end = dt.date.today()
    start = end - dt.timedelta(days=months_back * 30)
    q = {"startDate": start.isoformat(), "endDate": end.isoformat()}
    payload = canonical_payload("GET", query=q)
    headers = auth_headers(API_KEY, SECRET_KEY, payload)
    url = f"https://api.drgreennft.com/api/v1/dapp/revenue/chart-data?{urlencode(q)}"
    r = httpx.get(url, headers=headers)
    return r.json()["data"]  # [{name, value, date}]
```

***

## Reconciliation note

Three endpoints surface "profit"-shaped numbers:

| Source                      | Field                             | What it actually is                                   |
| --------------------------- | --------------------------------- | ----------------------------------------------------- |
| `/dapp/dashboard/summary`   | `totalProfit`                     | Lifetime profit (USD)                                 |
| `/dapp/revenue/summary`     | `totalProfit`                     | Same number, but sometimes equals `totalSales` ⚠️     |
| `/dapp/commissions/summary` | `commissionSummary.totalInDollar` | Sum of all commission records (delivered orders only) |

Use `dashboard.totalProfit` as the canonical "lifetime earnings" figure. The other two are useful for breakdowns but should reconcile back to it (within rounding).

***

## Caching guidance

| Endpoint           | TTL               |
| ------------------ | ----------------- |
| Revenue list       | 5m                |
| Revenue summary    | 5m                |
| Revenue chart-data | 5m per date range |

***

## See also

* [Strains](./strains.md) — `strainId` cross-reference
* [Orders](./orders.md) — orders capture per-sale price snapshots
* [Commissions](./commissions.md) — per-order payout view
* [Dashboard](./dashboard.md) — canonical lifetime profit figure
