Pay-As-You-Go Pricing Catalog
Documents the MarkDocket PAYG pricing catalog structure, how live prices are fetched at SSR time via the shared customer-pricing-contract library, why no hardcoded prices appear in any UI, and how the system handles pricing unavailability gracefully.
The MarkDocket PAYG pricing catalog is a live, server-authoritative data structure shared across the entire platform. No numeric prices are stored in the web app or marketing site source code. Every price shown in the dashboard, on the pricing page, or returned by any API is fetched from the API server at request time and rendered from the catalog contract.
The CustomerPricingCatalog contract
All pricing data is defined by the CustomerPricingCatalog type, published in the shared @markdocket/customer-pricing-contract library. This library is imported by the web app, the API server, the agent microservice, and the marketing site — ensuring that every part of the platform speaks the same billing language.
The catalog carries a version constant (CUSTOMER_PRICING_CONTRACT_VERSION) that must match the version embedded in any catalog payload returned by the API. If the versions do not match, the catalog parse function returns null and the UI treats the catalog as unavailable rather than displaying data from an incompatible rate card.
Price precision
Unit amounts are stored as exact decimal strings rather than floating-point numbers. This allows the platform to represent sub-cent per-unit rates (for example, token-based pricing) without IEEE 754 rounding errors. A dedicated formatting function reads the actual decimal length of each rate and configures the locale formatter accordingly, so displayed prices are always exact.
For byte-unit rates, rescaling to per-GB is performed entirely in string arithmetic, which preserves correctness for rates that would lose precision as floats.
Public vs. internal fields
The catalog parse functions reconstruct catalog objects from scratch using only the fields that are safe to expose externally. Internal fields — including provider cost rates, rate card identifiers, internal margin data, and collection strategy hints — are silently dropped at the trust boundary before any catalog data is serialized into an API response or rendered in a UI. A suite of CI source-guard tests asserts that the API routes always call the projection wrappers before returning catalog data, and that no UI surface renders any internal field.
How prices are fetched
Both the MarkDocket app and the marketing site fetch the live pricing catalog from the API server at SSR (server-side rendering) time.
- The shared pricing client builds absolute URLs from the incoming SSR request's origin, so the same fetch logic works correctly on preview deployments and custom domains.
- Public pricing SSR calls never forward session cookies or authentication headers, preventing any session data from leaking to a public endpoint.
- The client enforces a timeout (5 seconds on the marketing site) to bound how long SSR can wait for a pricing response.
On the API server side, a last-known-good catalog cache is maintained to absorb transient meter worker stalls. During a momentary outage, the server can serve the most recently valid catalog rather than failing every SSR request.
Why no hardcoded prices appear in the UI
No hardcoded monetary literals exist anywhere in the pricing UI surfaces of either the web app or the marketing site. This is enforced automatically: the source-guard.test.ts CI test reads sibling package source files at test time and fails the build if any hardcoded numeric price value is found in a pricing UI module.
The same tests also verify that:
- Both the app and the marketing site use the shared
formatPriceAmountfunction and catalog accessor helpers — not hand-rolled display logic. - SSR pricing modules use the shared isomorphic pricing client rather than calling the billing API directly.
- No internal billing fields are referenced in any UI component.
This approach means that a pricing change on the server is immediately reflected on every surface the next time a page is rendered, with no frontend deployment required and no risk of stale prices being displayed.
Pricing temporarily unavailable
If the pricing catalog cannot be fetched or parsed — due to a network error, a version mismatch, or a meter worker outage — the UI degrades gracefully rather than showing stale or incorrect prices.
Intentional design choice
MarkDocket deliberately shows a disabled state instead of falling back to any cached or static price. This means customers never see outdated pricing, and purchase actions are only available when the system can confirm the current rate.
On the marketing site pricing page, a failed or unavailable catalog causes the page to display a "Pricing temporarily unavailable" message and disables all purchase action buttons. No partial or approximate pricing is shown.
In the dashboard billing section, the same fail-closed behavior applies. Additionally, if the billing account state returned by the API was computed under a different catalog version or policy key than the currently active catalog, the response is treated as unavailable — preventing the display of a balance computed under a stale rate card.
Billing account state
Alongside the pricing catalog, the platform fetches per-account billing state. The system distinguishes three distinct outcomes:
| Outcome | Meaning |
|---|---|
| Account found | Usage and balance data is available and current |
| No account yet | The customer has not yet incurred any charges (returned as a clean empty state) |
| Meter unavailable | The meter service returned an error or an unparsable response |
A meter outage is never reported as the "no charges yet" state. The distinction is enforced in the API layer so that a transient infrastructure problem cannot be misread as a zero balance.
Using catalog data in your integration
If you are building an integration that needs to display or apply MarkDocket PAYG pricing:
- Fetch current pricing from the authenticated billing endpoint rather than storing prices client-side.
- Treat a missing or unparsable catalog response as "pricing unavailable" and disable any purchase flow until a valid catalog is returned.
- Use the catalog's formatted unit amounts as display values; do not apply your own rounding or conversion.
- If you are processing usage quantities in byte units, note that the API exposes per-GB rates — no client-side unit conversion is needed.
Do not cache prices for extended periods
PAYG rates are version-controlled and may change. Always re-fetch the catalog at the start of a user session or billing flow. Displaying cached prices without re-validation risks showing customers incorrect rates.
Billing Overview
How MarkDocket billing works — pay-as-you-go pricing, the pricing catalog, account states, and where to find your usage and charges.
Pre-Authorization and Reservations
Explains how MarkDocket pre-authorizes billing capacity before agent turns and other operations, documents the turn state machine and CAS transitions, describes reservation lifecycle, and explains what happens to reservations and files on expiry or cancellation.