logicspike/docs

Forms

Forms Service — Billing & Credits

Last Updated: 2026-06-28 Status: Active Companion docs: architecture.md · database.md · api-spec.md


1. Rate

0.25 credits per accepted (non-spam) submission.

Spam submissions (honeypot trips, captcha failures) are stored but not billed. The credit charge happens only after the submission passes all spam guards and is stored with status=new.


2. Pre-flight Credit Gate

Before storing a submission, the service checks the tenant's available credit balance:

(balance × 1000) - accumulator_thousandths ≥ 250
  • balance = sum of active subscription credits + non-expired permanent credits (from tenant_credits in core DB)
  • accumulator_thousandths = unbilled thousandths already committed but not yet settled (from tenant_forms_usage in forms DB)
  • 250 = the cost of one submission in thousandths (0.25 × 1000)

If the gate fails → 402 Payment Required — the submission is rejected and nothing is stored.

NOTE

The credit balance is read from CORE_DATABASE_URL. If this connection is unavailable (network hiccup, DB down), the gate fails open — the submission is accepted and metered normally. The metering step will then fail silently and log an error. This is a deliberate availability trade-off (capture > billing accuracy during infra wobble).


3. Accumulator Model

Credits are billed in whole units, but submissions cost 0.25 credits each. To avoid fractional credit charges, the service uses an accumulator in tenant_forms_usage:

Every 4 submissions = 1 credit charged to the core ledger.

Settlement

When accumulator_thousandths + 250 ≥ 1000, the service:

  1. Calculates whole credits to charge: Math.floor((accumulator + 250) / 1000)
  2. Debits the core ledger: inserts a credit_transactions row (reason="forms.submission")
  3. Updates accumulator_thousandths to the remainder: (accumulator + 250) % 1000
  4. Increments settlement_seq — an optimistic lock to prevent double-charges if two submissions arrive simultaneously

Settlement runs in waitUntil() and does not block the visitor's response.


4. System Tenant Bypass

Tenants with the system:owner role bypass all credit checks — no pre-flight gate and no metering. This is used for internal tooling and demo accounts.


5. Analytics

Credit usage is surfaced in the tenant overview analytics (GET /admin/forms/overview):

{
  "credits": {
    "balance": 42.5,
    "spentThisMonth": 21.75,
    "perSubmission": 0.25,
    "pending": 500
  }
}
Field Meaning
balance Current credit balance (subscription + permanent, minus expired)
spentThisMonth Sum of credit_transactions debits with reason="forms.submission" since the 1st of the current UTC month
perSubmission Always 0.25 — the cost per submission
pending Unbilled thousandths in tenant_forms_usage.accumulator_thousandths (0–999). Divide by 1000 to get pending credits.

6. Plan-based Monthly Cap

The core DB stores a monthly submission limit per plan in plan_service_limits. The forms service reads this limit and surfaces it in the overview (usage.limit). However, this cap is not currently enforced in the public submit path — the credit balance is the hard cap. A -1 limit means no cap is configured for the tenant's plan.


7. Known Behaviors

Behavior Detail
Fail-open on core DB outage Credit gate skipped if CORE_DATABASE_URL is unreachable. Submissions are accepted and logged, but metering may not settle.
Spam not billed Honeypot and captcha failures bypass billing entirely.
Settlement idempotency settlement_seq prevents double-charges if two submissions race through the accumulator simultaneously.
No refunds on delete Deleting a submission does not refund the credit already charged.
Forms