# Billing & BYOC rendering — pay per operation, bring your own content

> **TL;DR for agents:** Campaigns have `renderMode`: **PLATFORM** (default) — the platform renders emails with its AI, billed per email (`platform_render`); **BYOC** — you render emails with your own tokens and submit finished content via `POST /api/campaigns/:id/recipients/content`, the platform bills only the mandatory audit (`audit` / `audit_failed`) and `send`. Every paid response carries `_billing {operation, cost, currency, balance_after}`; balance + pricing via `GET /api/billing/balance` (scope `billing:read`). Top-up is ONLY in the web dashboard — there is no top-up API.

## Render modes

Set `renderMode` at campaign create (`PLATFORM` | `BYOC`). The mode locks as soon as any recipient passes SCREENED. BYOC is incompatible with canvas multi-step flows (v1). In BYOC the platform never spends its AI on your emails: the render cron skips the campaign, `prepare` only audits content you already submitted, and `recipients/rerender` just resets rows to SCREENED awaiting your resubmission.

## BYOC intake

Submission itself is free. Limits: ≤100 items per call, `subject` ≤998 chars, `bodyHtml` ≤256 KB, `bodyText` ≤64 KB. Content must be FINISHED text — unresolved `{{placeholders}}` are rejected per-item at intake. Rows must be in stage SCREENED.

```bash
curl -s -X POST "https://api.live-direct-marketing.online/api/campaigns/$CAMPAIGN/recipients/content" \
  -H "Authorization: Bearer $LDM_KEY" -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "recipientId": "cmx...", "subject": "Hi Anna", "bodyHtml": "<p>Finished personalised email</p>" }
    ]
  }'
# → { "accepted": 1, "rejected": [], "awaitingAudit": 1 }
```

## Mandatory paid audit (no bypass, any mode)

- Every email — platform-rendered or BYOC — passes the audit gate before it can be sent. The audit is the ONLY writer of the READY stage; the send point additionally verifies a content hash fixed at audit time.
- Audit iterations are billed per email: passed = `audit`, failed = `audit_failed` (50% of the audit price).
- Rejections return COARSE categories only (`auditCategories`: spam_signals, quality_low, broken_personalization, link_policy, language_mix, formatting, policy_violation, other). No detailed rationale by design — the auditor is not an oracle to probe.
- Limits: ≤5 paid audit iterations per recipient (then CREATIVE_FAILED); a campaign that burns more than 5× its recipient count in audit iterations is set to AUDIT_BLOCKED — manual moderation only.

## Pricing & balance

One account balance (USD). Default prices per email-operation: `platform_render` $0.002, `audit` $0.001, `audit_failed` $0.0005, `send` $0.0005 (per-tenant overrides possible). `GET /api/billing/balance` returns balance, the pricing table and `topUpUrl`; `GET /api/billing/ledger` returns the itemised charge journal, including background cron charges.

```js
// Every paid response (MCP/A2A clients) carries:
"_billing": { "operation": "audit", "cost": 0.05, "currency": "USD", "balance_after": 12.35 }

// 402 on insufficient funds — nothing is partially executed:
{ "status": 402, "code": "insufficient_balance",
  "required": 0.098, "balance": 0.01, "currency": "USD",
  "topUpUrl": "https://app.live-direct-marketing.online/crm/settings?tab=ai" }
```

Background conveyor (cron render/audit/send) pauses the campaign with `circuitBreaker.reason = insufficient_balance` when the balance runs out — top up in the dashboard, then `POST /api/campaigns/:id/resume`. There is NO top-up via API or MCP.
