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

# Billing summary

> Retrieve the current billing period, active plan, usage totals, daily timeline, overage calculations, and USD cost breakdown for your app.

The billing summary endpoint gives your backend a single snapshot of everything relevant to the current billing cycle: which plan the app is on, the active subscription period, cumulative usage totals, a day-by-day fee timeline, overage charges, and owner/retail USD breakdowns.

All monetary values are **wei as decimal strings** to preserve precision across the full BigInt range. USD micro values (integer strings, `1000000` = \$1.00) are computed once at signing time using the ETH/USD oracle and stored immutably.

## Authentication

Two auth modes are accepted. The tenant boundary is enforced identically in both: the `clientId` in the URL path must match the authenticated principal's app.

### Confidential client (recommended for server-to-server)

```http theme={null}
GET /api/v1/apps/{clientId}/billing HTTP/1.1
Authorization: Basic base64(m2m_id:m2m_secret)
```

### Provider dashboard session

A logged-in session whose user is the app's owner, a platform admin, or a `providerAdmins` team member may call this endpoint without Basic auth.

<Note>
  Requests that satisfy neither auth mode, or whose authenticated principal does not match the path `clientId`, receive **`404 Not Found`**. The endpoint deliberately does not distinguish "unauthenticated" from "not found" to avoid leaking app existence.
</Note>

## Endpoint

```http theme={null}
GET /api/v1/apps/{clientId}/billing
```

### Path parameters

| Parameter  | Type   | Description                                                                            |
| ---------- | ------ | -------------------------------------------------------------------------------------- |
| `clientId` | string | OAuth `client_id` of the developer app (`app_…`). Must match the authenticated client. |

No query parameters.

## Response

### 200 OK

```json theme={null}
{
  "clientId": "app_f4c21e7ac5f35d3e91bfad7f",
  "plan": {
    "id": "plan-uuid",
    "type": "subscription",
    "name": "Pro",
    "priceAmount": "49.00",
    "priceCurrency": "USD",
    "includedUnits": "100000",
    "includedUsdMicros": "10000000",
    "overageRateWei": "1000000000000",
    "billingCycle": "monthly",
    "status": "active"
  },
  "subscription": {
    "id": "sub-uuid",
    "status": "active",
    "currentPeriodStart": "2026-04-01T00:00:00.000Z",
    "currentPeriodEnd": "2026-04-30T23:59:59.999Z"
  },
  "cycle": {
    "periodStart": "2026-04-01T00:00:00.000Z",
    "periodEnd": "2026-04-30T23:59:59.999Z",
    "usage": {
      "requestCount": 8420,
      "totalFeeWei": "8420000000000000",
      "totalFeeEth": "0.008420",
      "networkFeeUsdMicros": "25260000",
      "ownerChargeWei": "9262000000000000",
      "ownerChargeUsdMicros": "27786000",
      "platformFeeWei": "842000000000000",
      "totalUnits": "108300"
    },
    "timeline": [
      { "date": "2026-04-01", "requestCount": 310, "feeWei": "310000000000000" },
      { "date": "2026-04-02", "requestCount": 0,   "feeWei": "0" }
    ],
    "overage": {
      "overageUnits": "8300",
      "overageWei": "8300000000000000"
    }
  },
  "platformCutPercent": 10
}
```

### Response fields

