Flexprice Documentation Audit
The docs cover a lot of surface area — pricing, metering, webhooks, integrations, MCP, an OpenAPI spec, an llms.txt — but several pages contradict each other on base URLs, error shapes, bulk endpoints, and which subscription features still exist.
1. Base URL guidance contradicts itself across regions (critical)
Location: /api-reference/introduction vs /docs/event-ingestion/sending-events, /docs/event-ingestion/api-reference, /integrations/stripe/connection-setup
Problem: The API introduction documents two regional base URLs:
- US:
https://us.api.flexprice.io/v1 - India:
https://api.cloud.flexprice.io/v1
But the event ingestion pages and the Stripe integration page hardcode only the India URL, e.g. POST https://api.cloud.flexprice.io/v1/events and the Stripe webhook target https://api.cloud.flexprice.io/v1/webhooks/stripe/tenant_ID/env_ID. There is no note instructing US-region tenants to substitute us.api.flexprice.io.
Consequence: A US-region customer copy-pasting the event ingestion curl, the bulk endpoint, or the Stripe webhook URL will send traffic to the India region. Depending on whether API keys are region-scoped, the realistic outcome is a 401 or — if keys are accepted cross-region — silent misrouting of events and webhook traffic to the wrong region. There is no documented behavior to predict which.
The fix: Replace hardcoded api.cloud.flexprice.io strings in event-ingestion and integration docs with a region-templated placeholder ({base_url} or a tabbed US/India example), and add a one-line "Pick your region" callout at the top of every page that shows a curl example. Document explicitly whether keys are region-scoped.
2. Pause/resume subscription is simultaneously removed and documented (critical)
Location: /docs/changelog (April 27th 2026) vs /api-reference/webhook-events/subscriptionpaused, /docs/webhook/webhooks, /llms.txt
Problem: The April 27th 2026 changelog explicitly states "Subscription pause and resume endpoints removed — use cancellation or trial periods instead" and "Pause and resume subscription endpoints removed from the API". Yet:
/api-reference/webhook-events/subscriptionpausedis still live and describes "subscription.paused webhook event is triggered when a subscription enters a paused state"- The webhook overview at
/docs/webhook/webhooksstill lists "pausing… and resumption" under Subscription event categories llms.txtstill publishessubscriptionpaused.mdandsubscriptionresumed.md
Consequence: Developers (and AI agents indexing llms.txt) implement webhook handlers for events that can no longer fire, or write integration tests that assume a pause API still exists. Worse, the customer-portal docs and changelog point opposing directions on whether pause is a supported lifecycle state.
The fix: Either restore the API endpoints or fully retire the webhook documentation: delete the subscription.paused/resumed pages, remove them from llms.txt, and edit the webhook category list to drop "pausing" and "resumption". Add a deprecation banner to any page that still references pause for SEO.
3. Two different error response schemas documented (critical)
Location: /api-reference/error-responses vs /api-reference/events/ingest-event
Problem: The canonical error reference says every error has success: false and an error object containing message and internal_error. The ingest-event reference page documents an entirely different shape: error objects with code, message, http_status_code, and details, plus an enum of error codes (validation_error, permission_denied, database_error, service_unavailable, …) that the canonical page doesn't acknowledge.
Consequence: Anyone writing a typed error handler — particularly in a typed SDK or an AI agent generating code from the OpenAPI spec — has to guess which schema is real. Catching error.message will work in both shapes; catching error.code works in one and silently fails in the other. There's no way for a developer to know without making real requests and observing.
The fix: Pick one shape, update the OpenAPI spec to match, and rewrite both pages against it. Publish a short table of every error code value with its corresponding HTTP status — currently the codes only appear inline on the ingest page.
4. Two bulk event endpoints documented with no guidance on which to use (significant)
Location: /docs/event-ingestion/sending-events vs /docs/changelog (March 23rd 2026)
Problem: The sending-events page documents POST https://api.cloud.flexprice.io/v1/events/bulk for "up to 1000 events per request" at 100 req/min. The March 23rd 2026 changelog announces a "new" endpoint POST /v1/events/raw/bulk that "accepts up to 1,000 raw events per request for async processing via Kafka". Both accept up to 1,000 events; neither page references the other; nothing on the sending-events page acknowledges that a "raw" variant exists.
Consequence: A developer building bulk ingestion has no way to choose: are these the same endpoint with different paths, or two endpoints with different processing semantics (sync vs async-via-Kafka)? Does the original /v1/events/bulk still exist after the March release, or is the sending-events page stale? Picking wrong likely changes durability and ordering guarantees silently.
The fix: Either consolidate into one endpoint and redirect the other, or publish a side-by-side comparison: path, processing pipeline (direct vs Kafka), durability guarantees, latency expectations, and rate limits. Update the sending-events page to reflect the post-March state.
5. "Test endpoint" is the production endpoint with the word "test" in the payload (significant)
Location: /docs/event-ingestion/api-reference
Problem: Under "Test Endpoint" the docs say "For testing purposes, you can use the same endpoints with test data" and show a curl that posts {"event_name": "test.feature", "external_customer_id": "test_customer", …} to the live production URL https://api.cloud.flexprice.io/v1/events.
Consequence: This isn't a sandbox — it's the production ingestion pipe. A developer following this section will pollute their real ClickHouse aggregations and consume the 1000 req/min rate limit shared with prod traffic. The Sandbox environment described in /docs/getting-started/cloud is never linked here, so the dedicated test path is invisible at exactly the point a developer is looking for it.
The fix: Either remove the "Test Endpoint" section or replace it with explicit Sandbox-environment instructions: how to switch to Sandbox in the dashboard, the Sandbox API key, and a note that Sandbox events do not affect production billing.
6. "Credit Grants" headlined as core feature, but only free credits are supported (significant)
Location: /docs/welcome-to-flexprice vs /docs/wallet/create
Problem: The welcome page lists "Credit Grants" as one of five core features and links it as a primary CTA. The wallet creation page then states "Currently, only free credits can be added to wallets. These credits operate on a 1:1 ratio with currency… and do not generate invoices since they're granted at no cost" and "only one active wallet per currency is allowed for a customer."
Consequence: A prospect choosing Flexprice for prepaid-credit billing (the obvious use case for "Credit Grants") will get to integration time before discovering paid top-ups aren't supported and customers can't hold multiple wallets per currency. This is exactly the kind of limitation that belongs above the fold, not three clicks into wallet creation.
The fix: Add a "Limitations" callout to the wallet docs landing page and the welcome card; change the welcome page label from "Credit Grants" to something accurate like "Free Credits" until paid top-ups ship; document the roadmap for paid credit purchase if applicable.
7. RBAC ships with only two ingestion-flavored roles for a billing platform (significant)
Location: /docs/rbac/overview
Problem: The RBAC overview documents exactly two roles: event_ingestor ("permits sending events only; blocks reading and accessing other resources") and event_reader ("permits reading event data only; blocks sending events and modifying data"). For a platform whose other documented surface area covers subscriptions, invoices, wallets, payments, customers, plans, features, prices, and connections, no admin/billing-operator/finance-readonly role is documented.
Consequence: A team trying to give finance read-only access to invoices and wallets, or to scope an automation key to "create subscriptions but not delete customers", has no documented role to assign. Either the product can't express it (a real limitation worth knowing pre-purchase) or it can but the docs don't say so. Either way, the RBAC page can't be relied on to plan least-privilege key issuance.
The fix: Either expand the documented role list to cover the rest of the resource surface (subscriptions, invoices, wallets, payments, customers, plans/features), or state explicitly that the only RBAC scopes today are the two event roles and that all other operations require an unscoped key — and link a roadmap.
8. Native webhook retry behavior is documented in one place, signing in another, with no parity table (significant)
Location: /docs/webhook/webhooks
Problem: The webhook page describes "Native Flexprice Webhooks: Direct HTTP POST requests with basic retry logic (up to 3 attempts over 2 minutes)" alongside "Svix Webhooks: advanced, robust delivery." Signing is described globally as "Every payload is signed using HMAC-SHA256 (default) or Ed25519." Self-hosted instances default to native; Cloud defaults to Svix. There's no table showing which signing scheme, header name, retry policy, or signature verification snippet applies to which mode.
Consequence: Developers self-hosting and verifying signatures will look up Svix's signature format (which Cloud customers are pointed at) and fail verification, or vice versa. Configuration variables like webhook retry intervals exist in the env config but aren't tied back to the native-vs-Svix decision.
The fix: Add a side-by-side comparison table covering: default in self-hosted vs Cloud, signing algorithm and header, retry count and backoff, history/replay availability, and the verification code snippet for each. Link the env-config webhook section to it.
9. Rate-limit response headers don't disambiguate single vs bulk limits (minor)
Location: /docs/event-ingestion/api-reference and /docs/event-ingestion/sending-events
Problem: The sending-events page documents two separate rate limits — single events at 1000 req/min, bulk events at 100 req/min. The rate-limit-headers section shows X-RateLimit-Limit: 1000 as the example. Nothing indicates whether X-RateLimit-Limit reflects the bucket of the route the caller actually hit, or whether the example just covers the single-event path.
Consequence: A developer wiring rate-limit-aware retries from response headers can't tell whether they need to special-case the bulk endpoint. Adaptive backoff implementations will overshoot or undershoot the bulk bucket.
The fix: Show the headers under both endpoints, with the actual X-RateLimit-Limit value the caller would see (1000 for single, 100 for bulk), and note that the value is per-route.
10. Stripe webhook URL placeholders never explained (minor)
Location: /integrations/stripe/connection-setup
Problem: The Stripe connection page tells the user to configure a webhook to https://api.cloud.flexprice.io/v1/webhooks/stripe/tenant_ID/env_ID but does not say where tenant_ID and env_ID come from — neither dashboard path, API call, nor relationship to other documented IDs is given.
Consequence: A developer following the Stripe integration steps stalls at the webhook URL or guesses at IDs from the dashboard, potentially configuring Stripe to POST to a non-routable path. Failures here are hard to diagnose because Stripe will report 404s from a URL the docs told them to use.
The fix: Show exactly where to find both IDs (dashboard path or API endpoint), and consider replacing path-segment IDs with a single signed token the dashboard can copy to clipboard.
11. Public docs URLs ship with spelling errors (minor)
Location: /docs/contributing-guide/code-of-contuct, /docs/contributing-guide/architechture
Problem: Two contributing-guide pages have misspelled slugs that are now part of public URLs: "contuct" (should be "conduct") and "architechture" (should be "architecture"). These appear in llms.txt-discoverable paths and in any internal cross-links.
Consequence: Developers searching for "code of conduct" or "architecture" via search engines or agent-driven retrieval may not match these slugs; a cleanup later will require redirects to avoid breaking inbound links.
The fix: Rename the slugs to the correct spellings and add 301 redirects from the old paths. Search the rest of the docs repo for the same typos.
12. Changelog has duplicate "Scheduled subscription cancellation" entries six weeks apart (minor)
Location: /docs/changelog (May 4th 2026 and March 30th 2026)
Problem: Both the May 4th 2026 entry and the March 30th 2026 entry are titled "Scheduled subscription cancellation" and describe what reads as the same capability — cancelling on a future date instead of immediately or at period end. The May entry adds a cancel_at field; the March entry says "Cancel subscriptions on a specific future date instead of only immediately or at period end."
Consequence: Readers can't tell whether the feature shipped in March, was rebuilt in May, or whether the May entry is a follow-up enhancement. AI agents summarizing the changelog will produce contradictory release histories.
The fix: Rename one of the entries (e.g. May → "Scheduled cancellation: cancel_at request field") or fold them together with a "What changed since March" note.
13. Customer portal is described as a portal but is read-only (minor)
Location: /docs/customers/customer-portal
Problem: The page calls the feature a "customer portal" with a JWT-token flow (1-hour expiry), then states "The portal provides read-only access to customer data… Customers cannot modify plans, update payment methods, or make payments through the portal."
Consequence: The word "portal" implies self-service across the SaaS industry — bill payment, plan changes, payment methods. A developer wiring this into a customer email expecting standard portal behavior will field support tickets when customers can't pay an invoice. There is also no documented refresh flow for the 1-hour JWT.
The fix: Rename the doc to "Customer usage view" or "Read-only customer portal", call out the limitations in the first paragraph, and document either a refresh-token flow or the recommended pattern for re-issuing tokens past the 1-hour expiry.
What they do well
- An OpenAPI spec is published at
/api-reference/openapi.jsonand an llms.txt enumerates every webhook event page — agents have machine-readable hooks even if some indexed pages are stale. - Configuration env vars (
FLEXPRICE_*) are categorized by subsystem (Auth, Postgres, ClickHouse, Kafka, Temporal, Webhooks) with sensible defaults documented. - The proration page actually shows worked examples with numbers ($33.34, $75 credit, $435.61) instead of waving at "prorated charges apply."
Top 3 recommendations
- Region-templatize every example URL so US-region customers don't silently send traffic to India by following copy-paste samples — and document explicitly whether API keys are region-scoped.
- Reconcile pause/resume across changelog, webhook reference, and llms.txt in one PR — either restore the endpoints or finish removing them everywhere; do the same audit for
/v1/events/bulkvs/v1/events/raw/bulk. - Pick one error-response schema and rewrite the canonical reference, the ingest page, and the OpenAPI spec to agree, then publish the full error-code table once.