# Billing

> Part of the Cowliss docs. Markdown for every page: append `index.md` to its URL. Site map: /llms.txt

<!-- GENERATED by apps/docs/scripts/generate-api-reference.ts. Do not edit. Regenerate with `pnpm -F cowliss-docs generate`. -->

Spend is one weighted total over events, emails, webhooks and automation runs, counted from what already happened, never billed in arrears. Credit is the plan's monthly included usage ($0.50 on Free, $15 on Plus; resets each UTC month, no rollover) plus the prepaid wallet (carries over). At zero credit, the platform hard-stops: ingestion is refused and sends are skipped. The plan also caps what an organization holds (live apps: 1 on Free, 15 on Plus; sending domains: 1 on Free, 10 on Plus). `limits` reports what it holds against them, and creating one more past a limit answers `plan_limit`. Subscribing to Plus and managing the subscription happen in the dashboard; the CLI and MCP read the plan from `GET /v1/billing`. See the [Billing guide](/guides/billing/).

## GET /v1/billing

Billing summary for the current UTC month

**Auth:** Session token.

**Response 200**

```json
{
  "data": {
    "plan": "free",
    "subscription": {
      "status": "string",
      "currentPeriodEnd": "2026-01-15T09:30:00.000Z",
      "cancelAtPeriodEnd": true
    },
    "limits": {
      "apps": {
        "used": 42,
        "max": 42
      },
      "senderDomains": {
        "used": 42,
        "max": 42
      }
    },
    "allowanceMicros": 42,
    "walletBalanceMicros": 42,
    "periodStart": "2026-01-15T09:30:00.000Z",
    "periodEnd": "2026-01-15T09:30:00.000Z",
    "spentMicros": 42,
    "remainingMicros": 42,
    "overageMicros": 42,
    "overageAllowed": true,
    "previousMonth": {
      "month": "string",
      "allowanceMicros": 42,
      "spentMicros": 42,
      "overageMicros": 42
    },
    "usage": {
      "events": 42,
      "emails": 42,
      "webhooks": 42,
      "automationRuns": 42
    },
    "prices": {
      "event": 42,
      "email": 42,
      "webhook": 42,
      "automationRun": 42
    },
    "warn": true,
    "over": true,
    "owing": true
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.plan` | "free" \| "plus" | yes |  |
| `data.subscription` | object \| null | yes |  |
| `data.subscription.status` | string | yes |  |
| `data.subscription.currentPeriodEnd` | string (date-time) | yes |  |
| `data.subscription.cancelAtPeriodEnd` | boolean | yes |  |
| `data.limits` | object | yes |  |
| `data.limits.apps` | object | yes |  |
| `data.limits.apps.used` | integer | yes |  |
| `data.limits.apps.max` | integer | yes |  |
| `data.limits.senderDomains` | object | yes |  |
| `data.limits.senderDomains.used` | integer | yes |  |
| `data.limits.senderDomains.max` | integer | yes |  |
| `data.allowanceMicros` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.walletBalanceMicros` | integer | yes |  |
| `data.periodStart` | string (date-time) | yes |  |
| `data.periodEnd` | string (date-time) | yes |  |
| `data.spentMicros` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.remainingMicros` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.overageMicros` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.overageAllowed` | boolean | yes |  |
| `data.previousMonth` | object \| null | yes |  |
| `data.previousMonth.month` | string | yes |  |
| `data.previousMonth.allowanceMicros` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.previousMonth.spentMicros` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.previousMonth.overageMicros` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.usage` | object | yes |  |
| `data.usage.events` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.usage.emails` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.usage.webhooks` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.usage.automationRuns` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.prices` | object | yes |  |
| `data.prices.event` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.prices.email` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.prices.webhook` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.prices.automationRun` | integer | yes | 0 ≤ x ≤ ∞ |
| `data.warn` | boolean | yes |  |
| `data.over` | boolean | yes |  |
| `data.owing` | boolean | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## GET /v1/billing/topups

List wallet top-ups

**Auth:** Session token.

**Query parameters**

```json
{
  "cursor": "string",
  "limit": 50
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `cursor` | string | no |  |
| `limit` | integer | no | default `50`; 1 ≤ x ≤ 100 |

**Response 200**

```json
{
  "data": [
    {
      "id": "string",
      "amountMicros": 42,
      "status": "pending",
      "stripeCheckoutSessionId": "string",
      "stripeAdjustmentId": "string",
      "createdAt": "2026-01-15T09:30:00.000Z",
      "completedAt": "2026-01-15T09:30:00.000Z"
    }
  ],
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object[] | yes |  |
| `data[]` | object | yes |  |
| `data[].id` | string | yes |  |
| `data[].amountMicros` | integer | yes |  |
| `data[].status` | "pending" \| "completed" \| "failed" | yes |  |
| `data[].stripeCheckoutSessionId` | string \| null | yes |  |
| `data[].stripeAdjustmentId` | string \| null | yes |  |
| `data[].createdAt` | string (date-time) | yes |  |
| `data[].completedAt` | string (date-time) \| null | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/billing/topups

Start a wallet top-up

Admin only. Returns a hosted checkout URL to redirect to. `amountMicros` must be one of the fixed presets.

**Auth:** Session token.

**Request body**

```json
{
  "data": {
    "amountMicros": 10000000
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.amountMicros` | 10000000 \| 50000000 \| 200000000 | yes |  |

**Response 201**

```json
{
  "data": {
    "checkoutUrl": "https://example.com/hooks/cowliss"
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.checkoutUrl` | string (uri) | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/billing/subscription

Subscribe to Plus

Admin only. Returns a hosted checkout URL to redirect to. The organization is on Plus once the first payment goes through.

**Auth:** Session token.

**Response 201**

```json
{
  "data": {
    "checkoutUrl": "https://example.com/hooks/cowliss"
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.checkoutUrl` | string (uri) | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/billing/subscription/portal

Manage the Plus subscription

Admin only. Returns a hosted page to redirect to, where the subscription is cancelled, its card changed, and its invoices downloaded. 404 for an organization that has never subscribed.

**Auth:** Session token.

**Response 201**

```json
{
  "data": {
    "portalUrl": "https://example.com/hooks/cowliss"
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.portalUrl` | string (uri) | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/webhooks/stripe

Stripe webhook (public)

**Auth:** None: verifies the caller's own signature/token.

Errors follow the [shared envelope and code table](/api-conventions/#errors).
