Billing
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.
GET /v1/billing
Section titled “GET /v1/billing”Billing summary for the current UTC month
Auth: Session token.
Response 200
{ "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.
GET /v1/billing/topups
Section titled “GET /v1/billing/topups”List wallet top-ups
Auth: Session token.
Query parameters
{ "cursor": "string", "limit": 50}| Field | Type | Required | Constraints |
|---|---|---|---|
cursor |
string | no | |
limit |
integer | no | default 50; 1 ≤ x ≤ 100 |
Response 200
{ "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.
POST /v1/billing/topups
Section titled “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
{ "data": { "amountMicros": 10000000 }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.amountMicros |
10000000 | 50000000 | 200000000 | yes |
Response 201
{ "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.
POST /v1/billing/subscription
Section titled “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
{ "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.
POST /v1/billing/subscription/portal
Section titled “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
{ "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.
POST /v1/webhooks/stripe
Section titled “POST /v1/webhooks/stripe”Stripe webhook (public)
Auth: None: verifies the caller’s own signature/token.
Errors follow the shared envelope and code table.