UnitPay Documentation Audit
UnitPay's docs (Mintlify, 31 pages, with llms.txt and llms-full.txt) tell a clear, well-written conceptual story about dollar-vs-credit billing and ship polished Node and React SDK references — but the REST API that every guide teaches by curl has no reference at all, key components and helpers used throughout the examples are undocumented, and the same concepts (env vars, check endpoint, wallet statuses, settlement outcomes) are spelled differently on different pages.
1. No API reference for a REST API the docs teach by curl (critical)
Location: https://docs.useunitpay.com/llms.txt (whole-site index); https://docs.useunitpay.com/documentation/getting-started/setup; https://docs.useunitpay.com/documentation/billing
Problem: The documentation index (llms.txt) contains no API-reference section, and probes confirm none exists: /api-reference.md → 404, /openapi.json → 404, and there is no machine-readable spec. Yet the concept and quickstart pages drive developers straight at raw REST endpoints — the Quickstart alone calls POST /v1/onboarding, POST /v1/customers, POST /v1/subscriptions, POST /v1/check, POST /v1/usage/track, GET /v1/credit-ledger, and GET /v1/credit-accounts via curl. More endpoints are name-dropped in passing with zero parameter or response documentation: "Create them with the catalog API (POST /v1/products, POST /v1/plans)" (Billing), POST /v1/credit-currencies (Quickstart), PUT /v1/customers/:id/auto-topup-rules/:creditCurrencyId (Top-ups), POST /v1/invoices/:id/pay (Billing), GET /v1/credit-grants (Wallets & the ledger), and POST /v1/onboarding/stream (AI setup).
Consequence: A developer who wants to create a plan — the very first thing the Quickstart requires ("Or build it by hand — a credit currency (POST /v1/credit-currencies) and a free plan with a monthly credit grant rule (POST /v1/plans). See Billing for the catalog API") — follows the link and finds the same endpoint named again with no request body, no fields, no response shape. The catalog API the Quickstart depends on cannot be completed from the docs. AI coding agents are hit hardest: with no OpenAPI spec and no reference pages, every endpoint's schema must be guessed from example payloads scattered across guides.
The fix: Publish an API reference section (ideally generated from an OpenAPI spec, which Mintlify supports natively) covering every /v1/* endpoint the guides mention — starting with the catalog endpoints (/v1/products, /v1/plans, /v1/credit-currencies) that the Quickstart requires but nowhere documents.
2. <PaymentForm> is used in ~15 examples, linked to the wrong page, and documented nowhere (critical)
Location: https://docs.useunitpay.com/react/checkout (Tip under useCheckoutSession); https://docs.useunitpay.com/react/payment-methods; https://docs.useunitpay.com/react/invoices
Problem: The <PaymentForm> component is the mandatory step for every card-collection flow — the Billing guide says "mount the React SDK's payment form" on requires_form, and examples across Checkout, Invoices, Payment methods, and Subscriptions render it (<PaymentForm clientSecret={form.token} />, <PaymentForm mode="save_only" clientSecret={clientSecret} />). There is no page documenting it: /react/payment-form.md → 404, and it does not appear in the llms.txt index. The one place that hyperlinks it, the Checkout page's Tip — "render the <PaymentForm> Elements step" — links to the Entitlements & gates page, which is about FeatureGate/BooleanGate/etc. and contains zero mention of PaymentForm.
Consequence: Every requires_form settlement outcome — the path every new customer without a card on file goes down — dead-ends at an undocumented component. Developers can't discover its props (is clientSecret the prop name? what does mode="save_only" accept? what are the callbacks?), and the only navigational link sends them to an unrelated gating page. Agents following the link will confidently ingest the wrong component's API.
The fix: Add a /react/payment-form reference page documenting props, modes, and completion callbacks, and repoint the Checkout Tip's link at it.
3. Two different environment variables for the same key across the auth flow (significant)
Location: https://docs.useunitpay.com/documentation/authentication ("Store it in the environment"); https://docs.useunitpay.com/node/introduction ("Instantiate")
Problem: The Authentication page's .env example is UNITPAY_SECRET_KEY=upay_sk_..., and its own prose says "The Node SDK reads UNITPAY_API_KEY automatically if you don't pass a key explicitly". The Node Introduction then instantiates with new UnitPay({ apiKey: process.env.UNITPAY_API_KEY }), while the Quickstart's curl snippets and the Billing guide use $UNITPAY_SECRET_KEY. The two names are used interchangeably across pages for the same secret key.
Consequence: A developer who follows the Authentication page verbatim (.env sets UNITPAY_SECRET_KEY) and then copies the Node Introduction snippet (process.env.UNITPAY_API_KEY) passes undefined — and per the docs "new UnitPay() throws if no key is found", so the failure appears at runtime with no hint that two pages disagreed on the variable's name. The auto-read feature is also unusable with the documented .env file, since the SDK only auto-reads UNITPAY_API_KEY.
The fix: Standardize on one variable name across every snippet (or have the SDK read both), and make the .env example on the Authentication page match the variable the SDK auto-reads.
4. The check verb is documented against two different endpoints (significant)
Location: https://docs.useunitpay.com/documentation/how-it-works ("The two verbs"); https://docs.useunitpay.com/react/entitlements-and-gates (useEntitlement, useEntitlements)
Problem: The concept docs (How UnitPay works, Quickstart) document the runtime access check as POST https://api.useunitpay.com/v1/check with customerId in the body: curl https://api.useunitpay.com/v1/check … -d '{ "customerId": "cus_123", "featureSlug": "ai-generation", "requestedUsage": 1 }'. The React SDK docs describe the same verb as POST /v1/customers/:id/check ("Pass requestedUsage (metered) or requestedValues (enum) to force a live POST /v1/customers/:id/check capacity probe") and POST /v1/customers/:id/check/batch. No page explains whether these are two routes to one API, a portal-scoped variant, or one of them stale.
Consequence: "check" is one of the "two verbs your app calls at runtime" — the core of the product's integration model — and a developer building against the raw API can't tell which route is canonical, which auth it takes (secret key vs portal token), or whether both accept requestedUsage. An agent asked to implement a server-side gate will pick whichever endpoint it scraped last, silently.
The fix: Document both routes in an API reference, state explicitly which callers use which (/v1/check with a secret key server-side; /v1/customers/:id/check behind portal tokens), and cross-link them from both pages.
5. Rendering helpers formatCredits, formatMoney, and <Money> are prescribed but never documented (significant)
Location: https://docs.useunitpay.com/documentation/credits/denominations; https://docs.useunitpay.com/react/entitlements-and-gates ("Credit features")
Problem: The Denominations page makes these helpers the required way to render balances — "Render unit credits with formatCredits(n)", "Render fiat credits as money with <Money> / formatMoney(cents, code) — never as a raw count" — and the "hard rule" section warns "never formatMoney a unit count, never formatCredits a fiat balance". The React entitlements page repeats the prescription ("Render creditBalance with formatCredits(n) for unit denominations"). A grep of the entire 187 KB docs corpus finds no import path, no package, and no reference entry for any of the three; none of the 14 React SDK pages documents them.
Consequence: Developers are told a correctness-critical rendering rule ("crossing the formatters" produces meaningless money strings) but can't find out where the formatters live — @unitpay/react? a util package? something they must write? Copy-pasting the Denominations snippets fails immediately on missing imports, which is exactly the copy-paste-completeness failure that breaks coding agents.
The fix: Document formatCredits, formatMoney, and <Money> in the React SDK reference with their import path, or rewrite the Denominations examples using only documented APIs.
6. Merchant webhooks are load-bearing but no event catalog exists (significant)
Location: https://docs.useunitpay.com/node/errors ("Verifying webhooks"); https://docs.useunitpay.com/documentation/billing ("Collecting the first card")
Problem: Webhooks carry critical state transitions — the Billing guide says a subscription "settles once the card is confirmed (a webhook provisions the subscription, invoice, and any credits)" and the Node SDK documents UnitPay.verifyWebhook(body, headers, secret) with UNITPAY_WEBHOOK_SECRET ("validates the Svix signature and returns the parsed, camelCased event"). But no page anywhere lists the webhook event types, their payload schemas, how to register an endpoint, or retry semantics. The verify snippet ends at "// handle event…" with no indication of what events can arrive. Relatedly, the SDK documents a RateLimitError for 429 responses, but no page states the actual rate limits.
Consequence: A developer can verify a webhook signature but cannot write the handler — there is no documented event name to switch on and no payload shape to type against. Since provisioning of paid subscriptions is explicitly webhook-driven, this gap sits directly on the money path.
The fix: Add a webhooks page: endpoint registration, the event catalog with payload schemas, delivery/retry semantics, and a worked handler for the subscription-provisioned event the Billing guide depends on. Document rate limits alongside the error taxonomy.
7. Wallet status casing contradicts between concepts and SDK examples (significant)
Location: https://docs.useunitpay.com/documentation/credits/wallets-and-ledger ("Wallet lifecycle"); https://docs.useunitpay.com/react/credits-and-wallets ("Credit account object")
Problem: The Wallets & the ledger page documents the lifecycle as uppercase enum values in a table — "ACTIVE | Normal operation … FROZEN | Temporarily suspended … CLOSED | Retired" — while the React Credits & wallets page's canonical "Credit account object" example returns lowercase: "status": "active". The concepts page also attributes the lifecycle to "v2 credit accounts" — a versioning label that appears nowhere else and has no migration or changelog documentation (/changelog.md → 404).
Consequence: Anyone writing if (wallet.status === 'ACTIVE') from the concepts page matches nothing against the API's lowercase values (or vice versa) — a silent, always-false comparison. Agents that ingest the lifecycle table will generate exactly this bug. The dangling "v2" reference leaves developers wondering whether there's a v1 they might be on.
The fix: Use the actual wire casing everywhere, note the casing convention once, and either document what "v2 credit accounts" means or drop the label.
8. Homepage-advertised surfaces (MCP server, Pricing Studio) have zero documentation (significant)
Location: https://www.useunitpay.com/ ("Connect", "Design" steps); absent from https://docs.useunitpay.com/llms.txt
Problem: The marketing homepage tells developers to "scaffold the whole integration with one prompt to our MCP server in Cursor" and to "Open Pricing Studio and build any model". A grep of the entire docs corpus finds zero occurrences of "MCP", "Cursor", "Pricing Studio", or "Revenue Intelligence". The docs' only setup paths are the dashboard import, raw curl, and the two SDKs.
Consequence: A developer sold on the MCP workflow — which the homepage presents as the primary "2 min" integration path — arrives at the docs and finds no server URL, no install command, no tool list, nothing to prompt Cursor with. For an AI-billing company pitching agent-first onboarding, the agent-first feature being undocumented is a conversion dead end.
The fix: Add an MCP server setup page (endpoint/config, supported tools, a Cursor walkthrough) and at least a stub for Pricing Studio, or remove them from the homepage integration steps until documented.
9. The hold/reserve flow is described with no API surface at all (significant)
Location: https://docs.useunitpay.com/documentation/credits/wallets-and-ledger ("Reserving before you spend"); https://docs.useunitpay.com/documentation/credits/overview ("When to reach for credits")
Problem: The Wallets & the ledger page promises a reservation capability: "you can hold balance on a wallet before the work starts, then confirm, adjust, or release the hold when it finishes. This reserve → work → finalize flow keeps concurrent requests from double-spending the same credits." No endpoint, SDK method, or parameter is given anywhere on the page or elsewhere in the corpus. The Credits overview even links to this section as the answer for "Metered work with an unknown cost — reserve a budget, then reconcile."
Consequence: This is the documented answer to the AI-product use case UnitPay markets hardest (long-running completions with unknown cost), and it is unimplementable from the docs — there is literally no call to make. Developers must either guess endpoints or abandon the pattern.
The fix: Document the reservation API — create/confirm/adjust/release calls, their parameters, and how holds appear in check results and the ledger.
10. Settlement outcomes: the Billing guide's table is missing three of seven kinds (minor)
Location: https://docs.useunitpay.com/documentation/billing ("Subscribing and paying"); https://docs.useunitpay.com/react/settlement ("Outcomes")
Problem: The Billing guide presents the POST /v1/subscriptions outcome as a four-row table (created_no_charge, charged_inline, requires_form, deferred), while the React Settlement model page documents seven kinds, adding invoice_sent, invoice_added, and no_action. The Billing page frames its table as the complete set ("It's a single outcome object discriminated by kind") without noting the scope is PLG-only or linking to the full union.
Consequence: A backend developer who builds an exhaustive switch on kind from the Billing page will hit unhandled invoice_sent/invoice_added/no_action outcomes the moment an SLG/send-invoice customer subscribes.
The fix: Note on the Billing page that the table shows the common self-serve outcomes and link to the Settlement model page for the full discriminated union.
11. Sandbox and live keys documented with identical prefixes and no way to tell them apart (minor)
Location: https://docs.useunitpay.com/documentation/authentication ("Sandbox vs live")
Problem: The Authentication page says the environment "is baked into the key, not the URL or a header" and then advises: "swap upay_sk_… (test) for upay_sk_… (live)" — the same literal prefix twice. Nothing on the page says how to distinguish a sandbox key from a live key by inspection.
Consequence: Because keys are the only environment selector, a developer holding two visually identical upay_sk_… strings has no documented way to confirm which environment a given key targets before it charges a real card — the exact mistake the surrounding Note warns about ("so a test key never runs in production").
The fix: Document the distinguishing marker (e.g. upay_sk_test_… vs upay_sk_live_… if it exists, or a dashboard/API way to identify a key's environment) and correct the swap example to show two distinct values.
12. Razorpay named as a supported PSP with zero integration documentation (minor)
Location: https://docs.useunitpay.com/welcome ("How is this different?"); https://docs.useunitpay.com/documentation/billing
Problem: The Welcome page states "UnitPay connects to your payment processor (Stripe, Razorpay) for moving money". Razorpay appears exactly once in the entire corpus. Every concrete payment flow assumes Stripe primitives: the Billing guide says the first card is "collected in the browser with Stripe Elements", and the Payment methods page's card-capture flow is built on a "PSP SetupIntent" mounted in the payment form.
Consequence: A Razorpay merchant (a natural audience for a billing engine) can't determine whether checkout, card-on-file, requires_form, or webhooks work at all on Razorpay — the documented flows assume Stripe primitives end to end.
The fix: Either add a Razorpay setup/flow page (or a PSP matrix noting which features are Stripe-only today), or drop Razorpay from the Welcome page until supported flows are documented.
What they do well
llms.txtand a completellms-full.txt(~187 KB) are published, and every page has a clean.mdvariant — the guide/SDK content is genuinely agent-ingestible.- The Node and React SDK references are thorough where they exist: typed errors with retry semantics, pagination contracts, and realistic JSON object examples with full field lists.
- The conceptual model (two paths, two verbs, grant priority with FIFO tie-breaks) is unusually clear and consistent as prose.
Top 3 recommendations
- Ship an API reference (OpenAPI-backed) for every
/v1/*endpoint the guides invoke — the Quickstart currently depends on endpoints that are documented nowhere. - Document the components and helpers the examples require:
<PaymentForm>(and fix its broken link),formatCredits/formatMoney/<Money>, and the reservation/hold API. - Reconcile the cross-page contradictions: one env-var name, one
checkendpoint story, one wallet-status casing, and one settlement-outcome table linked from everywhere.