| Field                              | Type                                | Description                                                                                                          |
| ---------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `clientId`                         | string                              | Echo of the path `clientId`.                                                                                         |
| `plan`                             | object \| null                      | Active plan for the app. `null` if no plan is configured.                                                            |
| `plan.type`                        | `free` \| `subscription` \| `usage` | Determines how overage is calculated.                                                                                |
| `plan.includedUnits`               | string \| null                      | Units included in the base price. Numeric string or `null` for free plans.                                           |
| `plan.includedUsdMicros`           | string \| null                      | Subscription usage allowance in USD micros (e.g. `10000000` = \$10.00).                                              |
| `plan.overageRateWei`              | string \| null                      | Per-unit overage charge in wei. Numeric string or `null` for free plans.                                             |
| `plan.billingCycle`                | string                              | Billing cycle for the plan. Default `"monthly"`.                                                                     |
| `subscription`                     | object \| null                      | Owner's active subscription. `null` when no active subscription exists; the period falls back to the calendar month. |
| `subscription.currentPeriodStart`  | string                              | ISO 8601 start of the current billing period.                                                                        |
| `subscription.currentPeriodEnd`    | string                              | ISO 8601 end of the current billing period.                                                                          |
| `cycle.periodStart`                | string                              | Effective period start (subscription period or calendar month).                                                      |
| `cycle.periodEnd`                  | string                              | Effective period end.                                                                                                |
| `cycle.usage.requestCount`         | integer                             | Number of usage records in the period.                                                                               |
| `cycle.usage.totalFeeWei`          | string                              | Cumulative network fees in wei, as a decimal string.                                                                 |
| `cycle.usage.totalFeeEth`          | string                              | Decimal ETH equivalent of `totalFeeWei`.                                                                             |
| `cycle.usage.networkFeeUsdMicros`  | string                              | Transaction-time USD micros for network cost (oracle-priced at signing time).                                        |
| `cycle.usage.ownerChargeWei`       | string                              | Network fee plus platform cut, in wei.                                                                               |
| `cycle.usage.ownerChargeUsdMicros` | string                              | Transaction-time USD micros for owner charge.                                                                        |
| `cycle.usage.platformFeeWei`       | string                              | PymtHouse platform cut in wei.                                                                                       |
| `cycle.usage.totalUnits`           | string                              | Cumulative units consumed, as a decimal string.                                                                      |
| `cycle.timeline`                   | array                               | One entry per calendar day in the period. Days with no usage have `requestCount: 0` and `feeWei: "0"`.               |
| `cycle.timeline[].date`            | string                              | `YYYY-MM-DD` date key (UTC).                                                                                         |
| `cycle.timeline[].requestCount`    | integer                             | Requests recorded on this day.                                                                                       |
| `cycle.timeline[].feeWei`          | string                              | Fees on this day in wei.                                                                                             |
| `cycle.overage.overageUnits`       | string                              | Units consumed beyond `plan.includedUnits`. `"0"` for free plans or when below the included quota.                   |
| `cycle.overage.overageWei`         | string                              | Overage charge in wei (`overageUnits × plan.overageRateWei`).                                                        |
| `platformCutPercent`               | number \| null                      | Platform fee percentage applied to payments. `null` if not configured.                                               |

<Warning>
  All `*Wei` fields are decimal strings, not numbers. They can exceed `Number.MAX_SAFE_INTEGER`. Parse with `BigInt(field)` in JavaScript or an equivalent in your language. Use `viem`'s `formatEther` (or equivalent) for human-readable display.
</Warning>

## Plan types and overage logic

| Plan type      | `includedUnits` | `overageRateWei` | Overage calculated?                                             |
| -------------- | --------------- | ---------------- | --------------------------------------------------------------- |
| `free`         | `null`          | `null`           | No                                                              |
| `subscription` | Required        | Required         | Yes — when `totalUnits > includedUnits`                         |
| `usage`        | Optional        | Optional         | Yes — when both fields are set and `totalUnits > includedUnits` |

For `free` plans, `overageUnits` and `overageWei` are always `"0"`.

## Starter plan

Every app has a **Starter** plan created automatically (`isStarterDefault: true`). It is separate from custom billing plans and from the Network Price discovery plan. Starter carries an `includedUsdMicros` allowance (default `5000000` = \$5.00) and is automatically synced to OpenMeter.

New end-users are auto-subscribed to the Starter plan when provisioned (via `POST /users`, signer token mint, or signed-ticket ingest) if they have no existing subscription.

Providers can update the Starter allowance via:

```http theme={null}
PUT /api/v1/apps/{clientId}/starter-plan
Content-Type: application/json

{ "includedUsdMicros": "10000000" }
```

This triggers an immediate OpenMeter plan sync. The response echoes the updated Starter plan.

For per-user entitlement balances and manual top-ups, see [Allowances](/integration/allowances).

## Invoices

Tenant-scoped invoice list (OpenMeter DTO mapped):

```http theme={null}
GET /api/v1/apps/{clientId}/billing/invoices
```

Auth: provider dashboard session (read). Returns a list of invoices for the app's OpenMeter subscriptions.

## Merchant billing (Stripe Connect)

Stripe Connect is used for invoicing and end-user checkout, not for plan provisioning in OpenMeter.

