Credit Pricing System
Overview
Credits are the billing unit for PDF conversions. 1 credit = 1 page for every conversion path β standard, high-fidelity, hybrid form, and premium form. The per-page rate is configurable system-wide via system_settings.credits_per_page (default 1) and applies uniformly regardless of page content type.
This replaces the previous tiered model (text=1, image=2, table=2, dense-table=3, mixed=3, plus form/high-fidelity multipliers). The high-fidelity 2x multiplier introduced in #940 was retired 2026-06-12 (HIGH_FIDELITY_CREDIT_MULTIPLIER = 1 in workers/api/src/services/system-settings.ts); between those dates high-fidelity pages billed at 2x.
Configuring the Rate
Admins change the rate at Admin β Tools β Settings β Credits per page. The setting writes system_settings.credits_per_page and audits the change to admin_audit_log. Backend workers read the value through getCreditsPerPage() in workers/api/src/services/system-settings.ts, which caches the value in-memory for ~5 seconds β so a rate change propagates across all warm Lambda containers and CF Worker isolates within that window.
Credit Deduction Points
Credits are deducted after successful conversion, not before. Every site reads creditsPerPage from system_settings:
| Path | Where Deducted | File |
|---|---|---|
| Pre-flight balance check | Before queueing the job | workers/api/src/routes/convert.ts (snapshot once per request) |
| Synchronous (struct-table, Mathpix, Marker) | Inline after conversion completes | workers/api/src/routes/convert.ts |
| Chunked async (agentic vision) | After chunk assembly completes | workers/api/src/scheduler/chunk-scheduler.ts |
| Hybrid form upload | At upload time | workers/api/src/routes/forms.ts |
| Premium form conversion | After completion | workers/api/src/routes/convert.ts (premium-form handler) |
| Large-PDF batch | At parent-job creation | workers/batch/src/convert-executor.ts |
The per-request snapshot pattern ensures a mid-request admin PUT canβt let the pre-flight pass at one rate while the deduction charges another.
Failure Modes
- DB unreachable / row missing at billing-hot-path sites:
safeGetCreditsPerPage()logs a structured error toapp_logsand falls back to the default (1) so the conversion does not abort. The fallback is logged so silent under/overcharging is visible to operators. - DB unreachable at batch executor: the throwing variant
getCreditsPerPage()is used; the job is marked failed and SQS will redeliver. Better to fail a batch job than to silently undercharge a large PDF. - Invalid value in DB (negative, non-integer, garbage): coerced to default (1) with a warn log.
Page Classification (UX-only)
Page classification (classifyDocumentPages() in pdf-complexity-detector.ts) is still run pre-conversion and still drives the CreditEstimatePanel breakdown β users see how many pages of each content type theyβre converting β but every row uses the same per-page rate. Classification no longer affects billing.
User-Facing UI
- Settings β Billing: shows the current rate and confirms β1 credit = 1 pageβ applies to all conversion paths.
- Dashboard βAuto-convert on uploadβ toggle: when off, files show a credit estimate before conversion. The estimate now equals
pageCount Γ creditsPerPageregardless of content mix. - Admin β Tools β Settings: the only place to change the rate.
Key Files
| File | Purpose |
|---|---|
supabase/migrations/20260530_133_system_settings.sql | system_settings table + seed |
workers/api/src/services/system-settings.ts | Cached getCreditsPerPage / safeGetCreditsPerPage / setSetting |
workers/api/src/services/credit-estimator.ts | computeCreditsFromClassification, estimateCredits (flat-rate) |
workers/api/src/routes/admin.ts | GET/PUT /api/admin/settings |
workers/api/src/routes/convert.ts | Per-request snapshot + every deduction site |
workers/api/src/routes/forms.ts | Hybrid form upload deduction |
workers/api/src/scheduler/chunk-scheduler.ts | Chunked job deduction (uses _creditsToDeduct from job options; fallback re-reads current rate) |
workers/batch/src/convert-executor.ts | Large-PDF pre-computed deduction |
apps/web/src/app/admin/settings/page.tsx | Admin UI for the rate |
apps/web/src/app/settings/page.tsx | User-facing pricing copy |
apps/web/src/components/dashboard/control-center/CreditEstimatePanel.tsx | Per-page breakdown UI |