Credit Pricing System
Overview
Credits are the billing unit for PDF conversions. Each page in a document is classified by content type before conversion, and credits are charged based on the complexity of each page. This replaces the previous flat 1-credit-per-page model.
Pricing Table
| Content Type | Credits/Page | Detection Method | Processing Pipeline |
|---|---|---|---|
| Pure text | 1 | No images, tables, or math operators | Marker fast path β cascade if quality < 80 |
| Math / equations | 1 | Math font names (cmsy, stix, etc.) or image mask density | Mathpix (native equation rendering) |
| Image pages | 2 | paintImageXObject operators in PDF stream | Gemini Flash alt text + vision fallback |
| Tables | 2 | Path-drawing operators (β₯10 draw + paint ops) or struct tree tags | Struct-tree extraction (free) or vision extraction |
| Dense tables | 3 | Text position clustering (β₯5 rows, β₯3 aligned columns, no gridlines) | Always requires AI vision (MathPix or agentic vision) |
| Mixed content | 3 | Combination of images + tables + text on same page | Full agentic vision with iterative screenshot comparison |
How Classification Works
Classification runs before conversion with zero API calls. It uses PDF binary operator inspection:
classifyDocumentPages()inworkers/api/src/services/pdf-complexity-detector.tsscans every pageβs operator list- Each page gets a
contentType:text,math,image,table,dense-table, ormixed - The estimate endpoint (
POST /api/convert/:fileId/estimate) returns aCreditEstimatewith per-type breakdown - Credits are computed by
estimateCredits()inworkers/api/src/services/credit-estimator.ts
Pre-Conversion Credit Check
Before conversion starts, the system checks the user has enough credits using a worst-case estimate of 3 credits/page (the maximum rate). This prevents undercharging if classification misses complexity. The actual deduction after conversion uses the real classification.
Credit Deduction Points
Credits are deducted after successful conversion, not before:
| Path | Where Deducted | File |
|---|---|---|
| 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 |
For chunked conversions, the pre-computed credit total is stored in the jobβs options._creditsToDeduct field at creation time and read back at assembly time.
Smart Cascade (Quality-Based Escalation)
The system always attempts the cheapest viable pipeline first and escalates if output quality is below 80/100:
Text page β Marker ($0.006) β if score < 80 β Gemini Flash β if score < 80 β Claude SonnetMath page β Marker+temml β if score < 80 β MathPix β if score < 80 β VisionImage page β MathPix β if score < 80 β Vision cascadeDense table β MathPix β if score < 80 β Vision cascadeThis means every page gets the best quality output regardless of its credit rate β the credit rate reflects the expected processing cost, not the quality level.
Quality Score Components
The 80-point threshold is a composite of:
- 30% axe-core critical/serious violations
- 15% semantic HTML ratio
- 10% heading hierarchy validity
- 10% table accessibility (headers, scope)
- 10% custom WCAG validator
- 10% equation rendering (MathML vs raw LaTeX)
- 10% alt text quality
- 5% image alt coverage
Post-Assembly Quality Gate
After all chunks are assembled, a final quality gate runs:
- Detects βthinβ pages (< 100 chars of text content)
- Detects tables with headers but no data rows
- For flagged pages: renders as PNG, sends to Claude Vision for comprehensive description
- Wraps fallback in expandable
<details>panel
Form Credits (Separate)
Form conversion is a separate premium action, not part of base page pricing:
- Standard form: 3 credits/page (
FORM_CREDIT_MULTIPLIER) - Premium form: 5 credits/page (
PREMIUM_FORM_CREDIT_MULTIPLIER)
Forms are detected during preflight via AcroForm field inspection and offered as an upsell after base conversion completes.
User-Facing UI
- Settings > Billing: Pricing table showing all content types and rates
- Dashboard: βAuto-convert on uploadβ toggle β when off, files show a credit estimate breakdown before the user clicks Convert
- Credit Estimate Panel: Expandable inline component showing per-content-type breakdown with page counts and subtotals
Key Files
| File | Purpose |
|---|---|
workers/api/src/services/credit-estimator.ts | BASE_CREDITS map, estimateCredits(), computeCreditsFromClassification() |
workers/api/src/services/pdf-complexity-detector.ts | Per-page classification (classifyDocumentPages()) |
workers/api/src/services/smart-cascade-converter.ts | Quality-based pipeline escalation |
workers/api/src/services/quality-gate.ts | Post-assembly thin page detection |
workers/api/src/services/quality-scorer.ts | 80-point composite quality score |
workers/api/src/routes/convert.ts | Estimate endpoint + credit deduction |
workers/api/src/scheduler/chunk-scheduler.ts | Chunked conversion credit deduction |
apps/web/src/hooks/useCreditEstimate.ts | Frontend estimate hook with caching |
apps/web/src/components/dashboard/control-center/CreditEstimatePanel.tsx | Estimate breakdown UI |
apps/web/src/app/settings/page.tsx | Pricing table in Settings > Billing |
packages/shared/src/constants.ts | Form credit multipliers |