| Method   | Path                                             | Auth                        | Description                                                              |
| -------- | ------------------------------------------------ | --------------------------- | ------------------------------------------------------------------------ |
| `GET`    | `/api/v1/apps/{clientId}/billing/stripe`         | Provider session            | Stripe Connect status for the app                                        |
| `POST`   | `/api/v1/apps/{clientId}/billing/stripe/connect` | App owner or platform admin | Start Stripe Connect OAuth flow                                          |
| `DELETE` | `/api/v1/apps/{clientId}/billing/stripe`         | App owner or platform admin | Disconnect Stripe                                                        |
| `POST`   | `/api/v1/apps/{clientId}/billing/checkout`       | Provider session            | End-user checkout: `{ planId, externalUserId, successUrl?, cancelUrl? }` |

<Note>
  All Stripe Connect operations require a provider dashboard session. M2M Basic auth is not accepted on these routes.
</Note>

## Period fallback

When the app has no active subscription, the billing period defaults to the **current calendar month in UTC** (midnight on the 1st to the last millisecond of the last day). This fallback applies whenever `subscription` is `null` in the response.

## Example

```bash theme={null}
export BASE_URL="https://your-pymthouse.example"
export CLIENT_ID="app_yourClientId"
export M2M_ID="m2m_yourClientId"
export M2M_SECRET="pmth_cs_yourSecret"

curl -sS \
  -u "${M2M_ID}:${M2M_SECRET}" \
  "${BASE_URL}/api/v1/apps/${CLIENT_ID}/billing" | jq .
```

Extract overage in wei with `jq`:

```bash theme={null}
curl -sS \
  -u "${M2M_ID}:${M2M_SECRET}" \
  "${BASE_URL}/api/v1/apps/${CLIENT_ID}/billing" \
  | jq '.cycle.overage.overageWei'
```

Display owner charge in USD (Node.js):

```bash theme={null}
RESPONSE=$(curl -sS \
  -u "${M2M_ID}:${M2M_SECRET}" \
  "${BASE_URL}/api/v1/apps/${CLIENT_ID}/billing")

node -e "
  const data = $(echo $RESPONSE | jq -c .);
  const usdMicros = BigInt(data.cycle.usage.ownerChargeUsdMicros ?? '0');
  const usd = Number(usdMicros) / 1_000_000;
  console.log('\$' + usd.toFixed(4));
"
```

## Error responses

| Status          | Condition                                                                                                             |
| --------------- | --------------------------------------------------------------------------------------------------------------------- |
| `404 Not Found` | No authenticated principal, credentials valid but for a different app, or `clientId` does not resolve to a known app. |

## Security boundaries

* Tenant isolation: the authenticated M2M client's `appId` must equal the path `clientId`. A valid credential for a different app returns `404`.
* Provider sessions must be the app owner, a platform admin, or a `providerAdmins` team member.
* No secrets, signer material, or per-request payloads are returned.
* Confidential client secrets must stay server-side. Do not call this endpoint from the browser with Basic auth.

## Key design decisions

1. **Single-call snapshot.** Plan, subscription, usage totals, timeline, overage, and USD breakdowns are assembled in one response so dashboard UIs can render a complete billing view without multiple round trips.
2. **Day-granularity timeline, not raw records.** The timeline buckets fee and request data by calendar day (UTC), keeping the response size bounded. For raw per-pipeline/model data, use `GET /api/v1/apps/{clientId}/usage?groupBy=pipeline_model` (see [Usage API](/integration/usage-api)).
3. **Calendar-month fallback when no subscription.** Apps on the Starter plan or free-tier state still get a consistent period reference (the current calendar month).
4. **`404` for all auth and tenant-mismatch failures.** Prevents enumeration of valid `clientId`s.
5. **USD micros stored at signing time.** Historical USD accuracy depends on the oracle at signing time; do not recompute historical values from the current oracle rate.

## Implementation tasks

* Parse all `*Wei` fields with `BigInt` before any arithmetic. Do not cast to `Number` before comparing or summing.
* Use `networkFeeUsdMicros` and `ownerChargeUsdMicros` for fiat-denominated cost reporting; `totalFeeWei` for wei-denominated analytics.
* Use the `timeline` array to drive sparklines or bar charts — every calendar day in the period is always present, so you never need to fill gaps client-side.
* When `plan` is `null`, surface a "No plan configured" state rather than treating it as an error.
* For overage alerting, poll this endpoint on a schedule and compare `cycle.overage.overageUnits` against thresholds you define in your system.
* For per-user attribution and pipeline/model breakdown, use the [Usage API](/integration/usage-api) with `groupBy=user` or `groupBy=pipeline_model`.
* To create or change plans, use [Plans](/integration/plans) from a provider dashboard session (not M2M).
* For per-user entitlement balances and allowance top-ups, see [Allowances](/integration/allowances).
